video directory removed

This commit is contained in:
nklhrstv 2020-04-30 11:39:17 +03:00
parent 146a80d310
commit 5bf74786c6
12 changed files with 1 additions and 2232 deletions

View file

@ -1,314 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var EventEmitter = require('events');
var subtitlesParser = require('./subtitlesParser');
var subtitlesRenderer = require('./subtitlesRenderer');
var colorConverter = require('./colorConverter');
var COLOR_REGEX = /^#[A-Fa-f0-9]{8}$/;
var ERROR_CODE = Object.freeze({
FETCH_FAILED: 70,
PARSE_FAILED: 71
});
var SIZE_COEF = 25;
function HTMLSubtitles(options) {
var containerElement = options && options.containerElement;
if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) {
throw new Error('Instance of HTMLElement with id attribute required');
}
if (!document.body.contains(containerElement)) {
throw new Error('Container element not attached to body');
}
var destroyed = false;
var events = new EventEmitter();
var cuesByTime = null;
var tracks = Object.freeze([]);
var selectedTrackId = null;
var delay = null;
var stylesElement = document.createElement('style');
var subtitlesElement = document.createElement('div');
events.on('error', function() { });
containerElement.appendChild(stylesElement);
var subtitlesContainerStylesIndex = stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles { position: absolute; right: 0; bottom: 0; left: 0; z-index: 0; text-align: center; }', stylesElement.sheet.cssRules.length);
var subtitlesCueStylesIndex = stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles .cue { display: inline-block; padding: 0.2em; text-shadow: 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff; background-color: #00000000; color: #ffffffff; font-size: 4vmin; }', stylesElement.sheet.cssRules.length);
stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles .cue * { font-size: inherit; }', stylesElement.sheet.cssRules.length);
containerElement.appendChild(subtitlesElement);
subtitlesElement.classList.add('subtitles');
function on(eventName, listener) {
if (destroyed) {
return;
}
events.on(eventName, listener);
}
function addTracks(extraTracks) {
if (destroyed || !Array.isArray(extraTracks)) {
return;
}
tracks = extraTracks
.filter(function(track) {
return track &&
typeof track.url === 'string' &&
track.url.length > 0 &&
typeof track.origin === 'string' &&
track.origin.length > 0 &&
track.origin !== 'EMBEDDED IN VIDEO';
})
.map(function(track) {
return Object.freeze(Object.assign({}, track, {
id: track.url
}));
})
.concat(tracks)
.filter(function(track, index, tracks) {
for (var i = 0; i < tracks.length; i++) {
if (tracks[i].id === track.id) {
return i === index;
}
}
return false;
});
Object.freeze(tracks);
events.emit('propChanged', 'tracks');
}
function updateText(mediaTime) {
while (subtitlesElement.hasChildNodes()) {
subtitlesElement.removeChild(subtitlesElement.lastChild);
}
if (cuesByTime === null || isNaN(mediaTime) || mediaTime === null) {
return;
}
var time = mediaTime + delay;
subtitlesRenderer.render(cuesByTime, time)
.forEach(function(cueNode) {
cueNode.classList.add('cue');
subtitlesElement.append(cueNode, document.createElement('br'));
});
}
function clearTracks() {
updateText(NaN);
cuesByTime = null;
tracks = Object.freeze([]);
selectedTrackId = null;
delay = null;
events.emit('propChanged', 'tracks');
events.emit('propChanged', 'selectedTrackId');
events.emit('propChanged', 'delay');
}
function destroy() {
destroyed = true;
clearTracks();
events.emit('propChanged', 'size');
events.emit('propChanged', 'textColor');
events.emit('propChanged', 'backgroundColor');
events.emit('propChanged', 'outlineColor');
events.emit('propChanged', 'offset');
events.removeAllListeners();
events.on('error', function() { });
containerElement.removeChild(stylesElement);
containerElement.removeChild(subtitlesElement);
}
Object.defineProperties(this, {
tracks: {
configurable: false,
enumerable: true,
get: function() {
return Object.freeze(tracks.slice());
}
},
selectedTrackId: {
configurable: false,
enumerable: true,
get: function() {
return selectedTrackId;
},
set: function(value) {
if (destroyed) {
return;
}
cuesByTime = null;
selectedTrackId = null;
delay = null;
updateText(NaN);
var selecterdTrack = tracks.find(function(track) {
return track.id === value;
});
if (selecterdTrack) {
selectedTrackId = selecterdTrack.id;
delay = 0;
fetch(selecterdTrack.url)
.then(function(resp) {
return resp.text();
})
.catch(function(error) {
events.emit('error', Object.freeze({
code: ERROR_CODE.FETCH_FAILED,
message: 'Failed to fetch subtitles from ' + selecterdTrack.origin,
track: selecterdTrack,
error: error
}));
})
.then(function(text) {
if (typeof text === 'string' && selectedTrackId === selecterdTrack.id) {
cuesByTime = subtitlesParser.parse(text);
if (cuesByTime.times.length === 0) {
throw new Error('parse failed');
}
events.emit('trackLoaded', selecterdTrack);
}
})
.catch(function(error) {
events.emit('error', Object.freeze({
code: ERROR_CODE.PARSE_FAILED,
message: 'Failed to parse subtitles from ' + selecterdTrack.origin,
track: selecterdTrack,
error: error
}));
});
}
events.emit('propChanged', 'selectedTrackId');
events.emit('propChanged', 'delay');
}
},
delay: {
configurable: false,
enumerable: true,
get: function() {
return delay;
},
set: function(value) {
if (destroyed || isNaN(value) || value === null || selectedTrackId === null) {
return;
}
delay = parseInt(value);
updateText(NaN);
events.emit('propChanged', 'delay');
}
},
size: {
configurable: false,
enumerable: true,
get: function() {
if (destroyed) {
return null;
}
return parseInt(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.fontSize) * SIZE_COEF
},
set: function(value) {
if (destroyed || isNaN(value) || value === null) {
return;
}
stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.fontSize = Math.floor(value / SIZE_COEF) + 'vmin';
events.emit('propChanged', 'size');
}
},
offset: {
configurable: false,
enumerable: true,
get: function() {
if (destroyed) {
return null;
}
return parseInt(stylesElement.sheet.cssRules[subtitlesContainerStylesIndex].style.bottom);
},
set: function(value) {
if (destroyed || isNaN(value) || value === null) {
return;
}
stylesElement.sheet.cssRules[subtitlesContainerStylesIndex].style.bottom = Math.max(0, Math.min(100, parseInt(value))) + '%';
events.emit('propChanged', 'offset');
}
},
textColor: {
configurable: false,
enumerable: true,
get: function() {
if (destroyed) {
return null;
}
return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.color);
},
set: function(value) {
if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) {
return;
}
stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.color = value;
events.emit('propChanged', 'textColor');
}
},
backgroundColor: {
configurable: false,
enumerable: true,
get: function() {
if (destroyed) {
return null;
}
return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.backgroundColor);
},
set: function(value) {
if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) {
return;
}
stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.backgroundColor = value;
events.emit('propChanged', 'backgroundColor');
}
},
outlineColor: {
configurable: false,
enumerable: false,
get: function() {
if (destroyed) {
return null;
}
return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.textShadow);
},
set: function(value) {
if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) {
return;
}
stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.textShadow =
value + ' 0 0 0.03em,' +
value + ' 0 0 0.03em,' +
value + ' 0 0 0.03em,' +
value + ' 0 0 0.03em,' +
value + ' 0 0 0.03em';
events.emit('propChanged', 'outlineColor');
}
}
});
this.on = on;
this.addTracks = addTracks;
this.updateText = updateText;
this.clearTracks = clearTracks;
this.destroy = destroy;
Object.freeze(this);
};
Object.freeze(HTMLSubtitles);
module.exports = HTMLSubtitles;

