mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-19 22:12:13 +00:00
simple react player implemented
This commit is contained in:
parent
98f1d643ae
commit
d0c0e5129d
8 changed files with 289 additions and 2 deletions
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
95
src/routes/Player/Player.js
Normal file
95
src/routes/Player/Player.js
Normal 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;
|
||||
3
src/routes/Player/index.js
Normal file
3
src/routes/Player/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Player from './Player';
|
||||
|
||||
export default Player;
|
||||
107
src/routes/Player/stremio-video/HTMLVideo.js
Normal file
107
src/routes/Player/stremio-video/HTMLVideo.js
Normal 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;
|
||||
3
src/routes/Player/stremio-video/README.MD
Normal file
3
src/routes/Player/stremio-video/README.MD
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# stremio-video
|
||||
|
||||
### TODO move this folder in a separate repo
|
||||
54
src/routes/Player/stremio-video/ReactHTMLVideo.js
Normal file
54
src/routes/Player/stremio-video/ReactHTMLVideo.js
Normal 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;
|
||||
15
src/routes/Player/styles.less
Normal file
15
src/routes/Player/styles.less
Normal 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%;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue