mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 07:32:02 +00:00
MPVVideo basic implementation
This commit is contained in:
parent
0055aad30c
commit
b08ee9aa8b
7 changed files with 482 additions and 18 deletions
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { StrictMode } from 'react';
|
const React = require('react');
|
||||||
import { Router } from 'stremio-common';
|
const { Router } = require('stremio-common');
|
||||||
import routerConfig from './routerConfig';
|
const routerConfig = require('./routerConfig').default;
|
||||||
import styles from './styles';
|
const styles = require('./styles');
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<StrictMode>
|
<React.StrictMode>
|
||||||
<Router className={styles['router']} config={routerConfig} />
|
<Router className={styles['router']} config={routerConfig} />
|
||||||
</StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
module.exports = App;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
import App from './App';
|
const App = require('./App');
|
||||||
|
|
||||||
export default App;
|
module.exports = App;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="text/javascript" src="/stremio-web.js"></script>
|
<script type="text/javascript" src="/stremio-web.js"></script>
|
||||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||||
<script type="text/javascript" src="qrc:///stremio-shell.js" onerror="window.runApp();"></script>
|
<script type="text/javascript" src="qrc:///stremio-shell.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
27
src/index.js
27
src/index.js
|
|
@ -1,7 +1,26 @@
|
||||||
import React from 'react';
|
const React = require('react');
|
||||||
import ReactDOM from 'react-dom';
|
const ReactDOM = require('react-dom');
|
||||||
import App from './App';
|
const App = require('./App');
|
||||||
|
|
||||||
window.runApp = () => {
|
const renderApp = () => {
|
||||||
ReactDOM.render(<App />, document.getElementById('app'));
|
ReactDOM.render(<App />, document.getElementById('app'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (window.qt) {
|
||||||
|
window.shellOnLoad = () => {
|
||||||
|
window.shell.dispatch('mpv', 'setOption', null, 'terminal', 'yes');
|
||||||
|
window.shell.dispatch('mpv', 'setOption', null, 'msg-level', 'all=v');
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'vo', 'opengl-cb');
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'opengl-hwdec-interop', 'auto');
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'cache-default', 15000);
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'cache-backbuffer', 15000);
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'cache-secs', 10);
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'audio-client-name', 'Stremio');
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'title', 'Stremio');
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'audio-fallback-to-null', 'yes');
|
||||||
|
window.shell.dispatch('mpv', 'setProp', null, 'sid', 'no');
|
||||||
|
renderApp();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
renderApp();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ class Player extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.dispatch('command', 'load', this.props.stream, {});
|
this.dispatch('command', 'load', this.props.stream, {
|
||||||
|
ipc: window.shell
|
||||||
|
});
|
||||||
this.dispatch('setProp', 'subtitleOffset', 18);
|
this.dispatch('setProp', 'subtitleOffset', 18);
|
||||||
this.dispatch('command', 'addSubtitleTracks', [{
|
this.dispatch('command', 'addSubtitleTracks', [{
|
||||||
url: 'https://raw.githubusercontent.com/caitp/ng-media/master/example/assets/captions/bunny-en.vtt',
|
url: 'https://raw.githubusercontent.com/caitp/ng-media/master/example/assets/captions/bunny-en.vtt',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import hat from 'hat';
|
import hat from 'hat';
|
||||||
import HTMLVideo from './stremio-video/HTMLVideo';
|
import HTMLVideo from './stremio-video/HTMLVideo';
|
||||||
import YouTubeVideo from './stremio-video/YouTubeVideo';
|
import YouTubeVideo from './stremio-video/YouTubeVideo';
|
||||||
|
import MPVVideo from './stremio-video/MPVVideo';
|
||||||
|
|
||||||
class Video extends Component {
|
class Video extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|
@ -22,7 +23,9 @@ class Video extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
selectVideoImplementation = (stream, options) => {
|
selectVideoImplementation = (stream, options) => {
|
||||||
if (stream.ytId) {
|
if (options.ipc) {
|
||||||
|
return MPVVideo;
|
||||||
|
} else if (stream.ytId) {
|
||||||
return YouTubeVideo;
|
return YouTubeVideo;
|
||||||
} else {
|
} else {
|
||||||
return HTMLVideo;
|
return HTMLVideo;
|
||||||
|
|
@ -34,7 +37,11 @@ class Video extends Component {
|
||||||
const Video = this.selectVideoImplementation(args[2], args[3]);
|
const Video = this.selectVideoImplementation(args[2], args[3]);
|
||||||
if (this.video === null || this.video.constructor !== Video) {
|
if (this.video === null || this.video.constructor !== Video) {
|
||||||
this.dispatch('command', 'destroy');
|
this.dispatch('command', 'destroy');
|
||||||
this.video = new Video({ containerElement: this.containerRef.current });
|
this.video = new Video({
|
||||||
|
...args[3],
|
||||||
|
id: this.id,
|
||||||
|
containerElement: this.containerRef.current
|
||||||
|
});
|
||||||
this.video.on('ended', this.props.onEnded);
|
this.video.on('ended', this.props.onEnded);
|
||||||
this.video.on('error', this.props.onError);
|
this.video.on('error', this.props.onError);
|
||||||
this.video.on('propValue', this.props.onPropValue);
|
this.video.on('propValue', this.props.onPropValue);
|
||||||
|
|
@ -54,7 +61,7 @@ class Video extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div ref={this.containerRef} id={this.id} className={this.props.className} />
|
<div ref={this.containerRef} className={this.props.className} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
436
src/routes/Player/Video/stremio-video/MPVVideo.js
Normal file
436
src/routes/Player/Video/stremio-video/MPVVideo.js
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
var EventEmitter = require('events');
|
||||||
|
|
||||||
|
var MPV_CRITICAL_ERROR_CODES = [];
|
||||||
|
|
||||||
|
function MPVVideo(options) {
|
||||||
|
var ipc = options && options.ipc;
|
||||||
|
var id = options && options.id;
|
||||||
|
if (!ipc) {
|
||||||
|
throw new Error('ipc parameter is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof id !== 'string') {
|
||||||
|
throw new Error('id parameter is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
var ready = false;
|
||||||
|
var loaded = false;
|
||||||
|
var destroyed = false;
|
||||||
|
var events = new EventEmitter();
|
||||||
|
var observedProps = {};
|
||||||
|
var dispatchArgsReadyQueue = [];
|
||||||
|
|
||||||
|
events.on('error', function() { });
|
||||||
|
ipc.dispatch('mpv', 'createChannel', id)
|
||||||
|
.then(function() {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ready = true;
|
||||||
|
Promise.all([getProp('volume'), getProp('mute')]).then(function(values) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPropChanged('volume', values[0]);
|
||||||
|
onPropChanged('mute', values[1]);
|
||||||
|
});
|
||||||
|
ipc.on('mpvEvent', onMpvEvent);
|
||||||
|
flushDispatchArgsQueue(dispatchArgsReadyQueue);
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
onChannelError(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
function mapPausedValue(pause) {
|
||||||
|
if (!loaded || typeof pause !== 'boolean') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pause;
|
||||||
|
}
|
||||||
|
function mapTimeValue(timePos) {
|
||||||
|
if (!loaded || isNaN(timePos) || timePos === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(timePos * 1000);
|
||||||
|
}
|
||||||
|
function mapDurationValue(duration) {
|
||||||
|
if (!loaded || isNaN(duration) || duration === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(duration * 1000);
|
||||||
|
}
|
||||||
|
function mapBufferingValue(seeking, pausedForCache) {
|
||||||
|
if (!loaded || (seeking === null && pausedForCache === null)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!seeking || !!pausedForCache;
|
||||||
|
}
|
||||||
|
function mapVolumeValue(volume) {
|
||||||
|
if (!ready || destroyed || isNaN(volume) || volume === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
function mapMutedValue(mute) {
|
||||||
|
if (!ready || destroyed || typeof mute !== 'boolean') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mute;
|
||||||
|
}
|
||||||
|
function onError(error) {
|
||||||
|
if (destroyed || !error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.freeze(error);
|
||||||
|
events.emit('error', error);
|
||||||
|
if (error.critical) {
|
||||||
|
dispatch('command', 'stop');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onEnded() {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.emit('ended');
|
||||||
|
}
|
||||||
|
function onPropChanged(propName, propValue) {
|
||||||
|
switch (propName) {
|
||||||
|
case 'pause': {
|
||||||
|
if (observedProps['paused']) {
|
||||||
|
events.emit('propChanged', 'paused', mapPausedValue(propValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'time-pos': {
|
||||||
|
if (observedProps['time']) {
|
||||||
|
events.emit('propChanged', 'time', mapTimeValue(propValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'duration': {
|
||||||
|
if (observedProps['duration']) {
|
||||||
|
events.emit('propChanged', 'duration', mapDurationValue(propValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'seeking': {
|
||||||
|
if (observedProps['buffering']) {
|
||||||
|
events.emit('propChanged', 'buffering', mapBufferingValue(propValue, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'paused-for-cache': {
|
||||||
|
if (observedProps['buffering']) {
|
||||||
|
events.emit('propChanged', 'buffering', mapBufferingValue(null, propValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'volume': {
|
||||||
|
if (observedProps['volume']) {
|
||||||
|
events.emit('propChanged', 'volume', mapVolumeValue(propValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'mute': {
|
||||||
|
if (observedProps['muted']) {
|
||||||
|
events.emit('propChanged', 'muted', mapMutedValue(propValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onMpvEvent(data) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.channelId !== id) {
|
||||||
|
onChannelError(ipc.errors.mpv_channel_id_expired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.eventName) {
|
||||||
|
case 'error': {
|
||||||
|
onError(Object.assign({}, data, {
|
||||||
|
critical: MPV_CRITICAL_ERROR_CODES.indexOf(data.code) !== -1
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ended': {
|
||||||
|
onEnded();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'propChanged': {
|
||||||
|
onPropChanged(data.propName, data.propValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onChannelError(error) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(Object.assign({}, error, {
|
||||||
|
critical: true
|
||||||
|
}));
|
||||||
|
dispatch('command', 'destroy');
|
||||||
|
}
|
||||||
|
function observeProp(propName) {
|
||||||
|
ipc.dispatch('mpv', 'observeProp', id, propName)
|
||||||
|
.catch(function(error) {
|
||||||
|
onChannelError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getProp(propName) {
|
||||||
|
return ipc.dispatch('mpv', 'getProp', id, propName)
|
||||||
|
.catch(function(error) {
|
||||||
|
onChannelError(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function setProp(propName) {
|
||||||
|
ipc.dispatch('mpv', 'setProp', id, propName)
|
||||||
|
.catch(function(error) {
|
||||||
|
onChannelError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function command() {
|
||||||
|
ipc.dispatch.apply(null, ['mpv', 'command', id].concat(Array.from(arguments)))
|
||||||
|
.catch(function(error) {
|
||||||
|
onChannelError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function flushDispatchArgsQueue(dispatchArgsQueue) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (dispatchArgsQueue.length > 0) {
|
||||||
|
var args = dispatchArgsQueue.shift();
|
||||||
|
dispatch.apply(null, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function on(eventName, listener) {
|
||||||
|
if (destroyed) {
|
||||||
|
throw new Error('Video is destroyed');
|
||||||
|
}
|
||||||
|
|
||||||
|
events.on(eventName, listener);
|
||||||
|
}
|
||||||
|
function dispatch() {
|
||||||
|
if (destroyed) {
|
||||||
|
throw new Error('Video is destroyed');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (arguments[0]) {
|
||||||
|
case 'observeProp': {
|
||||||
|
switch (arguments[1]) {
|
||||||
|
case 'paused': {
|
||||||
|
observeProp('pause');
|
||||||
|
getProp('pause').then(function(pause) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedProps['paused'] = true;
|
||||||
|
events.emit('propValue', 'paused', mapPausedValue(pause));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'time': {
|
||||||
|
observeProp('time-pos');
|
||||||
|
getProp('time-pos').then(function(timePos) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedProps['time'] = true;
|
||||||
|
events.emit('propValue', 'time', mapTimeValue(timePos));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'duration': {
|
||||||
|
observeProp('duration');
|
||||||
|
getProp('duration').then(function(duration) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedProps['duration'] = true;
|
||||||
|
events.emit('propValue', 'duration', mapDurationValue(duration));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'buffering': {
|
||||||
|
observeProp('seeking');
|
||||||
|
observeProp('paused-for-cache');
|
||||||
|
Promise.all([getProp('seeking'), getProp('paused-for-cache')]).then(function(values) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedProps['buffering'] = true;
|
||||||
|
events.emit('propValue', 'buffering', mapBufferingValue(values[0], values[1]));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'volume': {
|
||||||
|
observeProp('volume');
|
||||||
|
getProp('volume').then(function(volume) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedProps['volume'] = true;
|
||||||
|
events.emit('propValue', 'volume', mapVolumeValue(volume));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'muted': {
|
||||||
|
observeProp('mute');
|
||||||
|
getProp('mute').then(function(mute) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedProps['muted'] = true;
|
||||||
|
events.emit('propValue', 'muted', mapMutedValue(mute));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'setProp': {
|
||||||
|
switch (arguments[1]) {
|
||||||
|
case 'paused': {
|
||||||
|
if (loaded) {
|
||||||
|
setProp('pause', !!arguments[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'time': {
|
||||||
|
if (loaded && !isNaN(arguments[2]) && arguments[2] !== null) {
|
||||||
|
setProp('time-pos', arguments[2] / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'volume': {
|
||||||
|
if (ready) {
|
||||||
|
if (!isNaN(arguments[2]) && arguments[2] !== null) {
|
||||||
|
setProp('mute', false);
|
||||||
|
setProp('volume', Math.max(0, Math.min(100, arguments[2])));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatchArgsReadyQueue.push(Array.from(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'muted': {
|
||||||
|
if (ready) {
|
||||||
|
setProp('mute', !!arguments[2]);
|
||||||
|
} else {
|
||||||
|
dispatchArgsReadyQueue.push(Array.from(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'command': {
|
||||||
|
switch (arguments[1]) {
|
||||||
|
case 'stop': {
|
||||||
|
loaded = false;
|
||||||
|
if (ready) {
|
||||||
|
command('stop');
|
||||||
|
}
|
||||||
|
onPropChanged('pause', null);
|
||||||
|
onPropChanged('time-pos', null);
|
||||||
|
onPropChanged('duration', null);
|
||||||
|
onPropChanged('seeking', null);
|
||||||
|
onPropChanged('paused-for-cache', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'load': {
|
||||||
|
if (ready) {
|
||||||
|
dispatch('command', 'stop');
|
||||||
|
loaded = true;
|
||||||
|
var startTime = !isNaN(arguments[3].time) && arguments[3].time !== null ? Math.round(arguments[3].time / 1000) : 0;
|
||||||
|
command('loadfile', arguments[2].url, 'replace', 'time-pos=' + startTime);
|
||||||
|
setProp('pause', arguments[3].autoplay === false);
|
||||||
|
Promise.all([
|
||||||
|
getProp('pause'),
|
||||||
|
getProp('time-pos'),
|
||||||
|
getProp('duration'),
|
||||||
|
getProp('seeking'),
|
||||||
|
getProp('paused-for-cache')
|
||||||
|
]).then(function(values) {
|
||||||
|
if (destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPropChanged('pause', values[0]);
|
||||||
|
onPropChanged('time-pos', values[1]);
|
||||||
|
onPropChanged('duration', values[2]);
|
||||||
|
onPropChanged('seeking', values[3]);
|
||||||
|
onPropChanged('paused-for-cache', values[4]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatchArgsReadyQueue.push(Array.from(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'destroy': {
|
||||||
|
dispatch('command', 'stop');
|
||||||
|
destroyed = true;
|
||||||
|
onPropChanged('volume', null);
|
||||||
|
onPropChanged('mute', null);
|
||||||
|
events.removeAllListeners();
|
||||||
|
events.on('error', function() { });
|
||||||
|
ipc.off('mpvEvent', onMpvEvent);
|
||||||
|
dispatchArgsReadyQueue = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Invalid dispatch call: ' + Array.from(arguments).map(String));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on = on;
|
||||||
|
this.dispatch = dispatch;
|
||||||
|
|
||||||
|
Object.freeze(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPVVideo.manifest = Object.freeze({
|
||||||
|
name: 'MPVVideo',
|
||||||
|
embedded: true,
|
||||||
|
props: Object.freeze(['paused', 'time', 'duration', 'volume', 'muted', 'buffering'])
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.freeze(MPVVideo);
|
||||||
|
|
||||||
|
module.exports = MPVVideo;
|
||||||
Loading…
Reference in a new issue