1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-23 09:50:31 +01:00
onion/js/components/ascribe_media/media_player.js

261 lines
8.2 KiB
JavaScript
Raw Permalink Normal View History

2015-06-05 14:14:59 +02:00
'use strict';
2015-05-27 17:34:15 +02:00
import React from 'react';
2015-07-24 13:44:28 +02:00
import Q from 'q';
2015-06-04 17:37:46 +02:00
import Panel from 'react-bootstrap/lib/Panel';
2015-07-03 15:05:14 +02:00
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import AppConstants from '../../constants/application_constants';
import { escapeHTML } from '../../utils/general_utils';
import { InjectInHeadUtils } from '../../utils/inject_utils';
2015-05-27 17:34:15 +02:00
/**
* This is the component that implements display-specific functionality.
*
* ResourceViewer handles the following mimetypes:
* - image
* - video
* - audio
* - pdf
* - other
*/
2015-06-04 17:37:46 +02:00
let Other = React.createClass({
2015-06-05 14:14:59 +02:00
propTypes: {
url: React.PropTypes.string.isRequired
},
2015-06-04 17:37:46 +02:00
render() {
2015-09-03 16:11:41 +02:00
let filename = this.props.url.split('/').pop();
let tokens = filename.split('.');
let preview;
if (tokens.length > 1) {
preview = '.' + tokens.pop();
} else {
preview = 'file';
}
2015-06-04 17:37:46 +02:00
return (
<Panel className="media-other">
<p className="text-center">
2015-09-03 16:11:41 +02:00
{preview}
2015-06-04 17:37:46 +02:00
</p>
</Panel>
);
}
});
2015-06-03 16:53:27 +02:00
let Image = React.createClass({
2015-06-05 14:14:59 +02:00
propTypes: {
url: React.PropTypes.string,
2015-06-05 14:14:59 +02:00
preview: React.PropTypes.string.isRequired
},
2015-06-05 13:15:31 +02:00
componentDidMount() {
if(this.props.url) {
InjectInHeadUtils.inject(AppConstants.jquery.sdkUrl)
.then(() =>
Q.all([
InjectInHeadUtils.inject(AppConstants.shmui.cssUrl),
InjectInHeadUtils.inject(AppConstants.shmui.sdkUrl)
]).then(() => { window.jQuery('.shmui-ascribe').shmui(); }));
}
2015-06-05 13:15:31 +02:00
},
2015-06-03 16:53:27 +02:00
render() {
const { url, preview } = this.props;
if(url) {
return (
<img className="shmui-ascribe" src={preview} data-large-src={url}/>
);
} else {
return (
<img src={preview}/>
);
}
2015-06-03 16:53:27 +02:00
}
});
2015-07-02 18:27:09 +02:00
let Audio = React.createClass({
propTypes: {
url: React.PropTypes.string.isRequired
},
componentDidMount() {
InjectInHeadUtils.inject(AppConstants.audiojs.sdkUrl).then(this.ready);
2015-07-02 18:27:09 +02:00
},
ready() {
window.audiojs.events.ready(function() {
2015-07-03 15:05:14 +02:00
window.audiojs.createAll();
2015-07-02 18:27:09 +02:00
});
},
render() {
return (
<audio className="ascribe-audio" src={this.props.url} preload="auto" />
);
}
});
2015-07-29 18:19:04 +02:00
2015-06-03 16:53:27 +02:00
let Video = React.createClass({
2015-07-29 18:19:04 +02:00
/**
* The solution here is a bit convoluted.
* ReactJS is responsible for DOM manipulation but VideoJS updates the DOM
2015-07-29 18:24:54 +02:00
* to install itself to display the video, therefore ReactJS complains that we are
* changing the DOM under its feet.
* The component supports a fall-back to HTML5 video tag.
2015-07-29 18:19:04 +02:00
*
* What we do is the following:
* 1) set `state.libraryLoaded = null` (state.libraryLoaded can be in three states: `null`
* if we don't know anything about it, `true` if the external library has been loaded,
* `false` if we failed to load the external library)
* 2) render the cover using the `<Image />` component (because libraryLoaded is null)
2015-07-29 18:19:04 +02:00
* 3) on `componentDidMount`, we load the external `css` and `js` resources using
* the `InjectInHeadUtils`, attaching a function to `Promise.then` to change
* `state.libraryLoaded` to true
* 4) when the promise is succesfully resolved, we change `state.libraryLoaded` triggering
2015-07-29 18:19:04 +02:00
* a re-render
* 5) the new render calls `prepareVideoHTML` to get the raw HTML of the video tag
* (that will be later processed and expanded by VideoJS)
* 6) `componentDidUpdate` is called after `render`, setting `state.videoMounted` to true,
* to avoid re-installing the VideoJS library
* 7) to close the lifecycle, `componentWillUnmount` is called removing VideoJS from the DOM.
*/
2015-05-27 17:34:15 +02:00
propTypes: {
2015-06-03 16:53:27 +02:00
preview: React.PropTypes.string.isRequired,
url: React.PropTypes.string.isRequired,
2015-07-03 15:05:14 +02:00
extraData: React.PropTypes.array.isRequired,
encodingStatus: React.PropTypes.number
2015-05-27 17:34:15 +02:00
},
2015-06-03 16:53:27 +02:00
getInitialState() {
return { libraryLoaded: null, videoMounted: false };
2015-06-03 16:53:27 +02:00
},
2015-05-27 17:34:15 +02:00
componentDidMount() {
2015-07-24 13:44:28 +02:00
Q.all([
InjectInHeadUtils.inject(AppConstants.videojs.cssUrl),
InjectInHeadUtils.inject(AppConstants.videojs.sdkUrl)])
.then(() => this.setState({libraryLoaded: true}))
.fail(() => this.setState({libraryLoaded: false}));
2015-07-02 17:10:32 +02:00
},
shouldComponentUpdate(nextProps, nextState) {
return nextState.videoMounted === false;
},
2015-07-02 17:10:32 +02:00
componentDidUpdate() {
if (this.state.libraryLoaded && !this.state.videoMounted) {
2015-07-29 18:00:49 +02:00
window.videojs('#mainvideo');
2015-07-29 18:19:04 +02:00
/* eslint-disable */
2015-07-29 18:00:49 +02:00
this.setState({videoMounted: true});
2015-07-29 18:19:04 +02:00
/* eslint-enable*/
2015-07-02 17:10:32 +02:00
}
2015-06-05 14:14:59 +02:00
},
2015-07-29 18:00:49 +02:00
componentWillUnmount() {
if (this.state.videoMounted) {
window.videojs('#mainvideo').dispose();
}
2015-07-29 18:00:49 +02:00
},
prepareVideoHTML() {
let sources = this.props.extraData.map((data) => '<source type="video/' + data.type + '" src="' + escapeHTML(data.url) + '" />');
let html = [
'<video id="mainvideo" class="video-js vjs-default-skin" poster="' + escapeHTML(this.props.preview) + '"',
'controls preload="none" width="auto" height="auto">',
sources.join('\n'),
'</video>'];
return html.join('\n');
},
2015-06-03 16:53:27 +02:00
render() {
if (this.state.libraryLoaded !== null) {
2015-06-03 16:53:27 +02:00
return (
2015-07-29 18:00:49 +02:00
<div dangerouslySetInnerHTML={{__html: this.prepareVideoHTML() }}/>
2015-06-03 16:53:27 +02:00
);
} else {
2015-06-05 14:14:59 +02:00
return (
<Image preview={this.props.preview} />
2015-06-03 16:53:27 +02:00
);
}
}
});
let resourceMap = {
'image': Image,
2015-06-04 17:37:46 +02:00
'video': Video,
2015-07-02 18:27:09 +02:00
'audio': Audio,
2015-06-04 17:37:46 +02:00
'other': Other
2015-06-05 14:14:59 +02:00
};
2015-06-03 16:53:27 +02:00
2015-06-04 16:15:59 +02:00
let MediaPlayer = React.createClass({
2015-06-03 16:53:27 +02:00
propTypes: {
mimetype: React.PropTypes.oneOf(['image', 'video', 'audio', 'pdf', 'other']).isRequired,
preview: React.PropTypes.string.isRequired,
2015-06-05 14:14:59 +02:00
url: React.PropTypes.string.isRequired,
2015-07-03 15:05:14 +02:00
extraData: React.PropTypes.array,
encodingStatus: React.PropTypes.number
2015-05-27 17:34:15 +02:00
},
render() {
const { mimetype,
preview,
url,
extraData,
encodingStatus } = this.props;
if (mimetype === 'video' && encodingStatus !== undefined && encodingStatus !== 100) {
2015-07-03 15:05:14 +02:00
return (
<div className="ascribe-detail-header ascribe-media-player">
2015-07-21 17:29:58 +02:00
<p>
<em>We successfully received your video and it is now being encoded.
<br />You can leave this page and check back on the status later.</em>
</p>
<ProgressBar now={encodingStatus}
label="%(percent)s%"
className="ascribe-progress-bar" />
2015-07-03 15:05:14 +02:00
</div>
);
} else {
let Component = resourceMap[mimetype] || Other;
let componentProps = {
preview,
url,
extraData,
encodingStatus
};
// Since the launch of the portfolio whitelabel submission,
// we allow the user to specify a thumbnail upon piece-registration.
// As the `Component` is chosen according to its filetype but could potentially
// have a manually submitted thumbnail, we match if the to `Mediaplayer` submitted thumbnail
// is not the generally used fallback `url` (ascribe_spiral.png).
//
// If this is the case, we disable shmui by deleting the original `url` prop and replace
// the assigned component to `Image`.
if(!decodeURIComponent(preview).match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/) &&
Component === Other) {
Component = resourceMap.image;
delete componentProps.url;
}
2015-07-03 15:05:14 +02:00
return (
<div className="ascribe-media-player">
<Component {...componentProps}/>
2015-07-03 15:05:14 +02:00
</div>
);
}
2015-05-27 17:34:15 +02:00
}
});
2015-06-04 16:15:59 +02:00
export default MediaPlayer;