@stremio/stremio-video 0.0.53 → 0.0.55
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,12 +454,21 @@ 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) {
|
|
405
467
|
return track.id === propValue;
|
|
406
468
|
});
|
|
469
|
+
|
|
470
|
+
renderSubtitle('', 'hide');
|
|
471
|
+
|
|
407
472
|
if (selectedSubtitlesTrack) {
|
|
408
473
|
onPropChanged('selectedSubtitlesTrackId');
|
|
409
474
|
events.emit('subtitlesTrackLoaded', selectedSubtitlesTrack);
|
|
@@ -414,8 +479,8 @@ function TitanVideo(options) {
|
|
|
414
479
|
}
|
|
415
480
|
case 'subtitlesOffset': {
|
|
416
481
|
if (propValue !== null && isFinite(propValue)) {
|
|
417
|
-
|
|
418
|
-
|
|
482
|
+
offset = Math.max(0, Math.min(100, parseInt(propValue, 10)));
|
|
483
|
+
refreshSubtitle();
|
|
419
484
|
onPropChanged('subtitlesOffset');
|
|
420
485
|
}
|
|
421
486
|
|
|
@@ -423,7 +488,8 @@ function TitanVideo(options) {
|
|
|
423
488
|
}
|
|
424
489
|
case 'subtitlesSize': {
|
|
425
490
|
if (propValue !== null && isFinite(propValue)) {
|
|
426
|
-
|
|
491
|
+
size = Math.max(0, parseInt(propValue, 10));
|
|
492
|
+
refreshSubtitle();
|
|
427
493
|
onPropChanged('subtitlesSize');
|
|
428
494
|
}
|
|
429
495
|
|
|
@@ -432,12 +498,13 @@ function TitanVideo(options) {
|
|
|
432
498
|
case 'subtitlesTextColor': {
|
|
433
499
|
if (typeof propValue === 'string') {
|
|
434
500
|
try {
|
|
435
|
-
|
|
501
|
+
textColor = Color(propValue).rgb().string();
|
|
436
502
|
} catch (error) {
|
|
437
503
|
// eslint-disable-next-line no-console
|
|
438
|
-
console.error('
|
|
504
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
439
505
|
}
|
|
440
506
|
|
|
507
|
+
refreshSubtitle();
|
|
441
508
|
onPropChanged('subtitlesTextColor');
|
|
442
509
|
}
|
|
443
510
|
|
|
@@ -446,12 +513,14 @@ function TitanVideo(options) {
|
|
|
446
513
|
case 'subtitlesBackgroundColor': {
|
|
447
514
|
if (typeof propValue === 'string') {
|
|
448
515
|
try {
|
|
449
|
-
|
|
516
|
+
backgroundColor = Color(propValue).rgb().string();
|
|
450
517
|
} catch (error) {
|
|
451
518
|
// eslint-disable-next-line no-console
|
|
452
|
-
console.error('
|
|
519
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
453
520
|
}
|
|
454
521
|
|
|
522
|
+
refreshSubtitle();
|
|
523
|
+
|
|
455
524
|
onPropChanged('subtitlesBackgroundColor');
|
|
456
525
|
}
|
|
457
526
|
|
|
@@ -460,17 +529,35 @@ function TitanVideo(options) {
|
|
|
460
529
|
case 'subtitlesOutlineColor': {
|
|
461
530
|
if (typeof propValue === 'string') {
|
|
462
531
|
try {
|
|
463
|
-
|
|
532
|
+
outlineColor = Color(propValue).rgb().string();
|
|
464
533
|
} catch (error) {
|
|
465
534
|
// eslint-disable-next-line no-console
|
|
466
|
-
console.error('
|
|
535
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
467
536
|
}
|
|
468
537
|
|
|
538
|
+
refreshSubtitle();
|
|
539
|
+
|
|
469
540
|
onPropChanged('subtitlesOutlineColor');
|
|
470
541
|
}
|
|
471
542
|
|
|
472
543
|
break;
|
|
473
544
|
}
|
|
545
|
+
case 'subtitlesOpacity': {
|
|
546
|
+
if (typeof propValue === 'number') {
|
|
547
|
+
try {
|
|
548
|
+
subtitlesOpacity = Math.min(Math.max(propValue / 100, 0), 1);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
// eslint-disable-next-line no-console
|
|
551
|
+
console.error('Tizen player with HTML Subtitles', error);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
refreshSubtitle();
|
|
555
|
+
|
|
556
|
+
onPropChanged('subtitlesOpacity');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
474
561
|
case 'selectedAudioTrackId': {
|
|
475
562
|
if (stream !== null) {
|
|
476
563
|
for (var index = 0; index < videoElement.audioTracks.length; index++) {
|
|
@@ -529,7 +616,6 @@ function TitanVideo(options) {
|
|
|
529
616
|
onPropChanged('time');
|
|
530
617
|
onPropChanged('duration');
|
|
531
618
|
onPropChanged('buffering');
|
|
532
|
-
onPropChanged('buffered');
|
|
533
619
|
if (videoElement.textTracks) {
|
|
534
620
|
videoElement.textTracks.onaddtrack = function() {
|
|
535
621
|
videoElement.textTracks.onaddtrack = null;
|
|
@@ -571,7 +657,6 @@ function TitanVideo(options) {
|
|
|
571
657
|
onPropChanged('time');
|
|
572
658
|
onPropChanged('duration');
|
|
573
659
|
onPropChanged('buffering');
|
|
574
|
-
onPropChanged('buffered');
|
|
575
660
|
onPropChanged('subtitlesTracks');
|
|
576
661
|
onPropChanged('selectedSubtitlesTrackId');
|
|
577
662
|
onPropChanged('audioTracks');
|
|
@@ -608,7 +693,6 @@ function TitanVideo(options) {
|
|
|
608
693
|
videoElement.onratechange = null;
|
|
609
694
|
videoElement.textTracks.onchange = null;
|
|
610
695
|
containerElement.removeChild(videoElement);
|
|
611
|
-
containerElement.removeChild(styleElement);
|
|
612
696
|
break;
|
|
613
697
|
}
|
|
614
698
|
}
|
|
@@ -659,7 +743,7 @@ TitanVideo.canPlayStream = function(stream) {
|
|
|
659
743
|
TitanVideo.manifest = {
|
|
660
744
|
name: 'TitanVideo',
|
|
661
745
|
external: false,
|
|
662
|
-
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', '
|
|
746
|
+
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'subtitlesOpacity', 'volume', 'muted', 'playbackSpeed'],
|
|
663
747
|
commands: ['load', 'unload', 'destroy'],
|
|
664
748
|
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
|
|
665
749
|
};
|
|
@@ -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
|
})
|