View file

@ -1,460 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var EventEmitter = require('events');
var HTMLSubtitles = require('./HTMLSubtitles');
function HTMLVideo(options) {
var containerElement = options && options.containerElement;
if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) {
throw new Error('Instance of HTMLElement with id attribute required');
}
if (!document.body.contains(containerElement)) {
throw new Error('Container element not attached to body');
}
var destroyed = false;
var loaded = false;
var events = new EventEmitter();
var observedProps = {};
var subtitles = new HTMLSubtitles({ containerElement: containerElement });
var stylesElement = document.createElement('style');
var videoElement = document.createElement('video');
events.on('error', function() { });
subtitles.on('propChanged', onSubtitlesPropChanged);
subtitles.on('trackLoaded', onSubtitlesTrackLoaded);
subtitles.on('error', onSubtitlesError);
containerElement.appendChild(stylesElement);
stylesElement.sheet.insertRule('#' + containerElement.id + ' .video { position: absolute; width: 100%; height: 100%; z-index: -1; background-color: black; }', stylesElement.sheet.cssRules.length);
containerElement.appendChild(videoElement);
videoElement.classList.add('video');
videoElement.crossOrigin = 'anonymous';
videoElement.controls = false;
videoElement.onpause = function() {
onVideoPropChanged('paused');
};
videoElement.onplay = function() {
onVideoPropChanged('paused');
};
videoElement.ontimeupdate = function() {
onVideoPropChanged('currentTime');
};
videoElement.ondurationchange = function() {
onVideoPropChanged('duration');
};
videoElement.onwaiting = function() {
onVideoPropChanged('readyState');
};
videoElement.onplaying = function() {
onVideoPropChanged('readyState');
};
videoElement.onloadeddata = function() {
onVideoPropChanged('readyState');
};
videoElement.onvolumechange = function() {
onVideoPropChanged('volume');
onVideoPropChanged('muted');
};
videoElement.onended = function() {
onVideoEnded();
};
videoElement.onerror = function() {
onVideoError();
};
function onSubtitlesPropChanged(propName) {
switch (propName) {
case 'tracks': {
if (observedProps['subtitlesTracks']) {
events.emit('propChanged', 'subtitlesTracks', getProp('subtitlesTracks'));
}
break;
}
case 'selectedTrackId': {
if (observedProps['selectedSubtitlesTrackId']) {
events.emit('propChanged', 'selectedSubtitlesTrackId', getProp('selectedSubtitlesTrackId'));
}
break;
}
case 'delay': {
subtitles.updateText(getProp('time'));
if (observedProps['subtitlesDelay']) {
events.emit('propChanged', 'subtitlesDelay', getProp('subtitlesDelay'));
}
break;
}
case 'size': {
if (observedProps['subtitlesSize']) {
events.emit('propChanged', 'subtitlesSize', getProp('subtitlesSize'));
}
break;
}
case 'offset': {
if (observedProps['subtitlesOffset']) {
events.emit('propChanged', 'subtitlesOffset', getProp('subtitlesOffset'));
}
break;
}
case 'textColor': {
if (observedProps['subtitlesTextColor']) {
events.emit('propChanged', 'subtitlesTextColor', getProp('subtitlesTextColor'));
}
break;
}
case 'backgroundColor': {
if (observedProps['subtitlesBackgroundColor']) {
events.emit('propChanged', 'subtitlesBackgroundColor', getProp('subtitlesBackgroundColor'));
}
break;
}
case 'outlineColor': {
if (observedProps['subtitlesOutlineColor']) {
events.emit('propChanged', 'subtitlesOutlineColor', getProp('subtitlesOutlineColor'));
}
break;
}
}
}
function onSubtitlesTrackLoaded(track) {
subtitles.updateText(getProp('time'));
events.emit('subtitlesTrackLoaded', track);
}
function onSubtitlesError(error) {
onError(Object.assign({}, error, {
critical: false
}));
}
function onVideoPropChanged(propName) {
switch (propName) {
case 'paused': {
if (observedProps['paused']) {
events.emit('propChanged', 'paused', getProp('paused'));
}
break;
}
case 'currentTime': {
subtitles.updateText(getProp('time'));
if (observedProps['time']) {
events.emit('propChanged', 'time', getProp('time'));
}
break;
}
case 'duration': {
if (observedProps['duration']) {
events.emit('propChanged', 'duration', getProp('duration'));
}
break;
}
case 'readyState': {
if (observedProps['buffering']) {
events.emit('propChanged', 'buffering', getProp('buffering'));
}
break;
}
case 'volume': {
if (observedProps['volume']) {
events.emit('propChanged', 'volume', getProp('volume'));
}
break;
}
case 'muted': {
if (observedProps['muted']) {
events.emit('propChanged', 'muted', getProp('muted'));
}
break;
}
}
}
function onVideoEnded() {
events.emit('ended');
}
function onVideoError() {
onError({
code: videoElement.error.code,
message: videoElement.error.message,
critical: true
});
}
function onError(error) {
if (!error) {
return;
}
Object.freeze(error);
events.emit('error', error);
if (error.critical) {
command('stop');
}
}
function getProp(propName) {
switch (propName) {
case 'paused': {
if (!loaded) {
return null;
}
return !!videoElement.paused;
}
case 'time': {
if (!loaded || isNaN(videoElement.currentTime) || videoElement.currentTime === null) {
return null;
}
return Math.floor(videoElement.currentTime * 1000);
}
case 'duration': {
if (!loaded || isNaN(videoElement.duration) || videoElement.duration === null) {
return null;
}
return Math.floor(videoElement.duration * 1000);
}
case 'buffering': {
if (!loaded) {
return null;
}
return videoElement.readyState < videoElement.HAVE_FUTURE_DATA;
}
case 'volume': {
if (destroyed || isNaN(videoElement.volume) || videoElement.volume === null) {
return null;
}
return Math.floor(videoElement.volume * 100);
}
case 'muted': {
if (destroyed) {
return null;
}
return !!videoElement.muted;
}
case 'subtitlesTracks': {
return subtitles.tracks;
}
case 'selectedSubtitlesTrackId': {
return subtitles.selectedTrackId;
}
case 'subtitlesDelay': {
return subtitles.delay;
}
case 'subtitlesSize': {
return subtitles.size;
}
case 'subtitlesOffset': {
return subtitles.offset;
}
case 'subtitlesTextColor': {
return subtitles.textColor;
}
case 'subtitlesBackgroundColor': {
return subtitles.backgroundColor;
}
case 'subtitlesOutlineColor': {
return subtitles.outlineColor;
}
default: {
throw new Error('getProp not supported: ' + propName);
}
}
}
function observeProp(propName) {
if (HTMLVideo.manifest.props.indexOf(propName) === -1) {
throw new Error('observeProp not supported: ' + propName);
}
events.emit('propValue', propName, getProp(propName));
observedProps[propName] = true;
}
function setProp(propName, propValue) {
switch (propName) {
case 'paused': {
if (loaded) {
if (!!propValue) {
videoElement.pause();
} else {
videoElement.play();
}
}
break;
}
case 'time': {
if (loaded && !isNaN(propValue) && propValue !== null) {
videoElement.currentTime = parseInt(propValue) / 1000;
}
break;
}
case 'volume': {
if (!isNaN(propValue) && propValue !== null) {
videoElement.muted = false;
videoElement.volume = Math.max(0, Math.min(100, parseInt(propValue))) / 100;
}
break;
}
case 'muted': {
videoElement.muted = !!propValue;
break;
}
case 'selectedSubtitlesTrackId': {
if (loaded) {
subtitles.selectedTrackId = propValue;
}
break;
}
case 'subtitlesDelay': {
if (loaded) {
subtitles.delay = propValue;
}
break;
}
case 'subtitlesSize': {
subtitles.size = propValue;
break;
}
case 'subtitlesOffset': {
subtitles.offset = propValue;
break;
}
case 'subtitlesTextColor': {
subtitles.textColor = propValue;
break;
}
case 'subtitlesBackgroundColor': {
subtitles.backgroundColor = propValue;
break;
}
case 'subtitlesOutlineColor': {
subtitles.outlineColor = propValue;
break;
}
default: {
throw new Error('setProp not supported: ' + propName);
}
}
}
function command(commandName, commandArgs) {
switch (commandName) {
case 'addSubtitlesTracks': {
if (loaded && commandArgs) {
subtitles.addTracks(commandArgs.tracks);
}
break;
}
case 'stop': {
loaded = false;
videoElement.removeAttribute('src');
videoElement.load();
videoElement.currentTime = 0;
onVideoPropChanged('paused');
onVideoPropChanged('currentTime');
onVideoPropChanged('duration');
onVideoPropChanged('readyState');
subtitles.clearTracks();
break;
}
case 'load': {
if (commandArgs && commandArgs.stream && typeof commandArgs.stream.url === 'string') {
command('stop');
videoElement.autoplay = typeof commandArgs.autoplay === 'boolean' ? commandArgs.autoplay : true;
videoElement.currentTime = !isNaN(commandArgs.time) && commandArgs.time !== null ? parseInt(commandArgs.time) / 1000 : 0;
videoElement.src = commandArgs.stream.url;
loaded = true;
onVideoPropChanged('paused');
onVideoPropChanged('currentTime');
onVideoPropChanged('duration');
onVideoPropChanged('readyState');
}
break;
}
case 'destroy': {
command('stop');
destroyed = true;
onVideoPropChanged('volume');
onVideoPropChanged('muted');
subtitles.destroy();
events.removeAllListeners();
events.on('error', function() { });
videoElement.onpause = null;
videoElement.onplay = null;
videoElement.ontimeupdate = null;
videoElement.ondurationchange = null;
videoElement.onwaiting = null;
videoElement.onplaying = null;
videoElement.onloadeddata = null;
videoElement.onvolumechange = null;
videoElement.onended = null;
videoElement.onerror = null;
containerElement.removeChild(videoElement);
containerElement.removeChild(stylesElement);
break;
}
default: {
throw new Error('command not supported: ' + commandName);
}
}
}
function on(eventName, listener) {
if (destroyed) {
throw new Error('Video is destroyed');
}
events.on(eventName, listener);
}
function dispatch(args) {
if (destroyed) {
throw new Error('Video is destroyed');
}
if (args) {
if (typeof args.commandName === 'string') {
command(args.commandName, args.commandArgs);
return;
} else if (typeof args.propName === 'string') {
setProp(args.propName, args.propValue);
return;
} else if (typeof args.observedPropName === 'string') {
observeProp(args.observedPropName);
return;
}
}
throw new Error('Invalid dispatch call: ' + JSON.stringify(args));
}
this.on = on;
this.dispatch = dispatch;
Object.freeze(this);
};
HTMLVideo.manifest = Object.freeze({
name: 'HTMLVideo',
embedded: true,
props: Object.freeze(['paused', 'time', 'duration', 'buffering', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesSize', 'subtitlesDelay', 'subtitlesOffset', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor'])
});
Object.freeze(HTMLVideo);
module.exports = HTMLVideo;

