diff --git a/package.json b/package.json index 4fb5ae3bc..b5f79c99d 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "semver": "^5.3.0", "shapeshift.io": "^1.3.1", "source-map-support": "^0.5.4", + "stream-to-blob-url": "^2.1.1", "tree-kill": "^1.1.0", "y18n": "^4.0.0" }, diff --git a/src/renderer/component/video/internal/loading-screen.jsx b/src/renderer/component/common/loading-screen.jsx similarity index 100% rename from src/renderer/component/video/internal/loading-screen.jsx rename to src/renderer/component/common/loading-screen.jsx diff --git a/src/renderer/component/fileRender/index.js b/src/renderer/component/fileRender/index.js new file mode 100644 index 000000000..5eba1d818 --- /dev/null +++ b/src/renderer/component/fileRender/index.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +import { THEME } from 'constants/settings'; +import { makeSelectClientSetting } from 'redux/selectors/settings'; + +import FileRender from './view'; + +const select = (state, props) => ({ + currentTheme: makeSelectClientSetting(THEME)(state), +}); + +const perform = dispatch => ({}); + +export default connect( + select, + perform +)(FileRender); diff --git a/src/renderer/component/fileRender/view.jsx b/src/renderer/component/fileRender/view.jsx new file mode 100644 index 000000000..badd739dd --- /dev/null +++ b/src/renderer/component/fileRender/view.jsx @@ -0,0 +1,49 @@ +// @flow +import React from 'react'; +import LoadingScreen from 'component/common/loading-screen'; +//import ThreeViewer from 'component/threeViewer'; + +type Props = { + mediaType: string, + fileSource: { + filePath: string, + fileType: string, + }, + currentTheme: string, +}; + +class FileRender extends React.PureComponent { + constructor() { + super(); + } + + routeViewer() { + const { mediaType, fileSource, currentTheme } = this.props; + + if (!mediaType || !fileSource) return null; + + // Supported mediaTypes + const mediaTypes = { + // '3D-file': () => , + // 'e-book': () => , + // 'comic-book' () => , + // Add routes to viewer... + }; + + // Return viewer + return mediaType ? mediaTypes[mediaType] : null; + } + + render() { + const Viewer = this.routeViewer(); + const unsupportedMessage = "Sorry, looks like we can't preview this file."; + + return ( +
+ {Viewer ? : } +
+ ); + } +} + +export default FileRender; diff --git a/src/renderer/component/video/internal/player.jsx b/src/renderer/component/video/internal/player.jsx index 5ec6fc965..bbf28dec7 100644 --- a/src/renderer/component/video/internal/player.jsx +++ b/src/renderer/component/video/internal/player.jsx @@ -1,13 +1,18 @@ /* eslint-disable */ import React from 'react'; import { remote } from 'electron'; -import Thumbnail from 'component/common/thumbnail'; -import player from 'render-media'; import fs from 'fs'; -import LoadingScreen from './loading-screen'; +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'; + class VideoPlayer extends React.PureComponent { static MP3_CONTENT_TYPES = ['audio/mpeg3', 'audio/mpeg']; + static FILE_MEDIA_TYPES = ['3D-file', 'e-book', 'comic-book']; constructor(props) { super(props); @@ -51,7 +56,11 @@ class VideoPlayer extends React.PureComponent { // use renderAudio override for mp3 if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { this.renderAudio(container, null, false); - } else { + } + // Render custom viewer: FileRender + if (this.fileType()) this.renderFile(); + // Render default viewer: render-media (video, audio, img, iframe) + else if (this.supportedType()) { player.append( this.file(), container, @@ -159,6 +168,38 @@ class VideoPlayer extends React.PureComponent { return ['audio', 'video'].indexOf(mediaType) !== -1; } + supportedType() { + // Files supported by render-media + const { contentType, mediaType } = this.props; + + return Object.values(player.mime).indexOf(contentType) !== -1; + } + + fileType() { + // This files are supported using a custom viewer + const { mediaType } = this.props; + + return VideoPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1; + } + + renderFile() { + // This is what render-media does with unplayable files + const { filename, downloadPath, contentType, mediaType } = this.props; + toBlobURL(fs.createReadStream(downloadPath), contentType, (err, url) => { + if (err) { + this.setState({ unsupported: true }); + return false; + } + // File to render + const fileSource = { + filePath: url, + fileType: path.extname(filename).substring(1), + }; + // Update state + this.setState({ fileSource }); + }); + } + renderAudio(container, autoplay) { if (container.firstChild) { container.firstChild.remove(); @@ -175,23 +216,27 @@ class VideoPlayer extends React.PureComponent { render() { const { mediaType, poster } = this.props; - const { hasMetadata, unplayable } = this.state; + const { hasMetadata, unplayable, unsupported, fileSource } = this.state; + const noFileMessage = 'Waiting for blob.'; const noMetadataMessage = 'Waiting for metadata.'; const unplayableMessage = "Sorry, looks like we can't play this file."; - const hideMedia = this.playableType() && !hasMetadata && !unplayable; + const unsupportedMessage = "Sorry, looks like we can't preview this file."; + const isLoadingFile = !fileSource && this.fileType(); + const isLoadingMetadata = this.playableType() && (!hasMetadata && !unplayable); + const isUnplayable = this.playableType() && unplayable; + const isUnsupported = !this.supportedType() && !this.playableType() && !this.fileType(); + const isFile = fileSource && this.fileType(); return ( - {['audio', 'application'].indexOf(mediaType) !== -1 && - (!this.playableType() || hasMetadata) && - !unplayable && } - {this.playableType() && - !hasMetadata && - !unplayable && } - {unplayable && } + {isLoadingFile && } + {isLoadingMetadata && } + {isUnplayable && } + {unsupported || isUnsupported && } + {isFile && }
{ this.media = container; }} diff --git a/src/renderer/component/video/view.jsx b/src/renderer/component/video/view.jsx index b2448ba5f..3fc373630 100644 --- a/src/renderer/component/video/view.jsx +++ b/src/renderer/component/video/view.jsx @@ -5,7 +5,7 @@ import classnames from 'classnames'; import type { Claim } from 'types/claim'; import VideoPlayer from './internal/player'; import VideoPlayButton from './internal/play-button'; -import LoadingScreen from './internal/loading-screen'; +import LoadingScreen from 'component/common/loading-screen'; const SPACE_BAR_KEYCODE = 32; @@ -128,7 +128,7 @@ class Video extends React.PureComponent { const isPlaying = playingUri === uri; const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0; const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw; - const mediaType = Lbry.getMediaType(contentType, fileInfo && fileInfo.file_name); + const mediaType = (fileInfo && Lbry.getMediaType(null, fileInfo.file_name)) || Lbry.getMediaType(contentType); let loadStatusMessage = ''; diff --git a/src/renderer/page/file/view.jsx b/src/renderer/page/file/view.jsx index 5e6cb95c9..3efe50c37 100644 --- a/src/renderer/page/file/view.jsx +++ b/src/renderer/page/file/view.jsx @@ -49,6 +49,8 @@ type Props = { }; class FilePage extends React.Component { + static MEDIA_TYPES = ['audio', '3D-file', 'e-book', 'comic-book']; + constructor(props: Props) { super(props); @@ -111,12 +113,14 @@ class FilePage extends React.Component { } = this.props; // File info - const { title, thumbnail } = metadata; + const { title, thumbnail, filename } = metadata; const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id); const shouldObscureThumbnail = obscureNsfw && metadata.nsfw; const { height, channel_name: channelName, value } = claim; - const mediaType = Lbry.getMediaType(contentType); - const isPlayable = Object.values(player.mime).includes(contentType) || mediaType === 'audio'; + // TODO: fix getMediaType logic (lbry-redux) + const mediaType = Lbry.getMediaType(null, filename) || Lbry.getMediaType(contentType); + const isPlayable = + Object.values(player.mime).indexOf(contentType) !== -1 || FilePage.MEDIA_TYPES.indexOf(mediaType); const channelClaimId = value && value.publisherSignature && value.publisherSignature.certificateId; let subscriptionUri; diff --git a/yarn.lock b/yarn.lock index 8bb0709a8..b146e4683 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8726,6 +8726,12 @@ stream-to-blob-url@^2.0.0: dependencies: stream-to-blob "^1.0.0" +stream-to-blob-url@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/stream-to-blob-url/-/stream-to-blob-url-2.1.1.tgz#e1ac97f86ca8e9f512329a48e7830ce9a50beef2" + dependencies: + stream-to-blob "^1.0.0" + stream-to-blob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-to-blob/-/stream-to-blob-1.0.0.tgz#9f7a1ada39e16ea282ebb7e4cda307edabde658d"