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';
|
2015-11-09 14:32:14 +01:00
|
|
|
|
|
|
|
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.isRequired,
|
|
|
|
preview: React.PropTypes.string.isRequired
|
|
|
|
},
|
|
|
|
|
2015-06-05 13:15:31 +02:00
|
|
|
componentDidMount() {
|
2015-11-09 14:32:14 +01:00
|
|
|
InjectInHeadUtils.inject('https://code.jquery.com/jquery-2.1.4.min.js')
|
2015-06-05 13:15:31 +02:00
|
|
|
.then(() =>
|
2015-07-24 13:44:28 +02:00
|
|
|
Q.all([
|
2015-11-09 14:32:14 +01:00
|
|
|
InjectInHeadUtils.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/shmui.css'),
|
|
|
|
InjectInHeadUtils.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/jquery.shmui.js')
|
2015-06-05 14:14:59 +02:00
|
|
|
]).then(() => { window.jQuery('.shmui-ascribe').shmui(); }));
|
2015-06-05 13:15:31 +02:00
|
|
|
},
|
|
|
|
|
2015-06-03 16:53:27 +02:00
|
|
|
render() {
|
2015-06-04 17:17:39 +02:00
|
|
|
return (
|
2015-06-05 13:15:31 +02:00
|
|
|
<img className="shmui-ascribe" src={this.props.preview} data-large-src={this.props.url}/>
|
2015-06-04 17:37:46 +02:00
|
|
|
);
|
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() {
|
2015-11-09 14:32:14 +01:00
|
|
|
InjectInHeadUtils.inject(AppConstants.baseUrl + 'static/thirdparty/audiojs/audiojs/audio.min.js').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.
|
2015-10-20 14:16:49 +02:00
|
|
|
* The component supports a fall-back to HTML5 video tag.
|
2015-07-29 18:19:04 +02:00
|
|
|
*
|
|
|
|
* What we do is the following:
|
2015-10-20 14:16:49 +02:00
|
|
|
* 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
|
2015-11-09 14:32:14 +01:00
|
|
|
* the `InjectInHeadUtils`, attaching a function to `Promise.then` to change
|
2015-10-20 14:16:49 +02:00
|
|
|
* `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() {
|
2015-10-20 14:16:49 +02:00
|
|
|
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([
|
2015-11-09 14:32:14 +01:00
|
|
|
InjectInHeadUtils.inject('//vjs.zencdn.net/4.12/video-js.css'),
|
|
|
|
InjectInHeadUtils.inject('//vjs.zencdn.net/4.12/video.js')])
|
2015-10-20 14:16:49 +02:00
|
|
|
.then(() => this.setState({libraryLoaded: true}))
|
|
|
|
.fail(() => this.setState({libraryLoaded: false}));
|
2015-07-02 17:10:32 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
componentDidUpdate() {
|
2015-10-20 14:16:49 +02:00
|
|
|
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() {
|
2015-10-20 14:16:49 +02:00
|
|
|
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');
|
|
|
|
},
|
|
|
|
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
|
|
return nextState.videoMounted === false;
|
2015-06-03 16:53:27 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
render() {
|
2015-10-20 14:16:49 +02:00
|
|
|
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 (
|
2015-06-03 16:53:27 +02:00
|
|
|
<Image src={this.props.preview} />
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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() {
|
2015-07-08 14:37:20 +02:00
|
|
|
if (this.props.mimetype === 'video' && this.props.encodingStatus !== undefined && this.props.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>
|
2015-07-03 15:05:14 +02:00
|
|
|
<ProgressBar now={this.props.encodingStatus}
|
2015-09-09 14:11:52 +02:00
|
|
|
label="%(percent)s%"
|
|
|
|
className="ascribe-progress-bar" />
|
2015-07-03 15:05:14 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
let Component = resourceMap[this.props.mimetype] || Other;
|
|
|
|
return (
|
|
|
|
<div className="ascribe-media-player">
|
|
|
|
<Component preview={this.props.preview}
|
|
|
|
url={this.props.url}
|
|
|
|
extraData={this.props.extraData}
|
|
|
|
encodingStatus={this.props.encodingStatus} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2015-05-27 17:34:15 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-06-04 16:15:59 +02:00
|
|
|
export default MediaPlayer;
|