View file

@ -1,438 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
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, propValue) {
ipc.dispatch('mpv', 'setProp', id, propName, propValue)
.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;

View file

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

View file

@ -1,685 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var EventEmitter = require('events');
var HTMLSubtitles = require('./HTMLSubtitles');
function YouTubeVideo(options) {
var containerElement = options && options.containerElement;
if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) {
throw new Error('Instance of HTMLElement with id attribute required');
}
var self = this;
var ready = false;
var loaded = false;
var destroyed = false;
var events = new EventEmitter();
var dispatchArgsReadyQueue = [];
var dispatchArgsLoadedQueue = [];
var pausedObserved = false;
var timeObserved = false;
var durationObserved = false;
var bufferingObserved = false;
var volumeObserved = false;
var propChangedIntervalId = window.setInterval(onPropChangedInterval, 100);
var embeddedSubtitlesSelectedTrackId = null;
var subtitles = new HTMLSubtitles(containerElement);
var video = null;
var scriptElement = document.createElement('script');
var stylesElement = document.createElement('style');
var videoContainer = document.createElement('div');
events.on('error', function() { });
subtitles.on('error', onSubtitlesError);
subtitles.on('load', updateSubtitleText);
scriptElement.type = 'text/javascript';
scriptElement.src = 'https://www.youtube.com/iframe_api';
scriptElement.onload = onYouTubePlayerApiLoaded;
scriptElement.onerror = onYouTubePlayerApiError;
containerElement.appendChild(scriptElement);
containerElement.appendChild(stylesElement);
stylesElement.sheet.insertRule('#' + containerElement.id + ' .video { position: absolute; width: 100%; height: 100%; z-index: -1; }', stylesElement.sheet.cssRules.length);
containerElement.appendChild(videoContainer);
videoContainer.classList.add('video');
function getPaused() {
if (!loaded) {
return null;
}
return video.getPlayerState() !== YT.PlayerState.PLAYING;
}
function getTime() {
if (!loaded || isNaN(video.getCurrentTime()) || video.getCurrentTime() === null) {
return null;
}
return Math.floor(video.getCurrentTime() * 1000);
}
function getDuration() {
if (!loaded || isNaN(video.getDuration()) || video.getDuration() === null) {
return null;
}
return Math.floor(video.getDuration() * 1000);
}
function getBuffering() {
if (!loaded) {
return null;
}
return video.getPlayerState() === YT.PlayerState.BUFFERING;
}
function getVolume() {
if (!ready || destroyed || isNaN(video.getVolume()) || video.getVolume() === null) {
return null;
}
return video.isMuted() ? 0 : video.getVolume();
}
function getSubtitlesTracks() {
if (!loaded) {
return Object.freeze([]);
}
var embeddedTracks = (video.getOption('captions', 'tracklist') || [])
.map(function(track) {
return Object.freeze({
id: track.languageCode,
origin: 'EMBEDDED IN VIDEO',
label: track.languageName
});
});
var extraTracks = subtitles.dispatch('getProp', 'tracks');
var allTracks = embeddedTracks.concat(extraTracks)
.filter(function(track, index, tracks) {
for (var i = 0; i < tracks.length; i++) {
if (tracks[i].id === track.id) {
return i === index;
}
}
return false;
});
return Object.freeze(allTracks);
}
function getSelectedSubtitlesTrackId() {
if (!loaded) {
return null;
}
return embeddedSubtitlesSelectedTrackId !== null ?
embeddedSubtitlesSelectedTrackId
:
subtitles.dispatch('getProp', 'selectedTrackId');
}
function getSubtitlesDelay() {
if (!loaded) {
return null;
}
return embeddedSubtitlesSelectedTrackId !== null ?
null
:
subtitles.dispatch('getProp', 'delay');
}
function getsubtitlesSize() {
if (!ready || destroyed) {
return null;
}
return subtitles.dispatch('getProp', 'size');
}
function getSubtitlesDarkBackground() {
if (!ready || destroyed) {
return null;
}
return embeddedSubtitlesSelectedTrackId !== null ?
null
:
subtitles.dispatch('getProp', 'darkBackground');
}
function getSubtitleOffset() {
if (!ready || destroyed) {
return null;
}
return embeddedSubtitlesSelectedTrackId !== null ?
null
:
subtitles.dispatch('getProp', 'offset');
}
function onEnded() {
events.emit('ended');
}
function onError(error) {
Object.freeze(error);
events.emit('error', error);
if (error.critical) {
self.dispatch('command', 'stop');
}
}
function onPausedChanged() {
events.emit('propChanged', 'paused', getPaused());
}
function onTimeChanged() {
events.emit('propChanged', 'time', getTime());
}
function onDurationChanged() {
events.emit('propChanged', 'duration', getDuration());
}
function onBufferingChanged() {
events.emit('propChanged', 'buffering', getBuffering());
}
function onVolumeChanged() {
events.emit('propChanged', 'volume', getVolume());
}
function onSubtitlesTracksChanged() {
events.emit('propChanged', 'subtitlesTracks', getSubtitlesTracks());
}
function onSelectedSubtitlesTrackIdChanged() {
events.emit('propChanged', 'selectedSubtitlesTrackId', getSelectedSubtitlesTrackId());
}
function onSubtitlesDelayChanged() {
events.emit('propChanged', 'subtitlesDelay', getSubtitlesDelay());
}
function onsubtitlesSizeChanged() {
events.emit('propChanged', 'subtitlesSize', getsubtitlesSize());
}
function onSubtitlesDarkBackgroundChanged() {
events.emit('propChanged', 'subtitlesDarkBackground', getSubtitlesDarkBackground());
}
function onSubtitleOffsetChanged() {
events.emit('propChanged', 'subtitleOffset', getSubtitleOffset());
}
function onSubtitlesError(error) {
var code;
var message;
switch (error.code) {
case HTMLSubtitles.ERROR.SUBTITLES_FETCH_FAILED: {
code = HTMLSubtitles.ERROR.SUBTITLES_FETCH_FAILED;
message = 'Failed to fetch subtitles from ' + error.track.origin;
break;
}
case HTMLSubtitles.ERROR.SUBTITLES_PARSE_FAILED: {
code = HTMLSubtitles.ERROR.SUBTITLES_PARSE_FAILED;
message = 'Failed to parse subtitles from ' + error.track.origin;
break;
}
default: {
code = -1;
message = 'Unknown subtitles error';
}
}
onError({
code: code,
message: message,
critical: false
});
}
function onYouTubePlayerApiError() {
onError({
code: YouTubeVideo.ERROR.API_LOAD_FAILED,
message: 'YouTube player API failed to load',
critical: true
});
}
function onYouTubePlayerApiLoaded() {
if (destroyed) {
return;
}
if (!YT) {
onYouTubePlayerApiError();
return;
}
YT.ready(function() {
if (destroyed) {
return;
}
video = new YT.Player(videoContainer, {
height: '100%',
width: '100%',
playerVars: {
autoplay: 1,
cc_load_policy: 3,
controls: 0,
disablekb: 1,
enablejsapi: 1,
fs: 0,
iv_load_policy: 3,
loop: 0,
modestbranding: 1,
playsinline: 1,
rel: 0
},
events: {
onError: onVideoError,
onReady: onVideoReady,
onStateChange: onVideoStateChange,
onApiChange: onVideoApiChange
}
});
});
}
function onVideoError(error) {
var code;
var message;
switch (error.data) {
case YouTubeVideo.ERROR.INVALID_REQUEST: {
code = YouTubeVideo.ERROR.INVALID_REQUEST;
message = 'Invalid request';
break;
}
case YouTubeVideo.ERROR.CONTENT_CANNOT_BE_PLAYED: {
code = YouTubeVideo.ERROR.CONTENT_CANNOT_BE_PLAYED;
message = 'The requested content cannot be played';
break;
}
case YouTubeVideo.ERROR.REMOVED_VIDEO: {
code = YouTubeVideo.ERROR.REMOVED_VIDEO;
message = 'The video has been removed or marked as private';
break;
}
case YouTubeVideo.ERROR.CONTENT_CANNOT_BE_EMBEDDED1:
case YouTubeVideo.ERROR.CONTENT_CANNOT_BE_EMBEDDED2: {
code = YouTubeVideo.ERROR.CONTENT_CANNOT_BE_EMBEDDED1;
message = 'The video cannot be played in embedded players';
break;
}
default: {
code = -1;
message = 'Unknown video error';
}
}
onError({
code: code,
message: message,
critical: true
});
}
function onVideoReady() {
ready = true;
onVolumeChanged();
onsubtitlesSizeChanged();
onSubtitlesDarkBackgroundChanged();
onSubtitleOffsetChanged();
flushDispatchArgsQueue(dispatchArgsReadyQueue);
}
function onVideoStateChange(state) {
if (bufferingObserved) {
onBufferingChanged();
}
switch (state.data) {
case YT.PlayerState.ENDED: {
onEnded();
break;
}
case YT.PlayerState.PAUSED:
case YT.PlayerState.PLAYING: {
if (pausedObserved) {
onPausedChanged();
}
if (timeObserved) {
onTimeChanged();
}
if (durationObserved) {
onDurationChanged();
}
break;
}
case YT.PlayerState.UNSTARTED: {
if (pausedObserved) {
onPausedChanged();
}
break;
}
}
}
function onVideoApiChange() {
video.loadModule('captions');
onSubtitlesTracksChanged();
}
function onPropChangedInterval() {
if (timeObserved) {
onTimeChanged();
}
if (durationObserved) {
onDurationChanged();
}
if (volumeObserved) {
onVolumeChanged();
}
updateSubtitleText();
}
function updateSubtitleText() {
subtitles.dispatch('command', 'updateText', getTime());
}
function flushDispatchArgsQueue(dispatchArgsQueue) {
while (dispatchArgsQueue.length > 0) {
var args = dispatchArgsQueue.shift();
self.dispatch.apply(self, args);
}
}
this.on = function(eventName, listener) {
if (destroyed) {
throw new Error('Unable to add ' + eventName + ' listener');
}
events.on(eventName, listener);
};
this.dispatch = function() {
if (destroyed) {
throw new Error('Unable to dispatch ' + arguments[0]);
}
switch (arguments[0]) {
case 'observeProp': {
switch (arguments[1]) {
case 'paused': {
events.emit('propValue', 'paused', getPaused());
pausedObserved = true;
return;
}
case 'time': {
events.emit('propValue', 'time', getTime());
timeObserved = true;
return;
}
case 'duration': {
events.emit('propValue', 'duration', getDuration());
durationObserved = true;
return;
}
case 'buffering': {
events.emit('propValue', 'buffering', getBuffering());
bufferingObserved = true;
return;
}
case 'volume': {
events.emit('propValue', 'volume', getVolume());
volumeObserved = true;
return;
}
case 'subtitlesTracks': {
events.emit('propValue', 'subtitlesTracks', getSubtitlesTracks());
return;
}
case 'selectedSubtitlesTrackId': {
events.emit('propValue', 'selectedSubtitlesTrackId', getSelectedSubtitlesTrackId());
return;
}
case 'subtitlesDelay': {
events.emit('propValue', 'subtitlesDelay', getSubtitlesDelay());
return;
}
case 'subtitlesSize': {
events.emit('propValue', 'subtitlesSize', getsubtitlesSize());
return;
}
case 'subtitlesDarkBackground': {
events.emit('propValue', 'subtitlesDarkBackground', getSubtitlesDarkBackground());
return;
}
case 'subtitleOffset': {
events.emit('propValue', 'subtitleOffset', getSubtitleOffset());
return;
}
default: {
throw new Error('observeProp not supported: ' + arguments[1]);
}
}
}
case 'setProp': {
switch (arguments[1]) {
case 'paused': {
if (loaded) {
arguments[2] ? video.pauseVideo() : video.playVideo();
} else {
dispatchArgsLoadedQueue.push(Array.from(arguments));
}
return;
}
case 'time': {
if (loaded) {
if (!isNaN(arguments[2]) && arguments[2] !== null) {
video.seekTo(arguments[2] / 1000);
}
} else {
dispatchArgsLoadedQueue.push(Array.from(arguments));
}
return;
}
case 'volume': {
if (ready) {
if (!isNaN(arguments[2]) && arguments[2] !== null) {
video.unMute();
video.setVolume(Math.max(0, Math.min(100, arguments[2])));
}
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
case 'selectedSubtitlesTrackId': {
if (loaded) {
embeddedSubtitlesSelectedTrackId = null;
var tracks = getSubtitlesTracks();
for (var i = 0; i < tracks.length; i++) {
if (tracks[i].id === arguments[2] && tracks[i].origin === 'EMBEDDED IN VIDEO') {
embeddedSubtitlesSelectedTrackId = tracks[i].id;
break;
}
}
video.setOption('captions', 'track', { languageCode: arguments[2] });
subtitles.dispatch('setProp', 'selectedTrackId', arguments[2]);
onSubtitlesDelayChanged();
onSubtitlesDarkBackgroundChanged();
onSelectedSubtitlesTrackIdChanged();
updateSubtitleText();
} else {
dispatchArgsLoadedQueue.push(Array.from(arguments));
}
return;
}
case 'subtitlesDelay': {
if (loaded) {
subtitles.dispatch('setProp', 'delay', arguments[2]);
onSubtitlesDelayChanged();
updateSubtitleText();
} else {
dispatchArgsLoadedQueue.push(Array.from(arguments));
}
return;
}
case 'subtitlesSize': {
if (ready) {
subtitles.dispatch('setProp', 'size', arguments[2]);
video.setOption('captions', 'fontSize', Math.max(1, Math.min(5, Math.floor(arguments[2]))) - 2);
onsubtitlesSizeChanged();
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
case 'subtitlesDarkBackground': {
if (ready) {
subtitles.dispatch('setProp', 'darkBackground', arguments[2]);
onSubtitlesDarkBackgroundChanged();
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
case 'subtitleOffset': {
if (ready) {
subtitles.dispatch('setProp', 'offset', arguments[2]);
onSubtitleOffsetChanged();
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
default: {
throw new Error('setProp not supported: ' + arguments[1]);
}
}
}
case 'command': {
switch (arguments[1]) {
case 'addSubtitlesTracks': {
if (loaded) {
subtitles.dispatch('command', 'addTracks', arguments[2]);
onSubtitlesTracksChanged();
} else {
dispatchArgsLoadedQueue.push(Array.from(arguments));
}
return;
}
case 'mute': {
if (ready) {
video.mute();
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
case 'unmute': {
if (ready) {
video.unMute();
if (video.getVolume() === 0) {
video.setVolume(50);
}
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
case 'stop': {
loaded = false;
dispatchArgsLoadedQueue = [];
subtitles.dispatch('command', 'clearTracks');
if (ready) {
video.stopVideo();
}
onPausedChanged();
onTimeChanged();
onDurationChanged();
onBufferingChanged();
onSubtitlesTracksChanged();
onSelectedSubtitlesTrackIdChanged();
onSubtitlesDelayChanged();
updateSubtitleText();
return;
}
case 'load': {
if (ready) {
var dispatchArgsLoadedQueueCopy = dispatchArgsLoadedQueue.slice();
self.dispatch('command', 'stop');
dispatchArgsLoadedQueue = dispatchArgsLoadedQueueCopy;
var autoplay = typeof arguments[3].autoplay === 'boolean' ? arguments[3].autoplay : true;
var time = !isNaN(arguments[3].time) && arguments[3].time !== null ? arguments[3].time / 1000 : 0;
if (autoplay) {
video.loadVideoById({
videoId: arguments[2].ytId,
startSeconds: time
});
} else {
video.cueVideoById({
videoId: arguments[2].ytId,
startSeconds: time
});
}
loaded = true;
onPausedChanged();
onTimeChanged();
onDurationChanged();
onBufferingChanged();
onSubtitlesTracksChanged();
onSelectedSubtitlesTrackIdChanged();
onSubtitlesDelayChanged();
updateSubtitleText();
flushDispatchArgsQueue(dispatchArgsLoadedQueue);
} else {
dispatchArgsReadyQueue.push(Array.from(arguments));
}
return;
}
case 'destroy': {
self.dispatch('command', 'stop');
destroyed = true;
onVolumeChanged();
onsubtitlesSizeChanged();
onSubtitlesDarkBackgroundChanged();
onSubtitleOffsetChanged();
events.removeAllListeners();
clearInterval(propChangedIntervalId);
if (ready) {
video.destroy();
}
containerElement.removeChild(scriptElement);
containerElement.removeChild(videoContainer);
containerElement.removeChild(stylesElement);
subtitles.dispatch('command', 'destroy');
return;
}
default: {
throw new Error('command not supported: ' + arguments[1]);
}
}
}
default: {
throw new Error('Invalid dispatch call: ' + Array.from(arguments).map(String));
}
}
};
Object.freeze(this);
};
YouTubeVideo.ERROR = Object.freeze({
API_LOAD_FAILED: 12,
INVALID_REQUEST: 2,
CONTENT_CANNOT_BE_PLAYED: 5,
REMOVED_VIDEO: 100,
CONTENT_CANNOT_BE_EMBEDDED1: 101,
CONTENT_CANNOT_BE_EMBEDDED2: 150
});
YouTubeVideo.manifest = Object.freeze({
name: 'YouTubeVideo',
embedded: true,
props: Object.freeze(['paused', 'time', 'duration', 'volume', 'buffering', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesSize', 'subtitlesDelay', 'subtitlesDarkBackground', 'subtitleOffset'])
});
Object.freeze(YouTubeVideo);
module.exports = YouTubeVideo;

View file

@ -1,26 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
function binarySearchUpperBound(array, value) {
if (value < array[0] || array[array.length - 1] < value) {
return -1;
}
var left = 0;
var right = array.length - 1;
var index = -1;
while (left <= right) {
var middle = Math.floor((left + right) / 2);
if (array[middle] > value) {
right = middle - 1;
} else if (array[middle] < value) {
left = middle + 1;
} else {
index = middle;
left = middle + 1;
}
}
return index !== -1 ? index : right;
}
module.exports = binarySearchUpperBound;

View file

@ -1,18 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
function padWithZero(str) {
return ('0' + str).slice(-2);
}
function rgbaToHex(rgbaString) {
var values = rgbaString.split('(')[1].split(')')[0].split(',');
var red = parseInt(values[0]).toString(16);
var green = parseInt(values[1]).toString(16);
var blue = parseInt(values[2]).toString(16);
var alpha = Math.round((values[3] || 1) * 255).toString(16);
return '#' + padWithZero(red) + padWithZero(green) + padWithZero(blue) + padWithZero(alpha);
}
module.exports = {
rgbaToHex
};

View file

@ -1,13 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var HTMLVideo = require('./HTMLVideo');
var MPVVideo = require('./MPVVideo');
var YouTubeVideo = require('./YouTubeVideo');
var withStreamingServer = require('./withStreamingServer');
module.exports = {
HTMLVideo,
MPVVideo,
YouTubeVideo,
withStreamingServer
};

View file

@ -1,61 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var VTTJS = require('vtt.js');
var binarySearchUpperBound = require('./binarySearchUpperBound');
function parse(text) {
var nativeVTTCue = window.VTTCue;
window.VTTCue = VTTJS.VTTCue;
var parser = new VTTJS.WebVTT.Parser(window, VTTJS.WebVTT.StringDecoder());
var cues = [];
var cuesByTime = {};
parser.oncue = function(c) {
var cue = Object.freeze({
startTime: (c.startTime * 1000) | 0,
endTime: (c.endTime * 1000) | 0,
text: c.text
});
cues.push(cue);
cuesByTime[cue.startTime] = cuesByTime[cue.startTime] || [];
cuesByTime[cue.endTime] = cuesByTime[cue.endTime] || [];
};
parser.parse(text);
parser.flush();
window.VTTCue = nativeVTTCue;
cuesByTime.times = Object.keys(cuesByTime)
.map(function(time) {
return parseInt(time);
})
.sort(function(t1, t2) {
return t1 - t2;
});
Object.freeze(cues);
Object.freeze(cuesByTime);
Object.freeze(cuesByTime.times);
for (var i = 0; i < cues.length; i++) {
cuesByTime[cues[i].startTime].push(cues[i]);
var startTimeIndex = binarySearchUpperBound(cuesByTime.times, cues[i].startTime);
for (var j = startTimeIndex + 1; j < cuesByTime.times.length; j++) {
if (cues[i].endTime <= cuesByTime.times[j]) {
break;
}
cuesByTime[cuesByTime.times[j]].push(cues[i]);
}
}
for (var i = 0; i < cuesByTime.times.length; i++) {
cuesByTime[cuesByTime.times[i]].sort(function(c1, c2) {
return c1.startTime - c2.startTime ||
c1.endTime - c2.endTime;
});
Object.freeze(cuesByTime[cuesByTime.times[i]]);
}
return cuesByTime;
}
module.exports = Object.freeze({
parse: parse
});

View file

@ -1,22 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var VTTJS = require('vtt.js');
var binarySearchUpperBound = require('./binarySearchUpperBound');
function render(cuesByTime, time) {
var nodes = [];
var timeIndex = binarySearchUpperBound(cuesByTime.times, time);
if (timeIndex !== -1) {
var cuesForTime = cuesByTime[cuesByTime.times[timeIndex]];
for (var i = 0; i < cuesForTime.length; i++) {
var node = VTTJS.WebVTT.convertCueToDOMTree(window, cuesForTime[i].text);
nodes.push(node);
}
}
return Object.freeze(nodes);
}
module.exports = Object.freeze({
render: render
});

View file

@ -1,190 +0,0 @@
// Copyright (C) 2017-2020 Smart code 203358507
var UrlUtils = require('url');
var EventEmitter = require('events');
var parseVideoName = require('video-name-parser');
var VIDEO_FILE_EXTENTIONS = /.mkv$|.avi$|.mp4$|.wmv$|.vp8$|.mov$|.mpg$|.ts$|.webm$/i;
function createTorrent(streamingServerUrl, infoHash, sources) {
return fetch(UrlUtils.resolve(streamingServerUrl, `/${encodeURIComponent(infoHash)}/create`), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
torrent: {
infoHash,
peerSearch: {
sources: [`dht:${infoHash}`].concat(Array.isArray(sources) ? sources : []),
min: 40,
max: 150
}
}
})
}).then(function(resp) {
return resp.json();
}).catch(function(error) {
throw {
message: 'Unable to get files from torrent',
critical: true,
error: error
};
}).then(function(resp) {
if (!resp || !Array.isArray(resp.files) || resp.files.length === 0) {
throw {
message: 'Unable to get files from torrent',
critical: true
};
}
return resp;
});
}
function guessFileIdx(files, seriesInfo) {
var videoFilesForEpisode = files.filter(function(file) {
if (seriesInfo && file.path.match(VIDEO_FILE_EXTENTIONS)) {
try {
var info = parseVideoName(file.path);
return !isNaN(info.season) && Array.isArray(info.episode) &&
info.season === seriesInfo.season && info.episode.indexOf(seriesInfo.episode) !== -1;
} catch (e) {
return false;
}
}
});
var largestFile = (videoFilesForEpisode.length > 0 ? videoFilesForEpisode : files)
.reduce((result, file) => {
if (!result || file.length > result.length) {
return file;
}
return result;
}, null);
return files.indexOf(largestFile);
}
function withStreamingServer(Video) {
function StreamingServerVideo(options) {
var video = new Video(options);
var events = new EventEmitter();
var destroyed = false;
var stream = null;
events.on('error', function() { });
function onError(error) {
events.emit('error', error);
if (error.critical) {
stop();
video.dispatch({ commandName: 'stop' });
}
}
function stop() {
stream = null;
}
function load(args) {
video.dispatch({ commandName: 'stop' });
stream = args.stream;
new Promise(function(resolve, reject) {
if (typeof args.stream.ytId === 'string') {
resolve(UrlUtils.resolve(args.streamingServerUrl, `/yt/${encodeURIComponent(args.stream.ytId)}?${new URLSearchParams([['request', Date.now()]])}`));
return;
}
if (typeof args.stream.infoHash === 'string') {
if (args.stream.fileIdx !== null && !isNaN(args.stream.fileIdx)) {
resolve(UrlUtils.resolve(args.streamingServerUrl, `/${args.stream.infoHash}/${args.stream.fileIdx}`));
} else {
createTorrent(args.streamingServerUrl, args.stream.infoHash, args.stream.sources)
.then(function(resp) {
var fileIdx = guessFileIdx(resp.files, args.stream.seriesInfo);
resolve(UrlUtils.resolve(args.streamingServerUrl, `/${args.stream.infoHash}/${fileIdx}`));
})
.catch(function(error) {
reject(error);
});
}
return;
}
reject({
message: 'Unable to play stream',
critical: true,
stream: args.stream
});
}).then(function(url) {
if (destroyed || args.stream !== stream) {
return;
}
video.dispatch({
commandName: 'load',
commandArgs: {
autoplay: args.autoplay,
time: args.time,
stream: {
url: url
}
}
});
}).catch(function(error) {
if (destroyed || args.stream !== stream) {
return;
}
onError(error);
});
}
function destroy() {
stop();
destroyed = true;
events.removeAllListeners();
events.on('error', function() { });
}
this.on = function(eventName, listener) {
if (!destroyed) {
events.on(eventName, listener);
}
video.on(eventName, listener);
};
this.dispatch = function(args) {
if (!destroyed && args) {
if (typeof args.commandName === 'string') {
switch (args.commandName) {
case 'stop': {
stop();
break;
}
case 'load': {
load(args.commandArgs);
return;
}
case 'destroy': {
destroy();
break;
}
}
}
}
video.dispatch(args);
};
Object.freeze(this);
}
StreamingServerVideo.manifest = Object.freeze({
name: Video.manifest.name + 'WithStreamingServer',
embedded: true,
props: Object.freeze(Video.manifest.props)
});
Object.freeze(StreamingServerVideo);
return StreamingServerVideo;
}
module.exports = withStreamingServer;

View file

@ -106,8 +106,7 @@ module.exports = (env, argv) => ({
extensions: ['.js', '.json', '.less', '.wasm'],
alias: {
'stremio': path.resolve(__dirname, 'src'),
'stremio-router': path.resolve(__dirname, 'src/router'),
'stremio-video': path.resolve(__dirname, 'src/video')
'stremio-router': path.resolve(__dirname, 'src/router')
}
},
devServer: {