simple react player implemented

This commit is contained in:
NikolaBorislavovHristov 2018-11-07 16:07:45 +02:00
parent 98f1d643ae
commit d0c0e5129d
8 changed files with 289 additions and 2 deletions

View file

@ -1,4 +1,4 @@
import { Calendar, Discover, Addons, Settings, Board } from 'stremio-routes';
import { Calendar, Discover, Addons, Settings, Board, Player } from 'stremio-routes';
const config = {
views: [
@ -29,6 +29,14 @@ const config = {
component: Settings
}
]
},
{
routes: [
{
path: '/player',
component: Player
}
]
}
]
};

View file

@ -0,0 +1,95 @@
import React, { Component } from 'react';
import ReactHTMLVideo from './stremio-video/ReactHTMLVideo';
import styles from './styles';
class Player extends Component {
constructor(props) {
super(props);
this.state = {
time: null,
volume: null,
duration: null,
paused: null,
prepared: false
};
}
componentDidMount() {
this.prepareStream()
.then(({ source, component }) => {
this.VideoComponent = component;
this.setState({ prepared: true }, () => {
this.video.dispatch('command', 'load', {
source: source
});
});
})
.catch((error) => {
this.onError(error);
});
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.time !== this.state.time ||
nextState.volume !== this.state.volume ||
nextState.duration !== this.state.duration ||
nextState.paused !== this.state.paused ||
nextState.prepared !== this.state.prepared;
}
assignVideo = (video) => {
this.video = video;
}
prepareStream = () => {
return Promise.resolve({
source: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
component: ReactHTMLVideo
});
}
onPropChanged = (propName, propValue) => {
this.setState({ [propName]: propValue });
}
onPropValue = (propName, propValue) => {
this.setState({ [propName]: propValue });
}
onEnded = () => {
alert('ended');
}
onError = (error) => {
alert(error.message);
}
renderVideo() {
if (!this.state.prepared) {
return null;
}
return (
<this.VideoComponent
ref={this.assignVideo}
className={styles['video-layer']}
onPropChanged={this.onPropChanged}
onPropValue={this.onPropValue}
onEnded={this.onEnded}
onError={this.onError}
observedProps={['time', 'volume', 'duration', 'paused']}
/>
);
}
render() {
return (
<div className={styles['root-container']}>
{this.renderVideo()}
</div>
);
}
}
export default Player;

View file

@ -0,0 +1,3 @@
import Player from './Player';
export default Player;

View file

@ -0,0 +1,107 @@
var EventEmitter = require('events');
var HTMLVideo = function(videoElement) {
var events = new EventEmitter();
this.addListener = function(event, cb) {
events.addListener(event, cb);
};
this.removeListener = function(event, cb) {
events.removeListener(event, cb);
};
this.dispatch = function() {
if (arguments[0] === 'observeProp') {
var propName = arguments[1];
var propValue = videoElement[propName];
if (propName === 'time') {
propValue = videoElement.currentTime * 1000;
} else if (propName === 'duration') {
propValue = isNaN(videoElement.duration) ? null : (videoElement.duration * 1000);
}
events.emit('propValue', propName, propValue);
} else if (arguments[0] === 'setProp') {
switch (arguments[1]) {
case 'time':
videoElement.currentTime = arguments[2] / 1000;
break;
case 'paused':
arguments[2] ? videoElement.pause() : videoElement.play();
break;
case 'volume':
videoElement.volume = arguments[2];
break;
}
} else if (arguments[0] === 'command') {
var commandArgs = arguments[2];
if (arguments[1] === 'load') {
videoElement.src = commandArgs.source;
videoElement.autoplay = true;
if (!isNaN(commandArgs.time)) {
videoElement.currentTime = commandArgs.time / 1000;
}
videoElement.load();
} else if (arguments[1] === 'stop') {
videoElement.pause();
}
}
};
videoElement.addEventListener('durationchange', function() {
events.emit('propChanged', 'duration', isNaN(videoElement.duration) ? null : videoElement.duration * 1000);
});
videoElement.addEventListener('pause', function() {
events.emit('propChanged', 'paused', videoElement.paused);
});
videoElement.addEventListener('play', function() {
events.emit('propChanged', 'paused', videoElement.paused);
});
videoElement.addEventListener('volumechange', function() {
events.emit('propChanged', 'volume', videoElement.volume);
});
videoElement.addEventListener('timeupdate', function() {
events.emit('propChanged', 'time', videoElement.currentTime * 1000);
});
videoElement.addEventListener('error', function() {
var message;
var critical;
switch (videoElement.error.code) {
case 4:
message = 'video not supported';
critical = true;
break;
case 3:
message = 'error occurred when decoding';
critical = true;
break;
case 2:
message = 'error occurred when downloading';
critical = true;
break;
default:
message = 'fetching process aborted by user';
critical = false;
break;
};
events.emit('error', {
code: videoElement.error.code,
message: message,
critical: critical
});
});
videoElement.addEventListener('ended', function() {
events.emit('ended');
});
};
HTMLVideo.manifest = {
name: 'HTMLVideo',
embedded: true,
controls: ['playback', 'time', 'volume']
};
module.exports = HTMLVideo;

View file

@ -0,0 +1,3 @@
# stremio-video
### TODO move this folder in a separate repo

View file

@ -0,0 +1,54 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import HTMLVideo from './HTMLVideo';
class ReactHTMLVideo extends Component {
componentDidMount() {
this.video = new HTMLVideo(this.videoElement);
this.video.addListener('propChanged', this.props.onPropChanged);
this.video.addListener('propValue', this.props.onPropValue);
this.video.addListener('error', this.props.onError);
this.video.addListener('ended', this.props.onEnded);
this.props.observedProps.forEach((propName) => {
this.video.dispatch('observeProp', propName);
});
}
componentWillUnmount() {
this.video.removeListener('propChanged', this.props.onPropChanged);
this.video.removeListener('propValue', this.props.onPropValue);
this.video.removeListener('error', this.props.onError);
this.video.removeListener('ended', this.props.onEnded);
}
shouldComponentUpdate() {
return false;
}
assignVideoElement = (videoElement) => {
this.videoElement = videoElement;
}
dispatch = (...args) => {
this.video.dispatch(...args);
}
render() {
return (
<video ref={this.assignVideoElement} className={this.props.className}></video>
);
}
}
ReactHTMLVideo.manifest = HTMLVideo.manifest;
ReactHTMLVideo.propTypes = {
className: PropTypes.string,
onPropChanged: PropTypes.func.isRequired,
onPropValue: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
onEnded: PropTypes.func.isRequired,
observedProps: PropTypes.arrayOf(PropTypes.string).isRequired
};
export default ReactHTMLVideo;

View file

@ -0,0 +1,15 @@
.root-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.video-layer {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
}
}

View file

@ -6,6 +6,7 @@ import Library from './Library';
import Calendar from './Calendar';
import Search from './Search';
import Settings from './Settings';
import Player from './Player';
export {
Addons,
@ -15,5 +16,6 @@ export {
Library,
Calendar,
Search,
Settings
Settings,
Player
};