@stremio/stremio-video 0.0.14 → 0.0.17-rc.1
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 +3 -2
- package/src/ChromecastSenderVideo/ChromecastSenderVideo.js +3 -3
- package/src/HTMLVideo/HTMLVideo.js +271 -3
- package/src/HTMLVideo/getContentType.js +5 -1
- package/src/YouTubeVideo/YouTubeVideo.js +1 -1
- package/src/withHTMLSubtitles/subtitlesConverter.js +75 -0
- package/src/withHTMLSubtitles/withHTMLSubtitles.js +45 -17
- package/src/withStreamingServer/inferTorrentFileIdx.js +5 -1
- package/src/withStreamingServer/withStreamingServer.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stremio/stremio-video",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17-rc.1",
|
|
4
4
|
"description": "Abstraction layer on top of different media players",
|
|
5
5
|
"author": "Smart Code OOD",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"magnet-uri": "6.2.0",
|
|
22
22
|
"url": "0.11.0",
|
|
23
23
|
"video-name-parser": "1.4.6",
|
|
24
|
-
"vtt.js": "
|
|
24
|
+
"vtt.js": "github:jaruba/vtt.js#e4f5f5603730866bacb174a93f51b734c9f29e6a",
|
|
25
|
+
"color": "4.2.3"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"eslint": "7.32.0"
|
|
@@ -60,7 +60,7 @@ function ChromecastSenderVideo(options) {
|
|
|
60
60
|
extraSubtitlesOffset: false,
|
|
61
61
|
extraSubtitlesTextColor: false,
|
|
62
62
|
extraSubtitlesBackgroundColor: false,
|
|
63
|
-
|
|
63
|
+
extraSubtitlesOutlineColor: false
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
function onTransportError(error, message) {
|
|
@@ -122,7 +122,7 @@ function ChromecastSenderVideo(options) {
|
|
|
122
122
|
onPropChanged('extraSubtitlesOffset', null);
|
|
123
123
|
onPropChanged('extraSubtitlesTextColor', null);
|
|
124
124
|
onPropChanged('extraSubtitlesBackgroundColor', null);
|
|
125
|
-
onPropChanged('
|
|
125
|
+
onPropChanged('extraSubtitlesOutlineColor', null);
|
|
126
126
|
events.removeAllListeners();
|
|
127
127
|
chromecastTransport.off('message', onMessage);
|
|
128
128
|
containerElement.removeChild(deviceNameContainerElement);
|
|
@@ -179,7 +179,7 @@ ChromecastSenderVideo.canPlayStream = function() {
|
|
|
179
179
|
ChromecastSenderVideo.manifest = {
|
|
180
180
|
name: 'ChromecastSenderVideo',
|
|
181
181
|
external: true,
|
|
182
|
-
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', '
|
|
182
|
+
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesOutlineColor'],
|
|
183
183
|
commands: ['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'],
|
|
184
184
|
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'extraSubtitlesTrackLoaded', 'implementationChanged']
|
|
185
185
|
};
|
|
@@ -2,6 +2,7 @@ var EventEmitter = require('eventemitter3');
|
|
|
2
2
|
var Hls = require('hls.js');
|
|
3
3
|
var cloneDeep = require('lodash.clonedeep');
|
|
4
4
|
var deepFreeze = require('deep-freeze');
|
|
5
|
+
var Color = require('color');
|
|
5
6
|
var ERROR = require('../error');
|
|
6
7
|
var getContentType = require('./getContentType');
|
|
7
8
|
var HLS_CONFIG = require('./hlsConfig');
|
|
@@ -14,6 +15,9 @@ function HTMLVideo(options) {
|
|
|
14
15
|
throw new Error('Container element required to be instance of HTMLElement');
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
var styleElement = document.createElement('style');
|
|
19
|
+
containerElement.appendChild(styleElement);
|
|
20
|
+
styleElement.sheet.insertRule('video::cue { font-size: 4vmin; color: rgb(255, 255, 255); background-color: rgba(0, 0, 0, 0); text-shadow: rgb(34, 34, 34) 1px 1px 0.1em; }');
|
|
17
21
|
var videoElement = document.createElement('video');
|
|
18
22
|
videoElement.style.width = '100%';
|
|
19
23
|
videoElement.style.height = '100%';
|
|
@@ -75,12 +79,24 @@ function HTMLVideo(options) {
|
|
|
75
79
|
onPropChanged('volume');
|
|
76
80
|
onPropChanged('muted');
|
|
77
81
|
};
|
|
82
|
+
videoElement.onratechange = function() {
|
|
83
|
+
onPropChanged('playbackSpeed');
|
|
84
|
+
};
|
|
85
|
+
videoElement.textTracks.onchange = function() {
|
|
86
|
+
onPropChanged('subtitlesTracks');
|
|
87
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
88
|
+
onCueChange();
|
|
89
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
90
|
+
track.oncuechange = onCueChange;
|
|
91
|
+
});
|
|
92
|
+
};
|
|
78
93
|
containerElement.appendChild(videoElement);
|
|
79
94
|
|
|
80
95
|
var hls = null;
|
|
81
96
|
var events = new EventEmitter();
|
|
82
97
|
var destroyed = false;
|
|
83
98
|
var stream = null;
|
|
99
|
+
var subtitlesOffset = 0;
|
|
84
100
|
var observedProps = {
|
|
85
101
|
stream: false,
|
|
86
102
|
paused: false,
|
|
@@ -88,8 +104,18 @@ function HTMLVideo(options) {
|
|
|
88
104
|
duration: false,
|
|
89
105
|
buffering: false,
|
|
90
106
|
buffered: false,
|
|
107
|
+
subtitlesTracks: false,
|
|
108
|
+
selectedSubtitlesTrackId: false,
|
|
109
|
+
subtitlesOffset: false,
|
|
110
|
+
subtitlesSize: false,
|
|
111
|
+
subtitlesTextColor: false,
|
|
112
|
+
subtitlesBackgroundColor: false,
|
|
113
|
+
subtitlesOutlineColor: false,
|
|
114
|
+
audioTracks: false,
|
|
115
|
+
selectedAudioTrackId: false,
|
|
91
116
|
volume: false,
|
|
92
|
-
muted: false
|
|
117
|
+
muted: false,
|
|
118
|
+
playbackSpeed: false
|
|
93
119
|
};
|
|
94
120
|
|
|
95
121
|
function getProp(propName) {
|
|
@@ -139,6 +165,106 @@ function HTMLVideo(options) {
|
|
|
139
165
|
|
|
140
166
|
return Math.floor(time * 1000);
|
|
141
167
|
}
|
|
168
|
+
case 'subtitlesTracks': {
|
|
169
|
+
if (stream === null) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return Array.from(videoElement.textTracks)
|
|
174
|
+
.map(function(track, index) {
|
|
175
|
+
return Object.freeze({
|
|
176
|
+
id: 'EMBEDDED_' + String(index),
|
|
177
|
+
lang: track.language,
|
|
178
|
+
label: track.label,
|
|
179
|
+
origin: 'EMBEDDED',
|
|
180
|
+
embedded: true
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
case 'selectedSubtitlesTrackId': {
|
|
185
|
+
if (stream === null) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return Array.from(videoElement.textTracks)
|
|
190
|
+
.reduce(function(result, track, index) {
|
|
191
|
+
if (result === null && track.mode === 'showing') {
|
|
192
|
+
return 'EMBEDDED_' + String(index);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
}, null);
|
|
197
|
+
}
|
|
198
|
+
case 'subtitlesOffset': {
|
|
199
|
+
if (destroyed) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return subtitlesOffset;
|
|
204
|
+
}
|
|
205
|
+
case 'subtitlesSize': {
|
|
206
|
+
if (destroyed) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return parseInt(styleElement.sheet.cssRules[0].style.fontSize, 10) * 25;
|
|
211
|
+
}
|
|
212
|
+
case 'subtitlesTextColor': {
|
|
213
|
+
if (destroyed) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return styleElement.sheet.cssRules[0].style.color;
|
|
218
|
+
}
|
|
219
|
+
case 'subtitlesBackgroundColor': {
|
|
220
|
+
if (destroyed) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return styleElement.sheet.cssRules[0].style.backgroundColor;
|
|
225
|
+
}
|
|
226
|
+
case 'subtitlesOutlineColor': {
|
|
227
|
+
if (destroyed) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return styleElement.sheet.cssRules[0].style.textShadow.slice(0, styleElement.sheet.cssRules[0].style.textShadow.indexOf(')') + 1);
|
|
232
|
+
}
|
|
233
|
+
case 'audioTracks': {
|
|
234
|
+
if (hls === null || !Array.isArray(hls.audioTracks)) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return hls.audioTracks
|
|
239
|
+
.map(function(track) {
|
|
240
|
+
return Object.freeze({
|
|
241
|
+
id: 'EMBEDDED_' + String(track.id),
|
|
242
|
+
lang: typeof track.lang === 'string' && track.lang.length > 0 ?
|
|
243
|
+
track.lang
|
|
244
|
+
:
|
|
245
|
+
typeof track.name === 'string' && track.name.length > 0 ?
|
|
246
|
+
track.name
|
|
247
|
+
:
|
|
248
|
+
String(track.id),
|
|
249
|
+
label: typeof track.name === 'string' && track.name.length > 0 ?
|
|
250
|
+
track.name
|
|
251
|
+
:
|
|
252
|
+
typeof track.lang === 'string' && track.lang.length > 0 ?
|
|
253
|
+
track.lang
|
|
254
|
+
:
|
|
255
|
+
String(track.id),
|
|
256
|
+
origin: 'EMBEDDED',
|
|
257
|
+
embedded: true
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
case 'selectedAudioTrackId': {
|
|
262
|
+
if (hls === null || hls.audioTrack === null || !isFinite(hls.audioTrack) || hls.audioTrack === -1) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return 'EMBEDDED_' + String(hls.audioTrack);
|
|
267
|
+
}
|
|
142
268
|
case 'volume': {
|
|
143
269
|
if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) {
|
|
144
270
|
return null;
|
|
@@ -153,11 +279,26 @@ function HTMLVideo(options) {
|
|
|
153
279
|
|
|
154
280
|
return !!videoElement.muted;
|
|
155
281
|
}
|
|
282
|
+
case 'playbackSpeed': {
|
|
283
|
+
if (destroyed || videoElement.playbackRate === null || !isFinite(videoElement.playbackRate)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return videoElement.playbackRate;
|
|
288
|
+
}
|
|
156
289
|
default: {
|
|
157
290
|
return null;
|
|
158
291
|
}
|
|
159
292
|
}
|
|
160
293
|
}
|
|
294
|
+
function onCueChange() {
|
|
295
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
296
|
+
Array.from(track.activeCues || []).forEach(function(cue) {
|
|
297
|
+
cue.snapToLines = false;
|
|
298
|
+
cue.line = 100 - subtitlesOffset;
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
161
302
|
function onVideoError() {
|
|
162
303
|
if (destroyed) {
|
|
163
304
|
return;
|
|
@@ -226,6 +367,96 @@ function HTMLVideo(options) {
|
|
|
226
367
|
|
|
227
368
|
break;
|
|
228
369
|
}
|
|
370
|
+
case 'selectedSubtitlesTrackId': {
|
|
371
|
+
if (stream !== null) {
|
|
372
|
+
Array.from(videoElement.textTracks)
|
|
373
|
+
.forEach(function(track, index) {
|
|
374
|
+
track.mode = 'EMBEDDED_' + String(index) === propValue ? 'showing' : 'disabled';
|
|
375
|
+
});
|
|
376
|
+
var selecterdSubtitlesTrack = getProp('subtitlesTracks')
|
|
377
|
+
.find(function(track) {
|
|
378
|
+
return track.id === propValue;
|
|
379
|
+
});
|
|
380
|
+
if (selecterdSubtitlesTrack) {
|
|
381
|
+
events.emit('subtitlesTrackLoaded', selecterdSubtitlesTrack);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
case 'subtitlesOffset': {
|
|
388
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
389
|
+
subtitlesOffset = Math.max(0, Math.min(100, parseInt(propValue, 10)));
|
|
390
|
+
onCueChange();
|
|
391
|
+
onPropChanged('subtitlesOffset');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case 'subtitlesSize': {
|
|
397
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
398
|
+
styleElement.sheet.cssRules[0].style.fontSize = Math.floor(Math.max(0, parseInt(propValue, 10)) / 25) + 'vmin';
|
|
399
|
+
onPropChanged('subtitlesSize');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case 'subtitlesTextColor': {
|
|
405
|
+
if (typeof propValue === 'string') {
|
|
406
|
+
try {
|
|
407
|
+
styleElement.sheet.cssRules[0].style.color = Color(propValue).rgb().string();
|
|
408
|
+
} catch (error) {
|
|
409
|
+
// eslint-disable-next-line no-console
|
|
410
|
+
console.error('HTMLVideo', error);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
onPropChanged('subtitlesTextColor');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case 'subtitlesBackgroundColor': {
|
|
419
|
+
if (typeof propValue === 'string') {
|
|
420
|
+
try {
|
|
421
|
+
styleElement.sheet.cssRules[0].style.backgroundColor = Color(propValue).rgb().string();
|
|
422
|
+
} catch (error) {
|
|
423
|
+
// eslint-disable-next-line no-console
|
|
424
|
+
console.error('HTMLVideo', error);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
onPropChanged('subtitlesBackgroundColor');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case 'subtitlesOutlineColor': {
|
|
433
|
+
if (typeof propValue === 'string') {
|
|
434
|
+
try {
|
|
435
|
+
styleElement.sheet.cssRules[0].style.textShadow = Color(propValue).rgb().string() + ' 1px 1px 0.1em';
|
|
436
|
+
} catch (error) {
|
|
437
|
+
// eslint-disable-next-line no-console
|
|
438
|
+
console.error('HTMLVideo', error);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
onPropChanged('subtitlesOutlineColor');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case 'selectedAudioTrackId': {
|
|
447
|
+
if (hls !== null) {
|
|
448
|
+
var selecterdAudioTrack = getProp('audioTracks')
|
|
449
|
+
.find(function(track) {
|
|
450
|
+
return track.id === propValue;
|
|
451
|
+
});
|
|
452
|
+
hls.audioTrack = selecterdAudioTrack ? parseInt(selecterdAudioTrack.id.split('_').pop(), 10) : -1;
|
|
453
|
+
if (selecterdAudioTrack) {
|
|
454
|
+
events.emit('audioTrackLoaded', selecterdAudioTrack);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
229
460
|
case 'volume': {
|
|
230
461
|
if (propValue !== null && isFinite(propValue)) {
|
|
231
462
|
videoElement.muted = false;
|
|
@@ -238,6 +469,13 @@ function HTMLVideo(options) {
|
|
|
238
469
|
videoElement.muted = !!propValue;
|
|
239
470
|
break;
|
|
240
471
|
}
|
|
472
|
+
case 'playbackSpeed': {
|
|
473
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
474
|
+
videoElement.playbackRate = parseFloat(propValue);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
241
479
|
}
|
|
242
480
|
}
|
|
243
481
|
function command(commandName, commandArgs) {
|
|
@@ -254,6 +492,10 @@ function HTMLVideo(options) {
|
|
|
254
492
|
onPropChanged('duration');
|
|
255
493
|
onPropChanged('buffering');
|
|
256
494
|
onPropChanged('buffered');
|
|
495
|
+
onPropChanged('subtitlesTracks');
|
|
496
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
497
|
+
onPropChanged('audioTracks');
|
|
498
|
+
onPropChanged('selectedAudioTrackId');
|
|
257
499
|
getContentType(stream)
|
|
258
500
|
.then(function(contentType) {
|
|
259
501
|
if (stream !== commandArgs.stream) {
|
|
@@ -262,6 +504,14 @@ function HTMLVideo(options) {
|
|
|
262
504
|
|
|
263
505
|
if (contentType === 'application/vnd.apple.mpegurl' && Hls.isSupported()) {
|
|
264
506
|
hls = new Hls(HLS_CONFIG);
|
|
507
|
+
hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, function() {
|
|
508
|
+
onPropChanged('audioTracks');
|
|
509
|
+
onPropChanged('selectedAudioTrackId');
|
|
510
|
+
});
|
|
511
|
+
hls.on(Hls.Events.AUDIO_TRACK_SWITCHED, function() {
|
|
512
|
+
onPropChanged('audioTracks');
|
|
513
|
+
onPropChanged('selectedAudioTrackId');
|
|
514
|
+
});
|
|
265
515
|
hls.loadSource(stream.url);
|
|
266
516
|
hls.attachMedia(videoElement);
|
|
267
517
|
} else {
|
|
@@ -285,7 +535,12 @@ function HTMLVideo(options) {
|
|
|
285
535
|
}
|
|
286
536
|
case 'unload': {
|
|
287
537
|
stream = null;
|
|
538
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
539
|
+
track.oncuechange = null;
|
|
540
|
+
});
|
|
288
541
|
if (hls !== null) {
|
|
542
|
+
hls.removeAllListeners();
|
|
543
|
+
hls.detachMedia(videoElement);
|
|
289
544
|
hls.destroy();
|
|
290
545
|
hls = null;
|
|
291
546
|
}
|
|
@@ -298,13 +553,23 @@ function HTMLVideo(options) {
|
|
|
298
553
|
onPropChanged('duration');
|
|
299
554
|
onPropChanged('buffering');
|
|
300
555
|
onPropChanged('buffered');
|
|
556
|
+
onPropChanged('subtitlesTracks');
|
|
557
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
558
|
+
onPropChanged('audioTracks');
|
|
559
|
+
onPropChanged('selectedAudioTrackId');
|
|
301
560
|
break;
|
|
302
561
|
}
|
|
303
562
|
case 'destroy': {
|
|
304
563
|
command('unload');
|
|
305
564
|
destroyed = true;
|
|
565
|
+
onPropChanged('subtitlesOffset');
|
|
566
|
+
onPropChanged('subtitlesSize');
|
|
567
|
+
onPropChanged('subtitlesTextColor');
|
|
568
|
+
onPropChanged('subtitlesBackgroundColor');
|
|
569
|
+
onPropChanged('subtitlesOutlineColor');
|
|
306
570
|
onPropChanged('volume');
|
|
307
571
|
onPropChanged('muted');
|
|
572
|
+
onPropChanged('playbackSpeed');
|
|
308
573
|
events.removeAllListeners();
|
|
309
574
|
videoElement.onerror = null;
|
|
310
575
|
videoElement.onended = null;
|
|
@@ -321,7 +586,10 @@ function HTMLVideo(options) {
|
|
|
321
586
|
videoElement.canplaythrough = null;
|
|
322
587
|
videoElement.onloadeddata = null;
|
|
323
588
|
videoElement.onvolumechange = null;
|
|
589
|
+
videoElement.onratechange = null;
|
|
590
|
+
videoElement.textTracks.onchange = null;
|
|
324
591
|
containerElement.removeChild(videoElement);
|
|
592
|
+
containerElement.removeChild(styleElement);
|
|
325
593
|
break;
|
|
326
594
|
}
|
|
327
595
|
}
|
|
@@ -379,9 +647,9 @@ HTMLVideo.canPlayStream = function(stream) {
|
|
|
379
647
|
HTMLVideo.manifest = {
|
|
380
648
|
name: 'HTMLVideo',
|
|
381
649
|
external: false,
|
|
382
|
-
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'volume', 'muted'],
|
|
650
|
+
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'volume', 'muted', 'playbackSpeed'],
|
|
383
651
|
commands: ['load', 'unload', 'destroy'],
|
|
384
|
-
events: ['propValue', 'propChanged', 'ended', 'error']
|
|
652
|
+
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
|
|
385
653
|
};
|
|
386
654
|
|
|
387
655
|
module.exports = HTMLVideo;
|
|
@@ -9,7 +9,11 @@ function getContentType(stream) {
|
|
|
9
9
|
|
|
10
10
|
return fetch(stream.url, { method: 'HEAD' })
|
|
11
11
|
.then(function(resp) {
|
|
12
|
-
|
|
12
|
+
if (resp.ok) {
|
|
13
|
+
return resp.headers.get('content-type');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -243,7 +243,7 @@ function YouTubeVideo(options) {
|
|
|
243
243
|
})
|
|
244
244
|
.map(function(track, index) {
|
|
245
245
|
return Object.freeze({
|
|
246
|
-
id: String(index),
|
|
246
|
+
id: 'EMBEDDED_' + String(index),
|
|
247
247
|
lang: track.languageCode,
|
|
248
248
|
label: typeof track.displayName === 'string' ? track.displayName : track.languageCode,
|
|
249
249
|
origin: 'EMBEDDED',
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// from: https://github.com/silviapfeiffer/silviapfeiffer.github.io/blob/master/index.html#L150-L216
|
|
2
|
+
|
|
3
|
+
function srt2webvtt(data) {
|
|
4
|
+
// remove dos newlines
|
|
5
|
+
var srt = data.replace(/\r+/g, '');
|
|
6
|
+
// trim white space start and end
|
|
7
|
+
srt = srt.replace(/^\s+|\s+$/g, '');
|
|
8
|
+
// get cues
|
|
9
|
+
var cuelist = srt.split('\n\n');
|
|
10
|
+
var result = '';
|
|
11
|
+
if (cuelist.length > 0) {
|
|
12
|
+
result += 'WEBVTT\n\n';
|
|
13
|
+
for (var i = 0; i < cuelist.length; i = i + 1) {
|
|
14
|
+
result += convertSrtCue(cuelist[i]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function convertSrtCue(caption) {
|
|
21
|
+
// remove all html tags for security reasons
|
|
22
|
+
caption = caption.replace(/<[a-zA-Z/][^>]*>/g, '');
|
|
23
|
+
|
|
24
|
+
var cue = '';
|
|
25
|
+
var s = caption.split(/\n/);
|
|
26
|
+
// concatenate muilt-line string separated in array into one
|
|
27
|
+
while (s.length > 3) {
|
|
28
|
+
for (var i = 3; i < s.length; i++) {
|
|
29
|
+
s[2] += '\n' + s[i];
|
|
30
|
+
}
|
|
31
|
+
s.splice(3, s.length - 3);
|
|
32
|
+
}
|
|
33
|
+
var line = 0;
|
|
34
|
+
// detect identifier
|
|
35
|
+
if (!s[0].match(/\d+:\d+:\d+/) && s[1].match(/\d+:\d+:\d+/)) {
|
|
36
|
+
cue += s[0].match(/\w+/) + '\n';
|
|
37
|
+
line += 1;
|
|
38
|
+
}
|
|
39
|
+
// get time strings
|
|
40
|
+
if (s[line].match(/\d+:\d+:\d+/)) {
|
|
41
|
+
// convert time string
|
|
42
|
+
var m = s[1].match(/(\d+):(\d+):(\d+)(?:,(\d+))?\s*--?>\s*(\d+):(\d+):(\d+)(?:,(\d+))?/);
|
|
43
|
+
if (m) {
|
|
44
|
+
cue += m[1] + ':' + m[2] + ':' + m[3] + '.' + m[4] + ' --> '
|
|
45
|
+
+ m[5] + ':' + m[6] + ':' + m[7] + '.' + m[8] + '\n';
|
|
46
|
+
line += 1;
|
|
47
|
+
} else {
|
|
48
|
+
// Unrecognized timestring
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// file format error or comment lines
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
// get cue text
|
|
56
|
+
if (s[line]) {
|
|
57
|
+
cue += s[line] + '\n\n';
|
|
58
|
+
}
|
|
59
|
+
return cue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
convert: function(text) {
|
|
64
|
+
// presume all to be SRT if not WEBVTT
|
|
65
|
+
if (text.includes('WEBVTT')) {
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
return srt2webvtt(text);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error('Failed to convert srt to webvtt: ' + error.message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
var EventEmitter = require('eventemitter3');
|
|
2
2
|
var cloneDeep = require('lodash.clonedeep');
|
|
3
3
|
var deepFreeze = require('deep-freeze');
|
|
4
|
+
var Color = require('color');
|
|
4
5
|
var ERROR = require('../error');
|
|
5
6
|
var subtitlesParser = require('./subtitlesParser');
|
|
6
7
|
var subtitlesRenderer = require('./subtitlesRenderer');
|
|
8
|
+
var subtitlesConverter = require('./subtitlesConverter');
|
|
7
9
|
|
|
8
10
|
function withHTMLSubtitles(Video) {
|
|
9
11
|
function VideoWithHTMLSubtitles(options) {
|
|
@@ -48,9 +50,9 @@ function withHTMLSubtitles(Video) {
|
|
|
48
50
|
var delay = null;
|
|
49
51
|
var size = 100;
|
|
50
52
|
var offset = 0;
|
|
51
|
-
var textColor = '
|
|
52
|
-
var backgroundColor = '
|
|
53
|
-
var
|
|
53
|
+
var textColor = 'rgb(255, 255, 255)';
|
|
54
|
+
var backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
55
|
+
var outlineColor = 'rgb(34, 34, 34)';
|
|
54
56
|
var observedProps = {
|
|
55
57
|
extraSubtitlesTracks: false,
|
|
56
58
|
selectedExtraSubtitlesTrackId: false,
|
|
@@ -59,7 +61,7 @@ function withHTMLSubtitles(Video) {
|
|
|
59
61
|
extraSubtitlesOffset: false,
|
|
60
62
|
extraSubtitlesTextColor: false,
|
|
61
63
|
extraSubtitlesBackgroundColor: false,
|
|
62
|
-
|
|
64
|
+
extraSubtitlesOutlineColor: false
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
function renderSubtitles() {
|
|
@@ -78,8 +80,9 @@ function withHTMLSubtitles(Video) {
|
|
|
78
80
|
cueNode.style.fontSize = Math.floor(size / 25) + 'vmin';
|
|
79
81
|
cueNode.style.color = textColor;
|
|
80
82
|
cueNode.style.backgroundColor = backgroundColor;
|
|
81
|
-
cueNode.style.textShadow = '1px 1px 0.1em ' +
|
|
82
|
-
subtitlesElement.
|
|
83
|
+
cueNode.style.textShadow = '1px 1px 0.1em ' + outlineColor;
|
|
84
|
+
subtitlesElement.appendChild(cueNode);
|
|
85
|
+
subtitlesElement.appendChild(document.createElement('br'));
|
|
83
86
|
});
|
|
84
87
|
}
|
|
85
88
|
function onVideoError(error) {
|
|
@@ -167,12 +170,12 @@ function withHTMLSubtitles(Video) {
|
|
|
167
170
|
|
|
168
171
|
return backgroundColor;
|
|
169
172
|
}
|
|
170
|
-
case '
|
|
173
|
+
case 'extraSubtitlesOutlineColor': {
|
|
171
174
|
if (destroyed) {
|
|
172
175
|
return null;
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
return
|
|
178
|
+
return outlineColor;
|
|
176
179
|
}
|
|
177
180
|
default: {
|
|
178
181
|
return videoPropValue;
|
|
@@ -188,7 +191,7 @@ function withHTMLSubtitles(Video) {
|
|
|
188
191
|
case 'extraSubtitlesOffset':
|
|
189
192
|
case 'extraSubtitlesTextColor':
|
|
190
193
|
case 'extraSubtitlesBackgroundColor':
|
|
191
|
-
case '
|
|
194
|
+
case 'extraSubtitlesOutlineColor': {
|
|
192
195
|
events.emit('propValue', propName, getProp(propName, null));
|
|
193
196
|
observedProps[propName] = true;
|
|
194
197
|
return true;
|
|
@@ -212,7 +215,14 @@ function withHTMLSubtitles(Video) {
|
|
|
212
215
|
delay = 0;
|
|
213
216
|
fetch(selectedTrack.url)
|
|
214
217
|
.then(function(resp) {
|
|
215
|
-
|
|
218
|
+
if (resp.ok) {
|
|
219
|
+
return resp.text();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
223
|
+
})
|
|
224
|
+
.then(function(text) {
|
|
225
|
+
return subtitlesConverter.convert(text);
|
|
216
226
|
})
|
|
217
227
|
.then(function(text) {
|
|
218
228
|
return subtitlesParser.parse(text);
|
|
@@ -272,7 +282,13 @@ function withHTMLSubtitles(Video) {
|
|
|
272
282
|
}
|
|
273
283
|
case 'extraSubtitlesTextColor': {
|
|
274
284
|
if (typeof propValue === 'string') {
|
|
275
|
-
|
|
285
|
+
try {
|
|
286
|
+
textColor = Color(propValue).rgb().string();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
// eslint-disable-next-line no-console
|
|
289
|
+
console.error('withHTMLSubtitles', error);
|
|
290
|
+
}
|
|
291
|
+
|
|
276
292
|
renderSubtitles();
|
|
277
293
|
onPropChanged('extraSubtitlesTextColor');
|
|
278
294
|
}
|
|
@@ -281,18 +297,30 @@ function withHTMLSubtitles(Video) {
|
|
|
281
297
|
}
|
|
282
298
|
case 'extraSubtitlesBackgroundColor': {
|
|
283
299
|
if (typeof propValue === 'string') {
|
|
284
|
-
|
|
300
|
+
try {
|
|
301
|
+
backgroundColor = Color(propValue).rgb().string();
|
|
302
|
+
} catch (error) {
|
|
303
|
+
// eslint-disable-next-line no-console
|
|
304
|
+
console.error('withHTMLSubtitles', error);
|
|
305
|
+
}
|
|
306
|
+
|
|
285
307
|
renderSubtitles();
|
|
286
308
|
onPropChanged('extraSubtitlesBackgroundColor');
|
|
287
309
|
}
|
|
288
310
|
|
|
289
311
|
return true;
|
|
290
312
|
}
|
|
291
|
-
case '
|
|
313
|
+
case 'extraSubtitlesOutlineColor': {
|
|
292
314
|
if (typeof propValue === 'string') {
|
|
293
|
-
|
|
315
|
+
try {
|
|
316
|
+
outlineColor = Color(propValue).rgb().string();
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// eslint-disable-next-line no-console
|
|
319
|
+
console.error('withHTMLSubtitles', error);
|
|
320
|
+
}
|
|
321
|
+
|
|
294
322
|
renderSubtitles();
|
|
295
|
-
onPropChanged('
|
|
323
|
+
onPropChanged('extraSubtitlesOutlineColor');
|
|
296
324
|
}
|
|
297
325
|
|
|
298
326
|
return true;
|
|
@@ -357,7 +385,7 @@ function withHTMLSubtitles(Video) {
|
|
|
357
385
|
onPropChanged('extraSubtitlesOffset');
|
|
358
386
|
onPropChanged('extraSubtitlesTextColor');
|
|
359
387
|
onPropChanged('extraSubtitlesBackgroundColor');
|
|
360
|
-
onPropChanged('
|
|
388
|
+
onPropChanged('extraSubtitlesOutlineColor');
|
|
361
389
|
video.dispatch({ type: 'command', commandName: 'destroy' });
|
|
362
390
|
events.removeAllListeners();
|
|
363
391
|
containerElement.removeChild(subtitlesElement);
|
|
@@ -419,7 +447,7 @@ function withHTMLSubtitles(Video) {
|
|
|
419
447
|
VideoWithHTMLSubtitles.manifest = {
|
|
420
448
|
name: Video.manifest.name + 'WithHTMLSubtitles',
|
|
421
449
|
external: Video.manifest.external,
|
|
422
|
-
props: Video.manifest.props.concat(['extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', '
|
|
450
|
+
props: Video.manifest.props.concat(['extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesOutlineColor'])
|
|
423
451
|
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
424
452
|
commands: Video.manifest.commands.concat(['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'])
|
|
425
453
|
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
@@ -20,7 +20,11 @@ function inferTorrentFileIdx(streamingServerURL, infoHash, sources, seriesInfo)
|
|
|
20
20
|
}
|
|
21
21
|
})
|
|
22
22
|
}).then(function(resp) {
|
|
23
|
-
|
|
23
|
+
if (resp.ok) {
|
|
24
|
+
return resp.json();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
24
28
|
}).then(function(resp) {
|
|
25
29
|
if (!resp || !Array.isArray(resp.files) || resp.files.some(function(file) { return !file || typeof file.path !== 'string' || file.length === null || !isFinite(file.length); })) {
|
|
26
30
|
throw new Error('No files found in the torrent');
|
|
@@ -113,8 +113,8 @@ function withStreamingServer(Video) {
|
|
|
113
113
|
if (commandArgs.forceTranscoding) {
|
|
114
114
|
queryParams.set('forceTranscoding', '1');
|
|
115
115
|
}
|
|
116
|
-
if (commandArgs.
|
|
117
|
-
queryParams.set('
|
|
116
|
+
if (commandArgs.maxAudioChannels !== null && isFinite(commandArgs.maxAudioChannels)) {
|
|
117
|
+
queryParams.set('maxAudioChannels', commandArgs.maxAudioChannels);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
return {
|