Skip to content

Instantly share code, notes, and snippets.

@formigone
Last active May 13, 2016 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save formigone/7569ef180a2eeaf264261e61cd81f8a9 to your computer and use it in GitHub Desktop.
Save formigone/7569ef180a2eeaf264261e61cd81f8a9 to your computer and use it in GitHub Desktop.
A plain HTML5 video player styled to taste, and driven by React.js
<!doctype html>
<html>
<head>
<title>mp4</title>
<style>
.emVid {
position: relative;
bottom: 0;
left: 0;
background: #000;
width: 100%;
}
.emVid:fullscreen {
height: 100%;
}
.emVid video {
width: 100%;
}
.emVid--progress {
background: #333;
height: 2px;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
overflow: hidden;
}
.emVid--progress-done {
background: #ffcc00;
height: 2px;
width: 100%;
transition: width 0.05s;
}
.emVid--ctr {
background: rgba(0, 0, 0, 0.5);
height: 50px;
width: 100%;
position: absolute;
bottom: 2px;
left: 0;
overflow: hidden;
}
.emVid--ctr--btn {
color: #fff;
border: none;
background: transparent;
height: 100%;
padding: 0 20px;
margin: 0;
cursor: pointer;
}
.emVid--ctr--btn:disabled {
color: #555;
}
.emVid--ctr--btn__left {
float: left;
margin-left: 20px;
}
.emVid--ctr--btn__right {
float: right;
margin-right: 0;
}
.emVid--ctr--btn__right-most {
margin-right: 20px;
}
.emVid--ctr--btn__muted {
color: #c00;
}
.emVid--ctr--timer {
color: #fff;
padding: 0;
margin: 1.2em 0 0;
font-size: 1em;
display: inline-block;
line-height: 100%;
}
.emVid--ctr--timer__muted {
color: #555;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js"></script>
</head>
<body>
<div class="container" id="container"></div>
<script>
var el = React.createElement;
function isMobile(ua) {
ua = ua || navigator.userAgent;
if (typeof ua !== 'string') {
return false;
}
var match = ua.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i) || '';
return match.length > 0;
}
var Prog = React.createClass({
getDefaultProps: function () {
return {
percent: 0
};
},
render: function () {
return (
el('div', { className: 'emVid--progress' }, (
el('div', {
className: 'emVid--progress-done',
style: { width: this.props.percent * 100 + '%' }
})
))
);
}
});
var Ctr = React.createClass({
getDefaultProps: function () {
return {
onPlay: function () { },
onPause: function () { },
onVolumeChange: function () { },
onFullScreen: function () { },
isPlaying: false,
loading: true,
volume: 0.5,
position: 0,
duration: 0
};
},
getInitialState: function(){
return {
muted: false,
fullScreen: false
};
},
componentWillReceiveProps: function(oldProps){
if (oldProps.volume <= 0 && !this.state.muted) {
this.setState({
muted: true
});
}
},
changeVolume: function(val){
var pr = this.props;
var newVol = pr.volume + val;
this.setState({
muted: newVol <= 0
});
pr.onVolumeChange(val);
},
toggleMute: function() {
var muted = this.state.muted;
var pr = this.props;
if (muted) {
pr.onVolumeChange(0.5);
} else {
pr.onVolumeChange(-1);
}
this.setState({
muted: !muted
});
},
toggleFullScreen: function(){
var st = this.state;
this.props.onFullScreen(st.fullScreen);
this.setState({
fullScreen: !st.fullScreen
});
},
_formatTime: function(seconds) {
var sec = parseInt(seconds, 10);
var hours = Math.floor(sec / 3600);
var min = Math.floor((sec - (hours * 3600)) / 60);
var seconds = sec - (hours * 3600) - (min * 60);
return min + ':' + (seconds < 10 ? '0' + seconds : seconds);
},
render: function () {
var pr = this.props;
var st = this.state;
var hiddenOnMobile = isMobile() ? { display: 'none' } : { };
var duration = this._formatTime(pr.duration);
var position = this._formatTime(pr.position);
var currentTime = position + ' / ' + duration;
if (pr.loading){
currentTime = '-- : --';
}
var playPauseBtn = pr.isPlaying ?
el('button', {
tabIndex: 1,
className: 'emVid--ctr--btn emVid--ctr--btn__left fa fa-pause',
key: 'pause',
disabled: pr.loading,
onClick: pr.onPause
}) :
el('button', {
tabIndex: 1,
className: 'emVid--ctr--btn emVid--ctr--btn__left fa fa-play',
key: 'play',
disabled: pr.loading,
onClick: pr.onPlay
});
return (
el('div', { className: 'emVid--ctr' }, [
playPauseBtn,
el('span', { key: 'currTime', className: 'emVid--ctr--timer ' + (pr.loading ? 'emVid--ctr--timer__muted' : '') }, currentTime),
el('button', {
tabIndex: 13,
className: 'emVid--ctr--btn emVid--ctr--btn__right emVid--ctr--btn__right-most fa fa-' + (st.fullScreen ? 'compress' : 'expand'),
key: 'full',
onClick: this.toggleFullScreen,
disabled: pr.loading
}),
el('button', {
tabIndex: 12,
style: hiddenOnMobile,
className: 'emVid--ctr--btn emVid--ctr--btn__right fa fa-volume-up',
key: 'volUp',
onClick: this.changeVolume.bind(null, 0.1),
disabled: pr.loading || pr.volume >= 1
}),
el('button', {
tabIndex: 11,
style: hiddenOnMobile,
className: 'emVid--ctr--btn emVid--ctr--btn__right fa fa-volume-down',
key: 'volDown',
onClick: this.changeVolume.bind(null, -0.1),
disabled: pr.loading || pr.volume <= 0
}),
el('button', {
tabIndex: 10,
style: hiddenOnMobile,
className: 'emVid--ctr--btn emVid--ctr--btn__right fa fa-volume-off ' + (st.muted ? 'emVid--ctr--btn__muted' : ''),
key: 'mute',
onClick: this.toggleMute,
disabled: pr.loading
})
])
);
}
});
var Vid = React.createClass({
getDefaultProps: function () {
return {
src: '',
onEnded: function () { }
};
},
getInitialState: function () {
return {
duration: 0,
position: 0,
volume: 0,
isPlaying: false,
isLoading: true,
_timer: 0
};
},
componentDidMount: function () {
var vidEl = this.refs.video;
if (vidEl !== null) {
vidEl.addEventListener('loadeddata', this._vidLoadedData);
vidEl.addEventListener('ended', this._vidEnded);
}
},
componentWillUnmount: function(){
var vidEl = this.refs.video;
if (vidEl !== null) {
vidEl.removeEventListener('loadeddata', this._vidLoadedData);
vidEl.removeEventListener('ended', this._vidEnded);
}
},
_vidLoadedData: function () {
var vidEl = this.refs.video;
if (vidEl !== null) {
this.setState({
isLoading: false,
duration: vidEl.duration,
volume: Number(localStorage.getItem('emVolume') || this.state.volume)
});
}
},
_vidEnded: function () {
this.setState({
isLoading: true,
isPlaying: false
});
this.props.onEnded();
},
updateProgress: function () {
var state = this.state;
if (state.isPlaying) {
this.setState({
position: this.refs.video.currentTime,
_timer: setTimeout(this.updateProgress, 10)
});
}
},
updateVolume: function (add) {
var vol = this.state.volume + add;
if (vol < 0) {
vol = 0;
} else if (vol > 1) {
vol = 1;
}
this.refs.video.volume = vol;
localStorage.setItem('emVolume', vol);
this.setState({
volume: vol
})
},
onPlay: function () {
var vidEl = this.refs.video;
vidEl.play(true);
clearTimeout(this.state._timer);
setTimeout(this.updateProgress, 0);
this.updateVolume(0);
this.setState({
isPlaying: true
});
},
onPause: function () {
var vidEl = this.refs.video;
vidEl.pause(true);
this.setState({
isPlaying: false
});
},
onFullScreen: function(exit){
var elem = this.refs.videoContainer;
var fs = function(){ };
if (exit) {
fs = elem.exitFullscreen ||
elem.mozCancelFullScreen ||
elem.webkitExitFullscreen ||
elem.msExitFullscreen;
if (!fs) {
document.webkitCancelFullScreen();
return;
}
} else {
fs = elem.requestFullscreen ||
elem.msRequestFullscreen ||
elem.mozRequestFullScreen ||
elem.webkitRequestFullscreen;
}
fs.call(elem);
},
render: function () {
var pr = this.props;
var st = this.state;
return (
el('div', { className: 'emVid', ref: 'videoContainer' }, [
el('video', { key: 'video', src: pr.src, ref: 'video' }),
el(Ctr, {
key: 'controls',
isPlaying: st.isPlaying,
onPlay: this.onPlay,
onPause: this.onPause,
onVolumeChange: this.updateVolume,
onFullScreen: this.onFullScreen,
volume: st.volume,
loading: st.isLoading,
duration: st.duration,
position: st.position
}),
el(Prog, { key: 'progress', percent: st.isLoading ? 0 : st.position / st.duration })
])
);
}
});
ReactDOM.render(React.createElement(Vid, { src: 'vid.mp4' }), document.getElementById('container'));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment