mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-01-11 22:40:31 +00:00
video directory removed
This commit is contained in:
parent
146a80d310
commit
5bf74786c6
12 changed files with 1 additions and 2232 deletions
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# stremio-video
|
||||
|
||||
### TODO move this folder in a separate repo
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue