@stremio/stremio-video 0.0.8 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/src/ChromecastSenderVideo/ChromecastSenderVideo.js +15 -8
- package/src/HTMLVideo/HTMLVideo.js +7 -17
- package/src/HTMLVideo/getContentType.js +16 -0
- package/src/HTMLVideo/hlsConfig.js +15 -0
- package/src/IFrameVideo/IFrameVideo.js +130 -0
- package/src/IFrameVideo/index.js +3 -0
- package/src/StremioVideo/StremioVideo.js +11 -5
- package/src/StremioVideo/selectVideoImplementation.js +36 -0
- package/src/YouTubeVideo/YouTubeVideo.js +12 -9
- package/src/error.js +5 -25
- package/src/index.js +1 -13
- package/src/withHTMLSubtitles/subtitlesParser.js +69 -40
- package/src/withHTMLSubtitles/withHTMLSubtitles.js +59 -50
- package/src/withStreamingServer/convertStream.js +61 -0
- package/src/withStreamingServer/inferTorrentFileIdx.js +63 -0
- package/src/withStreamingServer/withStreamingServer.js +101 -189
- package/src/withHTMLSubtitles/fetchSubtitles.js +0 -27
- package/src/withStreamingServer/convertStreamToURL.js +0 -142
- package/src/withStreamingServer/createTranscoder.js +0 -46
- package/src/withStreamingServer/transcodeNextSegment.js +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stremio/stremio-video",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Abstraction layer on top of different media players",
|
|
5
5
|
"author": "Smart Code OOD",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"deep-freeze": "0.0.1",
|
|
17
17
|
"eventemitter3": "4.0.7",
|
|
18
|
-
"
|
|
18
|
+
"hat": "0.0.3",
|
|
19
|
+
"hls.js": "https://github.com/Stremio/hls.js/releases/download/v1.0.10-patch1/hls.js-1.0.10-patch1.tgz",
|
|
19
20
|
"lodash.clonedeep": "4.5.0",
|
|
20
|
-
"magnet-uri": "
|
|
21
|
+
"magnet-uri": "6.2.0",
|
|
21
22
|
"url": "0.11.0",
|
|
22
23
|
"video-name-parser": "1.4.6",
|
|
23
|
-
"vtt.js": "
|
|
24
|
+
"vtt.js": "git://github.com/jaruba/vtt.js.git#e4f5f5603730866bacb174a93f51b734c9f29e6a"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"eslint": "7.
|
|
27
|
+
"eslint": "7.32.0"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -63,9 +63,10 @@ function ChromecastSenderVideo(options) {
|
|
|
63
63
|
extraSubtitlesShadowColor: false
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
function onTransportError(error) {
|
|
66
|
+
function onTransportError(error, message) {
|
|
67
67
|
events.emit('error', Object.assign({}, ERROR.CHROMECAST_SENDER_VIDEO.MESSAGE_SEND_FAILED, {
|
|
68
|
-
error: error
|
|
68
|
+
error: error,
|
|
69
|
+
message: message
|
|
69
70
|
}));
|
|
70
71
|
}
|
|
71
72
|
function onMessage(message) {
|
|
@@ -75,14 +76,14 @@ function ChromecastSenderVideo(options) {
|
|
|
75
76
|
} catch (error) {
|
|
76
77
|
events.emit('error', Object.assign({}, ERROR.CHROMECAST_SENDER_VIDEO.INVALID_MESSAGE_RECEIVED, {
|
|
77
78
|
error: error,
|
|
78
|
-
|
|
79
|
+
message: message
|
|
79
80
|
}));
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
if (!parsedMessage || typeof parsedMessage.event !== 'string') {
|
|
84
85
|
events.emit('error', Object.assign({}, ERROR.CHROMECAST_SENDER_VIDEO.INVALID_MESSAGE_RECEIVED, {
|
|
85
|
-
|
|
86
|
+
message: message
|
|
86
87
|
}));
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
@@ -146,16 +147,22 @@ function ChromecastSenderVideo(options) {
|
|
|
146
147
|
switch (action.type) {
|
|
147
148
|
case 'observeProp': {
|
|
148
149
|
observeProp(action.propName);
|
|
149
|
-
chromecastTransport.sendMessage(action).catch(
|
|
150
|
+
chromecastTransport.sendMessage(action).catch(function(error) {
|
|
151
|
+
onTransportError(error, action);
|
|
152
|
+
});
|
|
150
153
|
return;
|
|
151
154
|
}
|
|
152
155
|
case 'setProp': {
|
|
153
|
-
chromecastTransport.sendMessage(action).catch(
|
|
156
|
+
chromecastTransport.sendMessage(action).catch(function(error) {
|
|
157
|
+
onTransportError(error, action);
|
|
158
|
+
});
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
161
|
case 'command': {
|
|
157
162
|
command(action.commandName, action.commandArgs);
|
|
158
|
-
chromecastTransport.sendMessage(action).catch(
|
|
163
|
+
chromecastTransport.sendMessage(action).catch(function(error) {
|
|
164
|
+
onTransportError(error, action);
|
|
165
|
+
});
|
|
159
166
|
return;
|
|
160
167
|
}
|
|
161
168
|
}
|
|
@@ -174,7 +181,7 @@ ChromecastSenderVideo.manifest = {
|
|
|
174
181
|
external: true,
|
|
175
182
|
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesShadowColor'],
|
|
176
183
|
commands: ['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'],
|
|
177
|
-
events: ['
|
|
184
|
+
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'extraSubtitlesTrackLoaded', 'implementationChanged']
|
|
178
185
|
};
|
|
179
186
|
|
|
180
187
|
module.exports = ChromecastSenderVideo;
|
|
@@ -3,6 +3,8 @@ var Hls = require('hls.js');
|
|
|
3
3
|
var cloneDeep = require('lodash.clonedeep');
|
|
4
4
|
var deepFreeze = require('deep-freeze');
|
|
5
5
|
var ERROR = require('../error');
|
|
6
|
+
var getContentType = require('./getContentType');
|
|
7
|
+
var HLS_CONFIG = require('./hlsConfig');
|
|
6
8
|
|
|
7
9
|
function HTMLVideo(options) {
|
|
8
10
|
options = options || {};
|
|
@@ -151,6 +153,9 @@ function HTMLVideo(options) {
|
|
|
151
153
|
|
|
152
154
|
return !!videoElement.muted;
|
|
153
155
|
}
|
|
156
|
+
default: {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
function onVideoError() {
|
|
@@ -256,7 +261,7 @@ function HTMLVideo(options) {
|
|
|
256
261
|
}
|
|
257
262
|
|
|
258
263
|
if (contentType === 'application/vnd.apple.mpegurl' && Hls.isSupported()) {
|
|
259
|
-
hls = new Hls();
|
|
264
|
+
hls = new Hls(HLS_CONFIG);
|
|
260
265
|
hls.loadSource(stream.url);
|
|
261
266
|
hls.attachMedia(videoElement);
|
|
262
267
|
} else {
|
|
@@ -356,21 +361,6 @@ function HTMLVideo(options) {
|
|
|
356
361
|
};
|
|
357
362
|
}
|
|
358
363
|
|
|
359
|
-
function getContentType(stream) {
|
|
360
|
-
if (!stream || typeof stream.url !== 'string') {
|
|
361
|
-
return Promise.reject(new Error('Invalid stream parameter!'));
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (stream.behaviorHints && stream.behaviorHints.headers && typeof stream.behaviorHints.headers['content-type'] === 'string') {
|
|
365
|
-
return Promise.resolve(stream.behaviorHints.headers['content-type']);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return fetch(stream.url, { method: 'HEAD' })
|
|
369
|
-
.then(function(resp) {
|
|
370
|
-
return resp.headers.get('content-type');
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
364
|
HTMLVideo.canPlayStream = function(stream) {
|
|
375
365
|
if (!stream || (stream.behaviorHints && stream.behaviorHints.notWebReady)) {
|
|
376
366
|
return Promise.resolve(false);
|
|
@@ -391,7 +381,7 @@ HTMLVideo.manifest = {
|
|
|
391
381
|
external: false,
|
|
392
382
|
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'volume', 'muted'],
|
|
393
383
|
commands: ['load', 'unload', 'destroy'],
|
|
394
|
-
events: ['
|
|
384
|
+
events: ['propValue', 'propChanged', 'ended', 'error']
|
|
395
385
|
};
|
|
396
386
|
|
|
397
387
|
module.exports = HTMLVideo;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function getContentType(stream) {
|
|
2
|
+
if (!stream || typeof stream.url !== 'string') {
|
|
3
|
+
return Promise.reject(new Error('Invalid stream parameter!'));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (stream.behaviorHints && stream.behaviorHints.headers && typeof stream.behaviorHints.headers['content-type'] === 'string') {
|
|
7
|
+
return Promise.resolve(stream.behaviorHints.headers['content-type']);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return fetch(stream.url, { method: 'HEAD' })
|
|
11
|
+
.then(function(resp) {
|
|
12
|
+
return resp.headers.get('content-type');
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = getContentType;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
debug: false,
|
|
3
|
+
enableWorker: true,
|
|
4
|
+
lowLatencyMode: false,
|
|
5
|
+
backBufferLength: 0,
|
|
6
|
+
maxBufferLength: 80,
|
|
7
|
+
maxMaxBufferLength: 80,
|
|
8
|
+
maxFragLookUpTolerance: 0,
|
|
9
|
+
maxBufferHole: 0,
|
|
10
|
+
appendErrorMaxRetry: 20,
|
|
11
|
+
nudgeMaxRetry: 20,
|
|
12
|
+
manifestLoadingTimeOut: 30000,
|
|
13
|
+
manifestLoadingMaxRetry: 10,
|
|
14
|
+
// liveDurationInfinity: false
|
|
15
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
var EventEmitter = require('eventemitter3');
|
|
2
|
+
var cloneDeep = require('lodash.clonedeep');
|
|
3
|
+
var deepFreeze = require('deep-freeze');
|
|
4
|
+
var ERROR = require('../error');
|
|
5
|
+
|
|
6
|
+
function IFrameVideo(options) {
|
|
7
|
+
options = options || {};
|
|
8
|
+
|
|
9
|
+
var containerElement = options.containerElement;
|
|
10
|
+
if (!(containerElement instanceof HTMLElement)) {
|
|
11
|
+
throw new Error('Container element required to be instance of HTMLElement');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var iframeElement = document.createElement('iframe');
|
|
15
|
+
iframeElement.style.width = '100%';
|
|
16
|
+
iframeElement.style.height = '100%';
|
|
17
|
+
iframeElement.style.border = 0;
|
|
18
|
+
iframeElement.style.backgroundColor = 'black';
|
|
19
|
+
iframeElement.allowFullscreen = false;
|
|
20
|
+
containerElement.appendChild(iframeElement);
|
|
21
|
+
|
|
22
|
+
var events = new EventEmitter();
|
|
23
|
+
var destroyed = false;
|
|
24
|
+
var stream = null;
|
|
25
|
+
var observedProps = {
|
|
26
|
+
stream: false
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function getProp(propName) {
|
|
30
|
+
switch (propName) {
|
|
31
|
+
case 'stream': {
|
|
32
|
+
return stream;
|
|
33
|
+
}
|
|
34
|
+
default: {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function onError(error) {
|
|
40
|
+
events.emit('error', error);
|
|
41
|
+
if (error.critical) {
|
|
42
|
+
command('unload');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function onPropChanged(propName) {
|
|
46
|
+
if (observedProps[propName]) {
|
|
47
|
+
events.emit('propChanged', propName, getProp(propName));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function observeProp(propName) {
|
|
51
|
+
if (observedProps.hasOwnProperty(propName)) {
|
|
52
|
+
events.emit('propValue', propName, getProp(propName));
|
|
53
|
+
observedProps[propName] = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function command(commandName, commandArgs) {
|
|
57
|
+
switch (commandName) {
|
|
58
|
+
case 'load': {
|
|
59
|
+
command('unload');
|
|
60
|
+
if (commandArgs && commandArgs.stream && typeof commandArgs.stream.playerFrameUrl === 'string') {
|
|
61
|
+
stream = commandArgs.stream;
|
|
62
|
+
onPropChanged('stream');
|
|
63
|
+
iframeElement.src = commandArgs.stream.playerFrameUrl;
|
|
64
|
+
} else {
|
|
65
|
+
onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, {
|
|
66
|
+
critical: true,
|
|
67
|
+
stream: commandArgs ? commandArgs.stream : null
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'unload': {
|
|
73
|
+
stream = null;
|
|
74
|
+
iframeElement.removeAttribute('src');
|
|
75
|
+
onPropChanged('stream');
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'destroy': {
|
|
79
|
+
command('unload');
|
|
80
|
+
destroyed = true;
|
|
81
|
+
events.removeAllListeners();
|
|
82
|
+
containerElement.removeChild(iframeElement);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.on = function(eventName, listener) {
|
|
89
|
+
if (destroyed) {
|
|
90
|
+
throw new Error('Video is destroyed');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
events.on(eventName, listener);
|
|
94
|
+
};
|
|
95
|
+
this.dispatch = function(action) {
|
|
96
|
+
if (destroyed) {
|
|
97
|
+
throw new Error('Video is destroyed');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (action) {
|
|
101
|
+
action = deepFreeze(cloneDeep(action));
|
|
102
|
+
switch (action.type) {
|
|
103
|
+
case 'observeProp': {
|
|
104
|
+
observeProp(action.propName);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
case 'command': {
|
|
108
|
+
command(action.commandName, action.commandArgs);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Error('Invalid action dispatched: ' + JSON.stringify(action));
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
IFrameVideo.canPlayStream = function(stream) {
|
|
119
|
+
return Promise.resolve(stream && typeof stream.playerFrameUrl === 'string');
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
IFrameVideo.manifest = {
|
|
123
|
+
name: 'IFrameVideo',
|
|
124
|
+
external: false,
|
|
125
|
+
props: ['stream'],
|
|
126
|
+
commands: ['load', 'unload', 'destroy'],
|
|
127
|
+
events: ['propValue', 'propChanged', 'error']
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
module.exports = IFrameVideo;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
var EventEmitter = require('eventemitter3');
|
|
2
2
|
var cloneDeep = require('lodash.clonedeep');
|
|
3
3
|
var deepFreeze = require('deep-freeze');
|
|
4
|
+
var selectVideoImplementation = require('./selectVideoImplementation');
|
|
5
|
+
var ERROR = require('../error');
|
|
4
6
|
|
|
5
7
|
function StremioVideo(options) {
|
|
6
8
|
options = options || {};
|
|
7
9
|
|
|
8
|
-
var selectVideoImplementation = options.selectVideoImplementation;
|
|
9
|
-
if (typeof selectVideoImplementation !== 'function') {
|
|
10
|
-
throw new Error('selectVideoImplementation argument required');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
10
|
var video = null;
|
|
14
11
|
var events = new EventEmitter();
|
|
15
12
|
var destroyed = false;
|
|
@@ -35,6 +32,15 @@ function StremioVideo(options) {
|
|
|
35
32
|
video = null;
|
|
36
33
|
}
|
|
37
34
|
if (video === null) {
|
|
35
|
+
if (Video === null) {
|
|
36
|
+
events.emit('error', Object.assign({}, ERROR.UNSUPPORTED_STREAM, {
|
|
37
|
+
error: new Error('No video implementation was selected'),
|
|
38
|
+
critical: true,
|
|
39
|
+
stream: action.commandArgs.stream
|
|
40
|
+
}));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
video = new Video(Object.assign({}, options, action.commandArgs));
|
|
39
45
|
video.on('ended', function() {
|
|
40
46
|
events.emit('ended');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var ChromecastSenderVideo = require('../ChromecastSenderVideo');
|
|
2
|
+
var HTMLVideo = require('../HTMLVideo');
|
|
3
|
+
var IFrameVideo = require('../IFrameVideo');
|
|
4
|
+
var YouTubeVideo = require('../YouTubeVideo');
|
|
5
|
+
var withStreamingServer = require('../withStreamingServer');
|
|
6
|
+
var withHTMLSubtitles = require('../withHTMLSubtitles');
|
|
7
|
+
|
|
8
|
+
function selectVideoImplementation(args) {
|
|
9
|
+
if (!args.stream || typeof args.stream.externalUrl === 'string') {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (args.chromecastTransport && args.chromecastTransport.getCastState() === cast.framework.CastState.CONNECTED) {
|
|
14
|
+
return ChromecastSenderVideo;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (typeof args.stream.ytId === 'string') {
|
|
18
|
+
return withHTMLSubtitles(YouTubeVideo);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof args.stream.playerFrameUrl === 'string') {
|
|
22
|
+
return IFrameVideo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof args.streamingServerURL === 'string') {
|
|
26
|
+
return withStreamingServer(withHTMLSubtitles(HTMLVideo));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof args.stream.url === 'string') {
|
|
30
|
+
return withHTMLSubtitles(HTMLVideo);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = selectVideoImplementation;
|
|
@@ -258,6 +258,9 @@ function YouTubeVideo(options) {
|
|
|
258
258
|
|
|
259
259
|
return selectedSubtitlesTrackId;
|
|
260
260
|
}
|
|
261
|
+
default: {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
261
264
|
}
|
|
262
265
|
}
|
|
263
266
|
function onError(error) {
|
|
@@ -352,9 +355,9 @@ function YouTubeVideo(options) {
|
|
|
352
355
|
function command(commandName, commandArgs) {
|
|
353
356
|
switch (commandName) {
|
|
354
357
|
case 'load': {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
+
command('unload');
|
|
359
|
+
if (commandArgs && commandArgs.stream && typeof commandArgs.stream.ytId === 'string') {
|
|
360
|
+
if (ready) {
|
|
358
361
|
stream = commandArgs.stream;
|
|
359
362
|
onPropChanged('stream');
|
|
360
363
|
var autoplay = typeof commandArgs.autoplay === 'boolean' ? commandArgs.autoplay : true;
|
|
@@ -379,13 +382,13 @@ function YouTubeVideo(options) {
|
|
|
379
382
|
onPropChanged('subtitlesTracks');
|
|
380
383
|
onPropChanged('selectedSubtitlesTrackId');
|
|
381
384
|
} else {
|
|
382
|
-
|
|
383
|
-
critical: true,
|
|
384
|
-
stream: commandArgs ? commandArgs.stream : null
|
|
385
|
-
}));
|
|
385
|
+
pendingLoadArgs = commandArgs;
|
|
386
386
|
}
|
|
387
387
|
} else {
|
|
388
|
-
|
|
388
|
+
onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, {
|
|
389
|
+
critical: true,
|
|
390
|
+
stream: commandArgs ? commandArgs.stream : null
|
|
391
|
+
}));
|
|
389
392
|
}
|
|
390
393
|
|
|
391
394
|
break;
|
|
@@ -466,7 +469,7 @@ YouTubeVideo.manifest = {
|
|
|
466
469
|
external: false,
|
|
467
470
|
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId'],
|
|
468
471
|
commands: ['load', 'unload', 'destroy'],
|
|
469
|
-
events: ['
|
|
472
|
+
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded']
|
|
470
473
|
};
|
|
471
474
|
|
|
472
475
|
module.exports = YouTubeVideo;
|
package/src/error.js
CHANGED
|
@@ -50,36 +50,16 @@ var ERROR = {
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
WITH_HTML_SUBTITLES: {
|
|
53
|
-
|
|
53
|
+
LOAD_FAILED: {
|
|
54
54
|
code: 70,
|
|
55
|
-
message: 'Failed to
|
|
56
|
-
},
|
|
57
|
-
PARSE_FAILED: {
|
|
58
|
-
code: 71,
|
|
59
|
-
message: 'Failed to parse subtitles'
|
|
55
|
+
message: 'Failed to load external subtitles'
|
|
60
56
|
}
|
|
61
57
|
},
|
|
62
58
|
WITH_STREAMING_SERVER: {
|
|
63
|
-
|
|
59
|
+
CONVERT_FAILED: {
|
|
64
60
|
code: 60,
|
|
65
|
-
message: 'Unable to convert stream
|
|
66
|
-
}
|
|
67
|
-
TRANSCODING_FAILED: {
|
|
68
|
-
code: 61,
|
|
69
|
-
message: 'Unable to transcode the next segment of the stream',
|
|
70
|
-
},
|
|
71
|
-
TRANSCODER_CREATE_FAILED: {
|
|
72
|
-
code: 62,
|
|
73
|
-
message: 'Failed to create transcoder'
|
|
74
|
-
},
|
|
75
|
-
TORRENT_CREATE_FAILED: {
|
|
76
|
-
code: 63,
|
|
77
|
-
message: 'Failed to create torrent'
|
|
78
|
-
},
|
|
79
|
-
NO_MEDIA_FILES_FOUND: {
|
|
80
|
-
code: 64,
|
|
81
|
-
message: 'No media files found into the torrent'
|
|
82
|
-
},
|
|
61
|
+
message: 'Unable to convert stream'
|
|
62
|
+
}
|
|
83
63
|
},
|
|
84
64
|
UNKNOWN_ERROR: {
|
|
85
65
|
code: 1,
|
package/src/index.js
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
1
|
var StremioVideo = require('./StremioVideo');
|
|
2
|
-
var HTMLVideo = require('./HTMLVideo');
|
|
3
|
-
var YouTubeVideo = require('./YouTubeVideo');
|
|
4
|
-
var ChromecastSenderVideo = require('./ChromecastSenderVideo');
|
|
5
|
-
var withHTMLSubtitles = require('./withHTMLSubtitles');
|
|
6
|
-
var withStreamingServer = require('./withStreamingServer');
|
|
7
2
|
|
|
8
|
-
module.exports =
|
|
9
|
-
StremioVideo: StremioVideo,
|
|
10
|
-
HTMLVideo: HTMLVideo,
|
|
11
|
-
YouTubeVideo: YouTubeVideo,
|
|
12
|
-
ChromecastSenderVideo: ChromecastSenderVideo,
|
|
13
|
-
withHTMLSubtitles: withHTMLSubtitles,
|
|
14
|
-
withStreamingServer: withStreamingServer
|
|
15
|
-
};
|
|
3
|
+
module.exports = StremioVideo;
|
|
@@ -1,52 +1,81 @@
|
|
|
1
1
|
var VTTJS = require('vtt.js');
|
|
2
2
|
var binarySearchUpperBound = require('./binarySearchUpperBound');
|
|
3
3
|
|
|
4
|
+
var CRITICAL_ERROR_CODE = 0;
|
|
5
|
+
|
|
4
6
|
function parse(text) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
return new Promise(function(resolve, reject) {
|
|
8
|
+
var parser = new VTTJS.WebVTT.Parser(window, VTTJS.WebVTT.StringDecoder());
|
|
9
|
+
var errors = [];
|
|
10
|
+
var cues = [];
|
|
11
|
+
var cuesByTime = {};
|
|
12
|
+
|
|
13
|
+
parser.oncue = function(c) {
|
|
14
|
+
var cue = {
|
|
15
|
+
startTime: (c.startTime * 1000) | 0,
|
|
16
|
+
endTime: (c.endTime * 1000) | 0,
|
|
17
|
+
text: c.text
|
|
18
|
+
};
|
|
19
|
+
cues.push(cue);
|
|
20
|
+
cuesByTime[cue.startTime] = cuesByTime[cue.startTime] || [];
|
|
21
|
+
cuesByTime[cue.endTime] = cuesByTime[cue.endTime] || [];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
parser.onparsingerror = function(error) {
|
|
25
|
+
if (error.code === CRITICAL_ERROR_CODE) {
|
|
26
|
+
parser.oncue = null;
|
|
27
|
+
parser.onparsingerror = null;
|
|
28
|
+
parser.onflush = null;
|
|
29
|
+
reject(error);
|
|
30
|
+
} else {
|
|
31
|
+
console.warn('Subtitles parsing error', error);
|
|
32
|
+
errors.push(error);
|
|
33
|
+
}
|
|
15
34
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
break;
|
|
35
|
+
|
|
36
|
+
parser.onflush = function() {
|
|
37
|
+
cuesByTime.times = Object.keys(cuesByTime)
|
|
38
|
+
.map(function(time) {
|
|
39
|
+
return parseInt(time, 10);
|
|
40
|
+
})
|
|
41
|
+
.sort(function(t1, t2) {
|
|
42
|
+
return t1 - t2;
|
|
43
|
+
});
|
|
44
|
+
for (var i = 0; i < cues.length; i++) {
|
|
45
|
+
cuesByTime[cues[i].startTime].push(cues[i]);
|
|
46
|
+
var startTimeIndex = binarySearchUpperBound(cuesByTime.times, cues[i].startTime);
|
|
47
|
+
for (var j = startTimeIndex + 1; j < cuesByTime.times.length; j++) {
|
|
48
|
+
if (cues[i].endTime <= cuesByTime.times[j]) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cuesByTime[cuesByTime.times[j]].push(cues[i]);
|
|
53
|
+
}
|
|
36
54
|
}
|
|
37
55
|
|
|
38
|
-
cuesByTime
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
for (var k = 0; k < cuesByTime.times.length; k++) {
|
|
57
|
+
cuesByTime[cuesByTime.times[k]].sort(function(c1, c2) {
|
|
58
|
+
return c1.startTime - c2.startTime ||
|
|
59
|
+
c1.endTime - c2.endTime;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
parser.oncue = null;
|
|
64
|
+
parser.onparsingerror = null;
|
|
65
|
+
parser.onflush = null;
|
|
66
|
+
// we may have multiple parsing errors here, but will only respond with the first
|
|
67
|
+
// if subtitle cues are available, we will not reject the promise
|
|
68
|
+
if (cues.length === 0 && errors.length) {
|
|
69
|
+
reject(errors[0]);
|
|
70
|
+
} else if (cuesByTime.times.length === 0) {
|
|
71
|
+
reject(new Error('Missing subtitle track cues'));
|
|
72
|
+
} else {
|
|
73
|
+
resolve(cuesByTime);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
48
76
|
|
|
49
|
-
|
|
77
|
+
parser.parse(text);
|
|
78
|
+
});
|
|
50
79
|
}
|
|
51
80
|
|
|
52
81
|
module.exports = {
|