@stremio/stremio-video 0.0.53 → 0.0.54
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
CHANGED
|
@@ -4,17 +4,23 @@ var deepFreeze = require('deep-freeze');
|
|
|
4
4
|
var Color = require('color');
|
|
5
5
|
var ERROR = require('../error');
|
|
6
6
|
|
|
7
|
+
var SSA_DESCRIPTORS_REGEX = /^\{(\\an[1-8])+\}/i;
|
|
8
|
+
|
|
7
9
|
function TitanVideo(options) {
|
|
8
10
|
options = options || {};
|
|
9
11
|
|
|
12
|
+
var size = 100;
|
|
13
|
+
var offset = 0;
|
|
14
|
+
var textColor = 'rgb(255, 255, 255)';
|
|
15
|
+
var backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
16
|
+
var outlineColor = 'rgb(34, 34, 34)';
|
|
17
|
+
var subtitlesOpacity = 1;
|
|
18
|
+
|
|
10
19
|
var containerElement = options.containerElement;
|
|
11
20
|
if (!(containerElement instanceof HTMLElement)) {
|
|
12
21
|
throw new Error('Container element required to be instance of HTMLElement');
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
var styleElement = document.createElement('style');
|
|
16
|
-
containerElement.appendChild(styleElement);
|
|
17
|
-
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; }');
|
|
18
24
|
var videoElement = document.createElement('video');
|
|
19
25
|
videoElement.style.width = '100%';
|
|
20
26
|
videoElement.style.height = '100%';
|
|
@@ -35,48 +41,39 @@ function TitanVideo(options) {
|
|
|
35
41
|
};
|
|
36
42
|
videoElement.ontimeupdate = function() {
|
|
37
43
|
onPropChanged('time');
|
|
38
|
-
onPropChanged('buffered');
|
|
39
44
|
};
|
|
40
45
|
videoElement.ondurationchange = function() {
|
|
41
46
|
onPropChanged('duration');
|
|
42
47
|
};
|
|
43
48
|
videoElement.onwaiting = function() {
|
|
44
49
|
onPropChanged('buffering');
|
|
45
|
-
onPropChanged('buffered');
|
|
46
50
|
};
|
|
47
51
|
videoElement.onseeking = function() {
|
|
48
52
|
onPropChanged('time');
|
|
49
53
|
onPropChanged('buffering');
|
|
50
|
-
onPropChanged('buffered');
|
|
51
54
|
};
|
|
52
55
|
videoElement.onseeked = function() {
|
|
53
56
|
onPropChanged('time');
|
|
54
57
|
onPropChanged('buffering');
|
|
55
|
-
onPropChanged('buffered');
|
|
56
58
|
};
|
|
57
59
|
videoElement.onstalled = function() {
|
|
58
60
|
onPropChanged('buffering');
|
|
59
|
-
onPropChanged('buffered');
|
|
60
61
|
};
|
|
61
62
|
videoElement.onplaying = function() {
|
|
62
63
|
onPropChanged('time');
|
|
63
64
|
onPropChanged('buffering');
|
|
64
|
-
onPropChanged('buffered');
|
|
65
65
|
};
|
|
66
66
|
videoElement.oncanplay = function() {
|
|
67
67
|
onPropChanged('buffering');
|
|
68
|
-
onPropChanged('buffered');
|
|
69
68
|
};
|
|
70
69
|
videoElement.canplaythrough = function() {
|
|
71
70
|
onPropChanged('buffering');
|
|
72
|
-
onPropChanged('buffered');
|
|
73
71
|
};
|
|
74
72
|
videoElement.onloadedmetadata = function() {
|
|
75
73
|
onPropChanged('loaded');
|
|
76
74
|
};
|
|
77
75
|
videoElement.onloadeddata = function() {
|
|
78
76
|
onPropChanged('buffering');
|
|
79
|
-
onPropChanged('buffered');
|
|
80
77
|
};
|
|
81
78
|
videoElement.onvolumechange = function() {
|
|
82
79
|
onPropChanged('volume');
|
|
@@ -88,17 +85,23 @@ function TitanVideo(options) {
|
|
|
88
85
|
videoElement.textTracks.onchange = function() {
|
|
89
86
|
onPropChanged('subtitlesTracks');
|
|
90
87
|
onPropChanged('selectedSubtitlesTrackId');
|
|
91
|
-
onCueChange();
|
|
92
|
-
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
93
|
-
track.oncuechange = onCueChange;
|
|
94
|
-
});
|
|
95
88
|
};
|
|
96
89
|
containerElement.appendChild(videoElement);
|
|
97
90
|
|
|
91
|
+
var subtitlesElement = document.createElement('div');
|
|
92
|
+
subtitlesElement.style.position = 'absolute';
|
|
93
|
+
subtitlesElement.style.right = '0';
|
|
94
|
+
subtitlesElement.style.bottom = '0';
|
|
95
|
+
subtitlesElement.style.left = '0';
|
|
96
|
+
subtitlesElement.style.zIndex = '1';
|
|
97
|
+
subtitlesElement.style.textAlign = 'center';
|
|
98
|
+
containerElement.style.position = 'relative';
|
|
99
|
+
containerElement.style.zIndex = '0';
|
|
100
|
+
containerElement.appendChild(subtitlesElement);
|
|
101
|
+
|
|
98
102
|
var events = new EventEmitter();
|
|
99
103
|
var destroyed = false;
|
|
100
104
|
var stream = null;
|
|
101
|
-
var subtitlesOffset = 0;
|
|
102
105
|
var observedProps = {
|
|
103
106
|
stream: false,
|
|
104
107
|
loaded: false,
|
|
@@ -106,7 +109,6 @@ function TitanVideo(options) {
|
|
|
106
109
|
time: false,
|
|
107
110
|
duration: false,
|
|
108
111
|
buffering: false,
|
|
109
|
-
buffered: false,
|
|
110
112
|
subtitlesTracks: false,
|
|
111
113
|
selectedSubtitlesTrackId: false,
|
|
112
114
|
subtitlesOffset: false,
|
|
@@ -121,6 +123,74 @@ function TitanVideo(options) {
|
|
|
121
123
|
playbackSpeed: false
|
|
122
124
|
};
|
|
123
125
|
|
|
126
|
+
var lastSub;
|
|
127
|
+
var disabledSubs = false;
|
|
128
|
+
|
|
129
|
+
async function refreshSubtitle() {
|
|
130
|
+
if (lastSub) {
|
|
131
|
+
renderSubtitle(lastSub.text, 'show');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function renderSubtitle(text, visibility) {
|
|
136
|
+
if (disabledSubs) return;
|
|
137
|
+
if (visibility === 'hide') {
|
|
138
|
+
while (subtitlesElement.hasChildNodes()) {
|
|
139
|
+
subtitlesElement.removeChild(subtitlesElement.lastChild);
|
|
140
|
+
}
|
|
141
|
+
lastSub = null;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
lastSub = {
|
|
146
|
+
text: text,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
while (subtitlesElement.hasChildNodes()) {
|
|
150
|
+
subtitlesElement.removeChild(subtitlesElement.lastChild);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
subtitlesElement.style.bottom = offset + '%';
|
|
154
|
+
subtitlesElement.style.opacity = subtitlesOpacity;
|
|
155
|
+
|
|
156
|
+
var cueNode = document.createElement('span');
|
|
157
|
+
cueNode.innerHTML = text;
|
|
158
|
+
cueNode.style.display = 'inline-block';
|
|
159
|
+
cueNode.style.padding = '0.2em';
|
|
160
|
+
cueNode.style.fontSize = Math.floor(size / 25) + 'vmin';
|
|
161
|
+
cueNode.style.color = textColor;
|
|
162
|
+
cueNode.style.backgroundColor = backgroundColor;
|
|
163
|
+
cueNode.style.textShadow = '1px 1px 0.1em ' + outlineColor;
|
|
164
|
+
cueNode.style.whiteSpace = 'pre-wrap';
|
|
165
|
+
|
|
166
|
+
subtitlesElement.appendChild(cueNode);
|
|
167
|
+
subtitlesElement.appendChild(document.createElement('br'));
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function renderCue(ev) {
|
|
172
|
+
var cues = (ev.target || {}).activeCues;
|
|
173
|
+
if (!cues.length) {
|
|
174
|
+
renderSubtitle('', 'hide');
|
|
175
|
+
} else {
|
|
176
|
+
if (cues.length > 3) {
|
|
177
|
+
// most probably SSA/ASS subs glitch
|
|
178
|
+
ev.target.removeEventListener('cuechange', renderCue);
|
|
179
|
+
renderSubtitle('', 'hide');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
var text = '';
|
|
183
|
+
for (var i in cues) {
|
|
184
|
+
var cue = cues[i];
|
|
185
|
+
if (cue.text) {
|
|
186
|
+
var cleanedText = cue.text.replace(SSA_DESCRIPTORS_REGEX, '');
|
|
187
|
+
text += (text ? '\n' : '') + cleanedText;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
renderSubtitle(text, 'show');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
124
194
|
function getProp(propName) {
|
|
125
195
|
switch (propName) {
|
|
126
196
|
case 'stream': {
|
|
@@ -161,20 +231,6 @@ function TitanVideo(options) {
|
|
|
161
231
|
|
|
162
232
|
return videoElement.readyState < videoElement.HAVE_FUTURE_DATA;
|
|
163
233
|
}
|
|
164
|
-
case 'buffered': {
|
|
165
|
-
if (stream === null) {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
var time = videoElement.currentTime !== null && isFinite(videoElement.currentTime) ? videoElement.currentTime : 0;
|
|
170
|
-
for (var i = 0; i < videoElement.buffered.length; i++) {
|
|
171
|
-
if (videoElement.buffered.start(i) <= time && time <= videoElement.buffered.end(i)) {
|
|
172
|
-
return Math.floor(videoElement.buffered.end(i) * 1000);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return Math.floor(time * 1000);
|
|
177
|
-
}
|
|
178
234
|
case 'subtitlesTracks': {
|
|
179
235
|
if (stream === null) {
|
|
180
236
|
return [];
|
|
@@ -209,7 +265,7 @@ function TitanVideo(options) {
|
|
|
209
265
|
|
|
210
266
|
return Array.from(videoElement.textTracks)
|
|
211
267
|
.reduce(function(result, track, index) {
|
|
212
|
-
if (result === null && track.mode === '
|
|
268
|
+
if (result === null && track.mode === 'hidden') {
|
|
213
269
|
return 'EMBEDDED_' + String(index);
|
|
214
270
|
}
|
|
215
271
|
|
|
@@ -221,35 +277,42 @@ function TitanVideo(options) {
|
|
|
221
277
|
return null;
|
|
222
278
|
}
|
|
223
279
|
|
|
224
|
-
return
|
|
280
|
+
return offset;
|
|
225
281
|
}
|
|
226
282
|
case 'subtitlesSize': {
|
|
227
283
|
if (destroyed) {
|
|
228
284
|
return null;
|
|
229
285
|
}
|
|
230
286
|
|
|
231
|
-
return
|
|
287
|
+
return size;
|
|
232
288
|
}
|
|
233
289
|
case 'subtitlesTextColor': {
|
|
234
290
|
if (destroyed) {
|
|
235
291
|
return null;
|
|
236
292
|
}
|
|
237
293
|
|
|
238
|
-
return
|
|
294
|
+
return textColor;
|
|
239
295
|
}
|
|
240
296
|
case 'subtitlesBackgroundColor': {
|
|
241
297
|
if (destroyed) {
|
|
242
298
|
return null;
|
|
243
299
|
}
|
|
244
300
|
|
|
245
|
-
return
|
|
301
|
+
return backgroundColor;
|
|
246
302
|
}
|
|
247
303
|
case 'subtitlesOutlineColor': {
|
|
248
304
|
if (destroyed) {
|
|
249
305
|
return null;
|
|
250
306
|
}
|
|
251
307
|
|
|
252
|
-
return
|
|
308
|
+
return outlineColor;
|
|
309
|
+
}
|
|
310
|
+
case 'subtitlesOpacity': {
|
|
311
|
+
if (destroyed) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return subtitlesOpacity;
|
|
253
316
|
}
|
|
254
317
|
case 'audioTracks': {
|
|
255
318
|
if (stream === null) {
|
|
@@ -316,14 +379,6 @@ function TitanVideo(options) {
|
|
|
316
379
|
}
|
|
317
380
|
}
|
|
318
381
|
}
|
|
319
|
-
function onCueChange() {
|
|
320
|
-
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
321
|
-
Array.from(track.cues || []).forEach(function(cue) {
|
|
322
|
-
cue.snapToLines = false;
|
|
323
|
-
cue.line = 100 - subtitlesOffset;
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
382
|
function onVideoError() {
|
|
328
383
|
if (destroyed) {
|
|
329
384
|
return;
|
|
@@ -388,6 +443,7 @@ function TitanVideo(options) {
|
|
|
388
443
|
}
|
|
389
444
|
case 'time': {
|
|
390
445
|
if (stream !== null && propValue !== null && isFinite(propValue)) {
|
|
446
|
+
renderSubtitle('', 'hide');
|
|
391
447
|
videoElement.currentTime = parseInt(propValue, 10) / 1000;
|
|
392
448
|
onPropChanged('time');
|
|
393
449
|
}
|
|
@@ -398,7 +454,13 @@ function TitanVideo(options) {
|
|
|
398
454
|
if (stream !== null) {
|
|
399
455
|
Array.from(videoElement.textTracks)
|
|
400
456
|
.forEach(function(track, index) {
|
|
401
|
-
track.mode
|
|
457
|
+
if (track.mode === 'hidden') {
|
|
458
|
+
track.removeEventListener('cuechange', renderCue);
|
|
459
|
+
}
|
|
460
|
+
track.mode = 'EMBEDDED_' + String(index) === propValue ? 'hidden' : 'disabled';
|
|
461
|
+
if (track.mode === 'hidden') {
|
|
462
|
+
track.addEventListener('cuechange', renderCue);
|
|
463
|
+
}
|
|
402
464
|
});
|
|
403
465
|
var selectedSubtitlesTrack = getProp('subtitlesTracks')
|
|
404
466
|
.find(function(track) {
|
|
@@ -414,8 +476,8 @@ function TitanVideo(options) {
|
|
|
414
476
|
}
|
|
415
477
|
case 'subtitlesOffset': {
|
|
416
478
|
if (propValue !== null && isFinite(propValue)) {
|
|
417
|
-
|
|
418
|
-
|
|
479
|
+
offset = Math.max(0, Math.min(100, parseInt(propValue, 10)));
|
|
480
|
+
refreshSubtitle();
|
|
419
481
|
onPropChanged('subtitlesOffset');
|
|
420
482
|
}
|
|
421
483
|
|
|
@@ -423,7 +485,8 @@ function TitanVideo(options) {
|
|
|
423
485
|
}
|
|
424
486
|
case 'subtitlesSize': {
|
|
425
487
|
if (propValue !== null && isFinite(propValue)) {
|
|
426
|
-
|
|
488
|
+
size = Math.max(0, parseInt(propValue, 10));
|
|
489
|
+
refreshSubtitle();
|
|
427
490
|
onPropChanged('subtitlesSize');
|
|
428
491
|
}
|
|
429
492
|
|
|
@@ -432,12 +495,13 @@ function TitanVideo(options) {
|
|
|
432
495
|
case 'subtitlesTextColor': {
|
|
433
496
|
if (typeof propValue === 'string') {
|
|
434
497
|
try {
|
|
435
|
-
|
|
498
|
+
textColor = Color(propValue).rgb().string();
|
|
436
499
|
} catch (error) {
|
|
437
500
|
// eslint-disable-next-line no-console
|
|
438
|
-
console.error('
|
|
501
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
439
502
|
}
|
|
440
503
|
|
|
504
|
+
refreshSubtitle();
|
|
441
505
|
onPropChanged('subtitlesTextColor');
|
|
442
506
|
}
|
|
443
507
|
|
|
@@ -446,12 +510,14 @@ function TitanVideo(options) {
|
|
|
446
510
|
case 'subtitlesBackgroundColor': {
|
|
447
511
|
if (typeof propValue === 'string') {
|
|
448
512
|
try {
|
|
449
|
-
|
|
513
|
+
backgroundColor = Color(propValue).rgb().string();
|
|
450
514
|
} catch (error) {
|
|
451
515
|
// eslint-disable-next-line no-console
|
|
452
|
-
console.error('
|
|
516
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
453
517
|
}
|
|
454
518
|
|
|
519
|
+
refreshSubtitle();
|
|
520
|
+
|
|
455
521
|
onPropChanged('subtitlesBackgroundColor');
|
|
456
522
|
}
|
|
457
523
|
|
|
@@ -460,17 +526,35 @@ function TitanVideo(options) {
|
|
|
460
526
|
case 'subtitlesOutlineColor': {
|
|
461
527
|
if (typeof propValue === 'string') {
|
|
462
528
|
try {
|
|
463
|
-
|
|
529
|
+
outlineColor = Color(propValue).rgb().string();
|
|
464
530
|
} catch (error) {
|
|
465
531
|
// eslint-disable-next-line no-console
|
|
466
|
-
console.error('
|
|
532
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
467
533
|
}
|
|
468
534
|
|
|
535
|
+
refreshSubtitle();
|
|
536
|
+
|
|
469
537
|
onPropChanged('subtitlesOutlineColor');
|
|
470
538
|
}
|
|
471
539
|
|
|
472
540
|
break;
|
|
473
541
|
}
|
|
542
|
+
case 'subtitlesOpacity': {
|
|
543
|
+
if (typeof propValue === 'number') {
|
|
544
|
+
try {
|
|
545
|
+
subtitlesOpacity = Math.min(Math.max(propValue / 100, 0), 1);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
// eslint-disable-next-line no-console
|
|
548
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
refreshSubtitle();
|
|
552
|
+
|
|
553
|
+
onPropChanged('subtitlesOpacity');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
474
558
|
case 'selectedAudioTrackId': {
|
|
475
559
|
if (stream !== null) {
|
|
476
560
|
for (var index = 0; index < videoElement.audioTracks.length; index++) {
|
|
@@ -529,7 +613,6 @@ function TitanVideo(options) {
|
|
|
529
613
|
onPropChanged('time');
|
|
530
614
|
onPropChanged('duration');
|
|
531
615
|
onPropChanged('buffering');
|
|
532
|
-
onPropChanged('buffered');
|
|
533
616
|
if (videoElement.textTracks) {
|
|
534
617
|
videoElement.textTracks.onaddtrack = function() {
|
|
535
618
|
videoElement.textTracks.onaddtrack = null;
|
|
@@ -571,7 +654,6 @@ function TitanVideo(options) {
|
|
|
571
654
|
onPropChanged('time');
|
|
572
655
|
onPropChanged('duration');
|
|
573
656
|
onPropChanged('buffering');
|
|
574
|
-
onPropChanged('buffered');
|
|
575
657
|
onPropChanged('subtitlesTracks');
|
|
576
658
|
onPropChanged('selectedSubtitlesTrackId');
|
|
577
659
|
onPropChanged('audioTracks');
|
|
@@ -608,7 +690,6 @@ function TitanVideo(options) {
|
|
|
608
690
|
videoElement.onratechange = null;
|
|
609
691
|
videoElement.textTracks.onchange = null;
|
|
610
692
|
containerElement.removeChild(videoElement);
|
|
611
|
-
containerElement.removeChild(styleElement);
|
|
612
693
|
break;
|
|
613
694
|
}
|
|
614
695
|
}
|
|
@@ -659,7 +740,7 @@ TitanVideo.canPlayStream = function(stream) {
|
|
|
659
740
|
TitanVideo.manifest = {
|
|
660
741
|
name: 'TitanVideo',
|
|
661
742
|
external: false,
|
|
662
|
-
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', '
|
|
743
|
+
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'subtitlesOpacity', 'volume', 'muted', 'playbackSpeed'],
|
|
663
744
|
commands: ['load', 'unload', 'destroy'],
|
|
664
745
|
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
|
|
665
746
|
};
|
|
@@ -226,9 +226,11 @@ function withHTMLSubtitles(Video) {
|
|
|
226
226
|
selectedTrackId = selectedTrack.id;
|
|
227
227
|
delay = 0;
|
|
228
228
|
|
|
229
|
-
function getSubtitlesData(track) {
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
function getSubtitlesData(track, isFallback) {
|
|
230
|
+
var url = isFallback ? track.fallbackUrl : track.url;
|
|
231
|
+
|
|
232
|
+
if (typeof url === 'string') {
|
|
233
|
+
return fetch(url)
|
|
232
234
|
.then(function(resp) {
|
|
233
235
|
if (resp.ok) {
|
|
234
236
|
return resp.text();
|
|
@@ -252,7 +254,7 @@ function withHTMLSubtitles(Video) {
|
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
function loadSubtitles(track, isFallback) {
|
|
255
|
-
getSubtitlesData(track)
|
|
257
|
+
getSubtitlesData(track, isFallback)
|
|
256
258
|
.then(function(text) {
|
|
257
259
|
return subtitlesConverter.convert(text);
|
|
258
260
|
})
|