From 1367eef934d367d972e5c87af71f0b88583867d2 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Fri, 1 Mar 2019 07:51:33 +0900 Subject: [PATCH 1/2] fix: load correct video player on web --- package.json | 1 - .../component/fileViewer/internal/player.jsx | 233 +++++++++--------- .../component/viewers/audioVideoViewer.jsx | 1 - src/renderer/scss/component/_file-render.scss | 6 +- yarn.lock | 2 +- 5 files changed, 121 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index 2295ed8f0..9f1318d89 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "reselect": "^3.0.0", "semver": "^5.3.0", "source-map-support": "^0.5.4", - "stream-to-blob-url": "^2.1.1", "three": "^0.93.0", "tree-kill": "^1.1.0", "video.js": "^7.2.2", diff --git a/src/renderer/component/fileViewer/internal/player.jsx b/src/renderer/component/fileViewer/internal/player.jsx index ff8ce2121..b247f4fe2 100644 --- a/src/renderer/component/fileViewer/internal/player.jsx +++ b/src/renderer/component/fileViewer/internal/player.jsx @@ -1,40 +1,75 @@ -/* eslint-disable */ -import React from 'react'; +// @flow +import type { Claim } from 'types/claim'; +import * as React from 'react'; import { remote } from 'electron'; import fs from 'fs'; import path from 'path'; import player from 'render-media'; -import toBlobURL from 'stream-to-blob-url'; import FileRender from 'component/fileRender'; -import Thumbnail from 'component/common/thumbnail'; import LoadingScreen from 'component/common/loading-screen'; +// Loading this here so we have uniform video style even if we use two different players +import 'video.js/dist/video-js.css'; -class MediaPlayer extends React.PureComponent { - static MP3_CONTENT_TYPES = ['audio/mpeg3', 'audio/mpeg']; +type Props = { + contentType: string, + mediaType: string, + downloadCompleted: boolean, + playingUri: ?string, + volume: number, + position: ?number, + downloadPath: string, + fileName: string, + claim: Claim, + onStartCb: ?() => void, + onFinishCb: ?() => void, + savePosition: number => void, + changeVolume: number => void, +}; + +type State = { + hasMetadata: boolean, + unplayable: boolean, + fileSource: ?{ + url?: string, + fileName?: string, + contentType?: string, + downloadPath?: string, + fileType?: string, + }, +}; + +class MediaPlayer extends React.PureComponent { static SANDBOX_TYPES = ['application/x-lbry', 'application/x-ext-lbry']; - static FILE_MEDIA_TYPES = ['text', 'script', 'e-book', 'comic-book', 'document', '3D-file']; + static FILE_MEDIA_TYPES = [ + 'text', + 'script', + 'e-book', + 'comic-book', + 'document', + '3D-file', + // The web can use the new video player, which has it's own file renderer + // @if TARGET='web' + 'video', + 'audio', + // @endif + ]; static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/'; static SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278'; - constructor(props) { + mediaContainer: { current: React.ElementRef }; + + constructor(props: Props) { super(props); this.state = { hasMetadata: false, - startedPlaying: false, unplayable: false, fileSource: null, }; - this.togglePlayListener = this.togglePlay.bind(this); - this.toggleFullScreenVideo = this.toggleFullScreen.bind(this); - } - - componentDidUpdate(nextProps) { - const el = this.refs.media.children[0]; - if (this.props.playingUri && !nextProps.playingUri && !el.paused) { - el.pause(); - } + this.mediaContainer = React.createRef(); + (this: any).togglePlay = this.togglePlay.bind(this); + (this: any).toggleFullScreen = this.toggleFullScreen.bind(this); } componentDidMount() { @@ -42,6 +77,7 @@ class MediaPlayer extends React.PureComponent { // Temp hack to force the video to play if the metadataloaded event was never fired // Will be removed with the new video player + // @if TARGET='app' setTimeout(() => { const { hasMetadata } = this.state; if (!hasMetadata) { @@ -49,47 +85,38 @@ class MediaPlayer extends React.PureComponent { this.playMedia(); } }, 5000); + // @endif } - componentWillReceiveProps(next) { - const el = this.media.children[0]; - if (!this.props.paused && next.paused && !el.paused) el.pause(); - } + // @if TARGET='app' + componentDidUpdate(prevProps: Props) { + const { downloadCompleted } = this.props; + const { fileSource } = this.state; - componentDidUpdate() { - const { contentType, downloadCompleted } = this.props; - const { startedPlaying, fileSource } = this.state; + const el = this.mediaContainer.current; - if (this.playableType() && !startedPlaying && downloadCompleted) { - const container = this.media.children[0]; - - if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { - this.renderAudio(this.media, true); - } else { - player.append( - this.file(), - container, - { autoplay: true, controls: true }, - renderMediaCallback.bind(this) - ); - } - } else if (this.fileType() && !fileSource && downloadCompleted) { + if (this.props.playingUri && !prevProps.playingUri && !el.paused) { + el.pause(); + } else if (this.isSupportedFile() && !fileSource && downloadCompleted) { this.renderFile(); } } + // @endif componentWillUnmount() { - document.removeEventListener('keydown', this.togglePlayListener); - const mediaElement = this.media.children[0]; + document.removeEventListener('keydown', this.togglePlay); + const mediaElement = this.mediaContainer.current.children[0]; if (mediaElement) { - mediaElement.removeEventListener('click', this.togglePlayListener); + mediaElement.removeEventListener('click', this.togglePlay); } } - toggleFullScreen(event) { - const mediaElement = this.media.children[0]; + toggleFullScreen() { + const mediaElement = this.mediaContainer.current; if (mediaElement) { + // $FlowFixMe if (document.webkitIsFullScreen) { + // $FlowFixMe document.webkitExitFullscreen(); } else { mediaElement.webkitRequestFullScreen(); @@ -98,19 +125,17 @@ class MediaPlayer extends React.PureComponent { } playMedia() { - const { hasMetadata } = this.state; - - const container = this.media; + // @if TARGET='app' + const container = this.mediaContainer.current; const { downloadCompleted, - contentType, changeVolume, volume, position, - claim, - onStartCb, onFinishCb, savePosition, + downloadPath, + fileName, } = this.props; const renderMediaCallback = error => { @@ -121,29 +146,29 @@ class MediaPlayer extends React.PureComponent { const win32FullScreenChange = () => { const win = remote.BrowserWindow.getFocusedWindow(); if (process.platform === 'win32') { + // $FlowFixMe win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu()); } }; - // use renderAudio override for mp3 - if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { - this.renderAudio(container, null, false); - } // Render custom viewer: FileRender - else if (this.fileType()) { - downloadCompleted && this.renderFile(); + if (this.isSupportedFile() && downloadCompleted) { + this.renderFile(); } // Render default viewer: render-media (video, audio, img, iframe) else { player.append( - this.file(), + { + name: fileName, + createReadStream: opts => fs.createReadStream(downloadPath, opts), + }, container, { autoplay: true, controls: true }, renderMediaCallback.bind(this) ); } - document.addEventListener('keydown', this.togglePlayListener); + document.addEventListener('keydown', this.togglePlay); const mediaElement = container.children[0]; if (mediaElement) { if (position) { @@ -152,7 +177,7 @@ class MediaPlayer extends React.PureComponent { mediaElement.addEventListener('loadedmetadata', () => this.refreshMetadata()); mediaElement.addEventListener('timeupdate', () => savePosition(mediaElement.currentTime)); - mediaElement.addEventListener('click', this.togglePlayListener); + mediaElement.addEventListener('click', this.togglePlay); mediaElement.addEventListener('ended', () => { if (onFinishCb) { onFinishCb(); @@ -164,34 +189,45 @@ class MediaPlayer extends React.PureComponent { changeVolume(mediaElement.volume); }); mediaElement.volume = volume; - mediaElement.addEventListener('dblclick', this.toggleFullScreenVideo); + mediaElement.addEventListener('dblclick', this.toggleFullScreen); } + // @endif + + // On the web, we have viewers for every file like normal people + + // @if TARGET='web' + if (this.isSupportedFile()) { + this.renderFile(); + } + // @endif } + // @if TARGET='app' refreshMetadata() { const { onStartCb } = this.props; - this.setState({ hasMetadata: true, startedPlaying: true }); + this.setState({ hasMetadata: true }); if (onStartCb) { onStartCb(); } - this.media.children[0].play(); - } - setReady() { - this.setState({ ready: true }); + const playerElement = this.mediaContainer.current; + if (playerElement) { + playerElement.children[0].play(); + } } + // @endif - togglePlay(event) { + togglePlay(event: any) { // ignore all events except click and spacebar keydown, or input events in a form control if ( event.type === 'keydown' && - (event.code !== 'Space' || event.target.tagName.toLowerCase() === 'input') + (event.code !== 'Space' || (event.target && event.target.tagName.toLowerCase() === 'input')) ) { return; } event.preventDefault(); - const mediaElement = this.media.children[0]; + const mediaElement = this.mediaContainer.current.children[0]; if (mediaElement) { if (!mediaElement.paused) { mediaElement.pause(); @@ -201,32 +237,21 @@ class MediaPlayer extends React.PureComponent { } } - file() { - const { downloadPath, fileName } = this.props; - - return { - name: fileName, - createReadStream: opts => fs.createReadStream(downloadPath, opts), - }; - } - - playableType() { + playableType(): boolean { const { mediaType } = this.props; - return ['audio', 'video'].indexOf(mediaType) !== -1; } - supportedType() { + isRenderMediaSupported() { // Files supported by render-media - const { contentType, mediaType } = this.props; - + const { contentType } = this.props; return ( Object.values(player.mime).indexOf(contentType) !== -1 || MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1 ); } - fileType() { + isSupportedFile() { // This files are supported using a custom viewer const { mediaType, contentType } = this.props; @@ -238,7 +263,7 @@ class MediaPlayer extends React.PureComponent { renderFile() { // This is what render-media does with unplayable files - const { claim, fileName, downloadPath, contentType, mediaType } = this.props; + const { claim, fileName, downloadPath, contentType } = this.props; if (MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1) { const outpoint = `${claim.txid}:${claim.nout}`; @@ -259,36 +284,15 @@ class MediaPlayer extends React.PureComponent { fileType: path.extname(fileName).substring(1), }; - // Readable stream from file - fileSource.stream = opts => fs.createReadStream(downloadPath, opts); - - // Blob url from stream - fileSource.blob = callback => - toBlobURL(fs.createReadStream(downloadPath), contentType, callback); - // Update state this.setState({ fileSource }); } - renderAudio(container, autoplay) { - if (container.firstChild) { - container.firstChild.remove(); - } - - // clear the container - const { downloadPath } = this.props; - const audio = document.createElement('audio'); - audio.autoplay = autoplay; - audio.controls = true; - audio.src = downloadPath; - container.appendChild(audio); - } - - showLoadingScreen(isFileType, isPlayableType) { + showLoadingScreen(isFileType: boolean, isPlayableType: boolean) { const { mediaType, contentType } = this.props; - const { hasMetadata, unplayable, unsupported, fileSource } = this.state; + const { unplayable, fileSource, hasMetadata } = this.state; - const loader = { + const loader: { isLoading: boolean, loadingStatus: ?string } = { isLoading: false, loadingStatus: null, }; @@ -306,7 +310,7 @@ class MediaPlayer extends React.PureComponent { const isLbryPackage = /application\/x(-ext)?-lbry$/.test(contentType); const isUnsupported = (mediaType === 'application' && !isLbryPackage) || - (!this.supportedType() && !isFileType && !isPlayableType); + (!this.isRenderMediaSupported() && !isFileType && !isPlayableType); // Media (audio, video) const isUnplayable = isPlayableType && unplayable; const isLoadingMetadata = isPlayableType && (!hasMetadata && !unplayable); @@ -320,7 +324,7 @@ class MediaPlayer extends React.PureComponent { } else if (isUnsupported || isUnplayable) { loader.loadingStatus = isUnsupported ? unsupportedMessage : unplayableMessage; } else if (isLbryPackage && !isLoadingFile) { - loader.loadingStatus = false; + loader.loadingStatus = null; } return loader; @@ -330,7 +334,7 @@ class MediaPlayer extends React.PureComponent { const { mediaType } = this.props; const { fileSource } = this.state; - const isFileType = this.fileType(); + const isFileType = this.isSupportedFile(); const isFileReady = fileSource && isFileType; const isPlayableType = this.playableType(); const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType); @@ -340,11 +344,9 @@ class MediaPlayer extends React.PureComponent { {loadingStatus && } {isFileReady && }
{ - this.media = container; - }} + ref={this.mediaContainer} /> ); @@ -352,4 +354,3 @@ class MediaPlayer extends React.PureComponent { } export default MediaPlayer; -/* eslint-disable */ diff --git a/src/renderer/component/viewers/audioVideoViewer.jsx b/src/renderer/component/viewers/audioVideoViewer.jsx index 443676fd7..7cba07a40 100644 --- a/src/renderer/component/viewers/audioVideoViewer.jsx +++ b/src/renderer/component/viewers/audioVideoViewer.jsx @@ -2,7 +2,6 @@ import React from 'react'; import { stopContextMenu } from 'util/context-menu'; import videojs from 'video.js'; -import 'video.js/dist/video-js.css'; type Props = { source: { diff --git a/src/renderer/scss/component/_file-render.scss b/src/renderer/scss/component/_file-render.scss index 839cfc734..c4782327c 100644 --- a/src/renderer/scss/component/_file-render.scss +++ b/src/renderer/scss/component/_file-render.scss @@ -35,9 +35,9 @@ // Removing the play button because we have autoplay turned on // These are classes added by video.js - // .video-js .vjs-big-play-button { - // display: none; - // } + .video-js .vjs-big-play-button { + display: none; + } } .document-viewer { diff --git a/yarn.lock b/yarn.lock index f9b27c785..2eab931e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9340,7 +9340,7 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -stream-to-blob-url@^2.0.0, stream-to-blob-url@^2.1.1: +stream-to-blob-url@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/stream-to-blob-url/-/stream-to-blob-url-2.1.1.tgz#e1ac97f86ca8e9f512329a48e7830ce9a50beef2" dependencies: -- 2.45.3 From 27eb1a8dbee9df01c891e75bde58793478b69da3 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Fri, 1 Mar 2019 08:13:37 +0900 Subject: [PATCH 2/2] fix: button style in file description --- .../component/fileViewer/internal/player.jsx | 2 -- .../component/viewers/audioVideoViewer.jsx | 1 + src/renderer/scss/component/_button.scss | 17 ++++++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/renderer/component/fileViewer/internal/player.jsx b/src/renderer/component/fileViewer/internal/player.jsx index b247f4fe2..48280afaf 100644 --- a/src/renderer/component/fileViewer/internal/player.jsx +++ b/src/renderer/component/fileViewer/internal/player.jsx @@ -7,8 +7,6 @@ import path from 'path'; import player from 'render-media'; import FileRender from 'component/fileRender'; import LoadingScreen from 'component/common/loading-screen'; -// Loading this here so we have uniform video style even if we use two different players -import 'video.js/dist/video-js.css'; type Props = { contentType: string, diff --git a/src/renderer/component/viewers/audioVideoViewer.jsx b/src/renderer/component/viewers/audioVideoViewer.jsx index 7cba07a40..443676fd7 100644 --- a/src/renderer/component/viewers/audioVideoViewer.jsx +++ b/src/renderer/component/viewers/audioVideoViewer.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { stopContextMenu } from 'util/context-menu'; import videojs from 'video.js'; +import 'video.js/dist/video-js.css'; type Props = { source: { diff --git a/src/renderer/scss/component/_button.scss b/src/renderer/scss/component/_button.scss index 82c7a0697..591ee1c46 100644 --- a/src/renderer/scss/component/_button.scss +++ b/src/renderer/scss/component/_button.scss @@ -92,14 +92,17 @@ } } -.button--link:not(:disabled) { - html[data-mode='dark'] & { - &:not(:hover) { - color: $lbry-teal-4; - } +.button--link { + word-break: break-all; + &:not(:disabled) { + html[data-mode='dark'] & { + &:not(:hover) { + color: $lbry-teal-4; + } - &:hover { - color: $lbry-teal-3; + &:hover { + color: $lbry-teal-3; + } } } } -- 2.45.3