@stremio/stremio-video 0.0.49 → 0.0.51
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 +1 -1
- package/src/HTMLVideo/HTMLVideo.js +1 -1
- package/src/StremioVideo/selectVideoImplementation.js +7 -0
- package/src/TitanVideo/TitanVideo.js +667 -0
- package/src/TitanVideo/index.js +3 -0
- package/src/TizenVideo/TizenVideo.js +6 -2
- package/src/WebOsVideo/WebOsVideo.js +2 -2
- package/src/supportsTranscoding.js +1 -1
- package/src/withHTMLSubtitles/withHTMLSubtitles.js +42 -34
- package/src/withStreamingServer/withStreamingServer.js +2 -0
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ var ChromecastSenderVideo = require('../ChromecastSenderVideo');
|
|
|
2
2
|
var ShellVideo = require('../ShellVideo');
|
|
3
3
|
var HTMLVideo = require('../HTMLVideo');
|
|
4
4
|
var TizenVideo = require('../TizenVideo');
|
|
5
|
+
var TitanVideo = require('../TitanVideo');
|
|
5
6
|
var WebOsVideo = require('../WebOsVideo');
|
|
6
7
|
var IFrameVideo = require('../IFrameVideo');
|
|
7
8
|
var YouTubeVideo = require('../YouTubeVideo');
|
|
@@ -37,6 +38,9 @@ function selectVideoImplementation(commandArgs, options) {
|
|
|
37
38
|
if (commandArgs.platform === 'webOS') {
|
|
38
39
|
return withStreamingServer(withHTMLSubtitles(WebOsVideo));
|
|
39
40
|
}
|
|
41
|
+
if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') {
|
|
42
|
+
return withStreamingServer(withHTMLSubtitles(TitanVideo));
|
|
43
|
+
}
|
|
40
44
|
return withStreamingServer(withHTMLSubtitles(HTMLVideo));
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -47,6 +51,9 @@ function selectVideoImplementation(commandArgs, options) {
|
|
|
47
51
|
if (commandArgs.platform === 'webOS') {
|
|
48
52
|
return withVideoParams(withHTMLSubtitles(WebOsVideo));
|
|
49
53
|
}
|
|
54
|
+
if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') {
|
|
55
|
+
return withVideoParams(withHTMLSubtitles(TitanVideo));
|
|
56
|
+
}
|
|
50
57
|
return withVideoParams(withHTMLSubtitles(HTMLVideo));
|
|
51
58
|
}
|
|
52
59
|
|
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
var EventEmitter = require('eventemitter3');
|
|
2
|
+
var cloneDeep = require('lodash.clonedeep');
|
|
3
|
+
var deepFreeze = require('deep-freeze');
|
|
4
|
+
var Color = require('color');
|
|
5
|
+
var ERROR = require('../error');
|
|
6
|
+
|
|
7
|
+
function TitanVideo(options) {
|
|
8
|
+
options = options || {};
|
|
9
|
+
|
|
10
|
+
var containerElement = options.containerElement;
|
|
11
|
+
if (!(containerElement instanceof HTMLElement)) {
|
|
12
|
+
throw new Error('Container element required to be instance of HTMLElement');
|
|
13
|
+
}
|
|
14
|
+
|
|
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
|
+
var videoElement = document.createElement('video');
|
|
19
|
+
videoElement.style.width = '100%';
|
|
20
|
+
videoElement.style.height = '100%';
|
|
21
|
+
videoElement.style.backgroundColor = 'black';
|
|
22
|
+
videoElement.controls = false;
|
|
23
|
+
videoElement.playsInline = true;
|
|
24
|
+
videoElement.onerror = function() {
|
|
25
|
+
onVideoError();
|
|
26
|
+
};
|
|
27
|
+
videoElement.onended = function() {
|
|
28
|
+
onEnded();
|
|
29
|
+
};
|
|
30
|
+
videoElement.onpause = function() {
|
|
31
|
+
onPropChanged('paused');
|
|
32
|
+
};
|
|
33
|
+
videoElement.onplay = function() {
|
|
34
|
+
onPropChanged('paused');
|
|
35
|
+
};
|
|
36
|
+
videoElement.ontimeupdate = function() {
|
|
37
|
+
onPropChanged('time');
|
|
38
|
+
onPropChanged('buffered');
|
|
39
|
+
};
|
|
40
|
+
videoElement.ondurationchange = function() {
|
|
41
|
+
onPropChanged('duration');
|
|
42
|
+
};
|
|
43
|
+
videoElement.onwaiting = function() {
|
|
44
|
+
onPropChanged('buffering');
|
|
45
|
+
onPropChanged('buffered');
|
|
46
|
+
};
|
|
47
|
+
videoElement.onseeking = function() {
|
|
48
|
+
onPropChanged('time');
|
|
49
|
+
onPropChanged('buffering');
|
|
50
|
+
onPropChanged('buffered');
|
|
51
|
+
};
|
|
52
|
+
videoElement.onseeked = function() {
|
|
53
|
+
onPropChanged('time');
|
|
54
|
+
onPropChanged('buffering');
|
|
55
|
+
onPropChanged('buffered');
|
|
56
|
+
};
|
|
57
|
+
videoElement.onstalled = function() {
|
|
58
|
+
onPropChanged('buffering');
|
|
59
|
+
onPropChanged('buffered');
|
|
60
|
+
};
|
|
61
|
+
videoElement.onplaying = function() {
|
|
62
|
+
onPropChanged('time');
|
|
63
|
+
onPropChanged('buffering');
|
|
64
|
+
onPropChanged('buffered');
|
|
65
|
+
};
|
|
66
|
+
videoElement.oncanplay = function() {
|
|
67
|
+
onPropChanged('buffering');
|
|
68
|
+
onPropChanged('buffered');
|
|
69
|
+
};
|
|
70
|
+
videoElement.canplaythrough = function() {
|
|
71
|
+
onPropChanged('buffering');
|
|
72
|
+
onPropChanged('buffered');
|
|
73
|
+
};
|
|
74
|
+
videoElement.onloadedmetadata = function() {
|
|
75
|
+
onPropChanged('loaded');
|
|
76
|
+
};
|
|
77
|
+
videoElement.onloadeddata = function() {
|
|
78
|
+
onPropChanged('buffering');
|
|
79
|
+
onPropChanged('buffered');
|
|
80
|
+
};
|
|
81
|
+
videoElement.onvolumechange = function() {
|
|
82
|
+
onPropChanged('volume');
|
|
83
|
+
onPropChanged('muted');
|
|
84
|
+
};
|
|
85
|
+
videoElement.onratechange = function() {
|
|
86
|
+
onPropChanged('playbackSpeed');
|
|
87
|
+
};
|
|
88
|
+
videoElement.textTracks.onchange = function() {
|
|
89
|
+
onPropChanged('subtitlesTracks');
|
|
90
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
91
|
+
onCueChange();
|
|
92
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
93
|
+
track.oncuechange = onCueChange;
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
containerElement.appendChild(videoElement);
|
|
97
|
+
|
|
98
|
+
var events = new EventEmitter();
|
|
99
|
+
var destroyed = false;
|
|
100
|
+
var stream = null;
|
|
101
|
+
var subtitlesOffset = 0;
|
|
102
|
+
var observedProps = {
|
|
103
|
+
stream: false,
|
|
104
|
+
loaded: false,
|
|
105
|
+
paused: false,
|
|
106
|
+
time: false,
|
|
107
|
+
duration: false,
|
|
108
|
+
buffering: false,
|
|
109
|
+
buffered: false,
|
|
110
|
+
subtitlesTracks: false,
|
|
111
|
+
selectedSubtitlesTrackId: false,
|
|
112
|
+
subtitlesOffset: false,
|
|
113
|
+
subtitlesSize: false,
|
|
114
|
+
subtitlesTextColor: false,
|
|
115
|
+
subtitlesBackgroundColor: false,
|
|
116
|
+
subtitlesOutlineColor: false,
|
|
117
|
+
audioTracks: false,
|
|
118
|
+
selectedAudioTrackId: false,
|
|
119
|
+
volume: false,
|
|
120
|
+
muted: false,
|
|
121
|
+
playbackSpeed: false
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function getProp(propName) {
|
|
125
|
+
switch (propName) {
|
|
126
|
+
case 'stream': {
|
|
127
|
+
return stream;
|
|
128
|
+
}
|
|
129
|
+
case 'loaded': {
|
|
130
|
+
if (stream === null) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return videoElement.readyState >= videoElement.HAVE_METADATA;
|
|
135
|
+
}
|
|
136
|
+
case 'paused': {
|
|
137
|
+
if (stream === null) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return !!videoElement.paused;
|
|
142
|
+
}
|
|
143
|
+
case 'time': {
|
|
144
|
+
if (stream === null || videoElement.currentTime === null || !isFinite(videoElement.currentTime)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return Math.floor(videoElement.currentTime * 1000);
|
|
149
|
+
}
|
|
150
|
+
case 'duration': {
|
|
151
|
+
if (stream === null || videoElement.duration === null || !isFinite(videoElement.duration)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return Math.floor(videoElement.duration * 1000);
|
|
156
|
+
}
|
|
157
|
+
case 'buffering': {
|
|
158
|
+
if (stream === null) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return videoElement.readyState < videoElement.HAVE_FUTURE_DATA;
|
|
163
|
+
}
|
|
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
|
+
case 'subtitlesTracks': {
|
|
179
|
+
if (stream === null) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!videoElement.textTracks || !Array.from(videoElement.textTracks).length) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Array.from(videoElement.textTracks)
|
|
188
|
+
.filter(function(track) {
|
|
189
|
+
return track.kind === 'subtitles';
|
|
190
|
+
})
|
|
191
|
+
.map(function(track, index) {
|
|
192
|
+
return Object.freeze({
|
|
193
|
+
id: 'EMBEDDED_' + String(index),
|
|
194
|
+
lang: track.language,
|
|
195
|
+
label: track.label || null,
|
|
196
|
+
origin: 'EMBEDDED',
|
|
197
|
+
embedded: true
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
case 'selectedSubtitlesTrackId': {
|
|
202
|
+
if (stream === null) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!videoElement.textTracks || !Array.from(videoElement.textTracks).length) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return Array.from(videoElement.textTracks)
|
|
211
|
+
.reduce(function(result, track, index) {
|
|
212
|
+
if (result === null && track.mode === 'showing') {
|
|
213
|
+
return 'EMBEDDED_' + String(index);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return result;
|
|
217
|
+
}, null);
|
|
218
|
+
}
|
|
219
|
+
case 'subtitlesOffset': {
|
|
220
|
+
if (destroyed) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return subtitlesOffset;
|
|
225
|
+
}
|
|
226
|
+
case 'subtitlesSize': {
|
|
227
|
+
if (destroyed) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return parseInt(styleElement.sheet.cssRules[0].style.fontSize, 10) * 25;
|
|
232
|
+
}
|
|
233
|
+
case 'subtitlesTextColor': {
|
|
234
|
+
if (destroyed) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return styleElement.sheet.cssRules[0].style.color;
|
|
239
|
+
}
|
|
240
|
+
case 'subtitlesBackgroundColor': {
|
|
241
|
+
if (destroyed) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return styleElement.sheet.cssRules[0].style.backgroundColor;
|
|
246
|
+
}
|
|
247
|
+
case 'subtitlesOutlineColor': {
|
|
248
|
+
if (destroyed) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return styleElement.sheet.cssRules[0].style.textShadow.slice(0, styleElement.sheet.cssRules[0].style.textShadow.indexOf(')') + 1);
|
|
253
|
+
}
|
|
254
|
+
case 'audioTracks': {
|
|
255
|
+
if (stream === null) {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!videoElement.audioTracks || !Array.from(videoElement.audioTracks).length) {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Array.from(videoElement.audioTracks)
|
|
264
|
+
.map(function(track, index) {
|
|
265
|
+
return Object.freeze({
|
|
266
|
+
id: 'EMBEDDED_' + String(index),
|
|
267
|
+
lang: track.language,
|
|
268
|
+
label: track.label || null,
|
|
269
|
+
origin: 'EMBEDDED',
|
|
270
|
+
embedded: true
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
case 'selectedAudioTrackId': {
|
|
275
|
+
|
|
276
|
+
if (stream === null) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!videoElement.audioTracks || !Array.from(videoElement.audioTracks).length) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return Array.from(videoElement.audioTracks)
|
|
285
|
+
.reduce(function(result, track, index) {
|
|
286
|
+
if (result === null && track.enabled) {
|
|
287
|
+
return 'EMBEDDED_' + String(index);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return result;
|
|
291
|
+
}, null);
|
|
292
|
+
}
|
|
293
|
+
case 'volume': {
|
|
294
|
+
if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Math.floor(videoElement.volume * 100);
|
|
299
|
+
}
|
|
300
|
+
case 'muted': {
|
|
301
|
+
if (destroyed) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return !!videoElement.muted;
|
|
306
|
+
}
|
|
307
|
+
case 'playbackSpeed': {
|
|
308
|
+
if (destroyed || videoElement.playbackRate === null || !isFinite(videoElement.playbackRate)) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return videoElement.playbackRate;
|
|
313
|
+
}
|
|
314
|
+
default: {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
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
|
+
function onVideoError() {
|
|
328
|
+
if (destroyed) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
var error;
|
|
333
|
+
switch (videoElement.error.code) {
|
|
334
|
+
case 1: {
|
|
335
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_ABORTED;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case 2: {
|
|
339
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_NETWORK;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case 3: {
|
|
343
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_DECODE;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case 4: {
|
|
347
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_SRC_NOT_SUPPORTED;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
default: {
|
|
351
|
+
error = ERROR.UNKNOWN_ERROR;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
onError(Object.assign({}, error, {
|
|
355
|
+
critical: true,
|
|
356
|
+
error: videoElement.error
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
function onError(error) {
|
|
360
|
+
events.emit('error', error);
|
|
361
|
+
if (error.critical) {
|
|
362
|
+
command('unload');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function onEnded() {
|
|
366
|
+
events.emit('ended');
|
|
367
|
+
}
|
|
368
|
+
function onPropChanged(propName) {
|
|
369
|
+
if (observedProps[propName]) {
|
|
370
|
+
events.emit('propChanged', propName, getProp(propName));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function observeProp(propName) {
|
|
374
|
+
if (observedProps.hasOwnProperty(propName)) {
|
|
375
|
+
events.emit('propValue', propName, getProp(propName));
|
|
376
|
+
observedProps[propName] = true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function setProp(propName, propValue) {
|
|
380
|
+
switch (propName) {
|
|
381
|
+
case 'paused': {
|
|
382
|
+
if (stream !== null) {
|
|
383
|
+
propValue ? videoElement.pause() : videoElement.play();
|
|
384
|
+
onPropChanged('paused');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
case 'time': {
|
|
390
|
+
if (stream !== null && propValue !== null && isFinite(propValue)) {
|
|
391
|
+
videoElement.currentTime = parseInt(propValue, 10) / 1000;
|
|
392
|
+
onPropChanged('time');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case 'selectedSubtitlesTrackId': {
|
|
398
|
+
if (stream !== null) {
|
|
399
|
+
Array.from(videoElement.textTracks)
|
|
400
|
+
.forEach(function(track, index) {
|
|
401
|
+
track.mode = 'EMBEDDED_' + String(index) === propValue ? 'showing' : 'disabled';
|
|
402
|
+
});
|
|
403
|
+
var selectedSubtitlesTrack = getProp('subtitlesTracks')
|
|
404
|
+
.find(function(track) {
|
|
405
|
+
return track.id === propValue;
|
|
406
|
+
});
|
|
407
|
+
if (selectedSubtitlesTrack) {
|
|
408
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
409
|
+
events.emit('subtitlesTrackLoaded', selectedSubtitlesTrack);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case 'subtitlesOffset': {
|
|
416
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
417
|
+
subtitlesOffset = Math.max(0, Math.min(100, parseInt(propValue, 10)));
|
|
418
|
+
onCueChange();
|
|
419
|
+
onPropChanged('subtitlesOffset');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case 'subtitlesSize': {
|
|
425
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
426
|
+
styleElement.sheet.cssRules[0].style.fontSize = Math.floor(Math.max(0, parseInt(propValue, 10)) / 25) + 'vmin';
|
|
427
|
+
onPropChanged('subtitlesSize');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case 'subtitlesTextColor': {
|
|
433
|
+
if (typeof propValue === 'string') {
|
|
434
|
+
try {
|
|
435
|
+
styleElement.sheet.cssRules[0].style.color = Color(propValue).rgb().string();
|
|
436
|
+
} catch (error) {
|
|
437
|
+
// eslint-disable-next-line no-console
|
|
438
|
+
console.error('TitanVideo', error);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
onPropChanged('subtitlesTextColor');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case 'subtitlesBackgroundColor': {
|
|
447
|
+
if (typeof propValue === 'string') {
|
|
448
|
+
try {
|
|
449
|
+
styleElement.sheet.cssRules[0].style.backgroundColor = Color(propValue).rgb().string();
|
|
450
|
+
} catch (error) {
|
|
451
|
+
// eslint-disable-next-line no-console
|
|
452
|
+
console.error('TitanVideo', error);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
onPropChanged('subtitlesBackgroundColor');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
case 'subtitlesOutlineColor': {
|
|
461
|
+
if (typeof propValue === 'string') {
|
|
462
|
+
try {
|
|
463
|
+
styleElement.sheet.cssRules[0].style.textShadow = Color(propValue).rgb().string() + ' 1px 1px 0.1em';
|
|
464
|
+
} catch (error) {
|
|
465
|
+
// eslint-disable-next-line no-console
|
|
466
|
+
console.error('TitanVideo', error);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
onPropChanged('subtitlesOutlineColor');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case 'selectedAudioTrackId': {
|
|
475
|
+
if (stream !== null) {
|
|
476
|
+
for (var index = 0; index < videoElement.audioTracks.length; index++) {
|
|
477
|
+
videoElement.audioTracks[index].enabled = !!('EMBEDDED_' + String(index) === propValue);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var selectedAudioTrack = getProp('audioTracks')
|
|
482
|
+
.find(function(track) {
|
|
483
|
+
return track.id === propValue;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
if (selectedAudioTrack) {
|
|
487
|
+
onPropChanged('selectedAudioTrackId');
|
|
488
|
+
events.emit('audioTrackLoaded', selectedAudioTrack);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
case 'volume': {
|
|
494
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
495
|
+
videoElement.muted = false;
|
|
496
|
+
videoElement.volume = Math.max(0, Math.min(100, parseInt(propValue, 10))) / 100;
|
|
497
|
+
onPropChanged('muted');
|
|
498
|
+
onPropChanged('volume');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
case 'muted': {
|
|
504
|
+
videoElement.muted = !!propValue;
|
|
505
|
+
onPropChanged('muted');
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case 'playbackSpeed': {
|
|
509
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
510
|
+
videoElement.playbackRate = parseFloat(propValue);
|
|
511
|
+
onPropChanged('playbackSpeed');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function command(commandName, commandArgs) {
|
|
519
|
+
switch (commandName) {
|
|
520
|
+
case 'load': {
|
|
521
|
+
command('unload');
|
|
522
|
+
if (commandArgs && commandArgs.stream && typeof commandArgs.stream.url === 'string') {
|
|
523
|
+
stream = commandArgs.stream;
|
|
524
|
+
onPropChanged('stream');
|
|
525
|
+
onPropChanged('loaded');
|
|
526
|
+
videoElement.autoplay = typeof commandArgs.autoplay === 'boolean' ? commandArgs.autoplay : true;
|
|
527
|
+
videoElement.currentTime = commandArgs.time !== null && isFinite(commandArgs.time) ? parseInt(commandArgs.time, 10) / 1000 : 0;
|
|
528
|
+
onPropChanged('paused');
|
|
529
|
+
onPropChanged('time');
|
|
530
|
+
onPropChanged('duration');
|
|
531
|
+
onPropChanged('buffering');
|
|
532
|
+
onPropChanged('buffered');
|
|
533
|
+
if (videoElement.textTracks) {
|
|
534
|
+
videoElement.textTracks.onaddtrack = function() {
|
|
535
|
+
videoElement.textTracks.onaddtrack = null;
|
|
536
|
+
setTimeout(function() {
|
|
537
|
+
onPropChanged('subtitlesTracks');
|
|
538
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
539
|
+
});
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
if (videoElement.audioTracks) {
|
|
543
|
+
videoElement.audioTracks.onaddtrack = function() {
|
|
544
|
+
videoElement.audioTracks.onaddtrack = null;
|
|
545
|
+
setTimeout(function() {
|
|
546
|
+
onPropChanged('audioTracks');
|
|
547
|
+
onPropChanged('selectedAudioTrackId');
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
videoElement.src = stream.url;
|
|
552
|
+
} else {
|
|
553
|
+
onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, {
|
|
554
|
+
critical: true,
|
|
555
|
+
stream: commandArgs ? commandArgs.stream : null
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
case 'unload': {
|
|
561
|
+
stream = null;
|
|
562
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
563
|
+
track.oncuechange = null;
|
|
564
|
+
});
|
|
565
|
+
videoElement.removeAttribute('src');
|
|
566
|
+
videoElement.load();
|
|
567
|
+
videoElement.currentTime = 0;
|
|
568
|
+
onPropChanged('stream');
|
|
569
|
+
onPropChanged('loaded');
|
|
570
|
+
onPropChanged('paused');
|
|
571
|
+
onPropChanged('time');
|
|
572
|
+
onPropChanged('duration');
|
|
573
|
+
onPropChanged('buffering');
|
|
574
|
+
onPropChanged('buffered');
|
|
575
|
+
onPropChanged('subtitlesTracks');
|
|
576
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
577
|
+
onPropChanged('audioTracks');
|
|
578
|
+
onPropChanged('selectedAudioTrackId');
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
case 'destroy': {
|
|
582
|
+
command('unload');
|
|
583
|
+
destroyed = true;
|
|
584
|
+
onPropChanged('subtitlesOffset');
|
|
585
|
+
onPropChanged('subtitlesSize');
|
|
586
|
+
onPropChanged('subtitlesTextColor');
|
|
587
|
+
onPropChanged('subtitlesBackgroundColor');
|
|
588
|
+
onPropChanged('subtitlesOutlineColor');
|
|
589
|
+
onPropChanged('volume');
|
|
590
|
+
onPropChanged('muted');
|
|
591
|
+
onPropChanged('playbackSpeed');
|
|
592
|
+
events.removeAllListeners();
|
|
593
|
+
videoElement.onerror = null;
|
|
594
|
+
videoElement.onended = null;
|
|
595
|
+
videoElement.onpause = null;
|
|
596
|
+
videoElement.onplay = null;
|
|
597
|
+
videoElement.ontimeupdate = null;
|
|
598
|
+
videoElement.ondurationchange = null;
|
|
599
|
+
videoElement.onwaiting = null;
|
|
600
|
+
videoElement.onseeking = null;
|
|
601
|
+
videoElement.onseeked = null;
|
|
602
|
+
videoElement.onstalled = null;
|
|
603
|
+
videoElement.onplaying = null;
|
|
604
|
+
videoElement.oncanplay = null;
|
|
605
|
+
videoElement.canplaythrough = null;
|
|
606
|
+
videoElement.onloadeddata = null;
|
|
607
|
+
videoElement.onvolumechange = null;
|
|
608
|
+
videoElement.onratechange = null;
|
|
609
|
+
videoElement.textTracks.onchange = null;
|
|
610
|
+
containerElement.removeChild(videoElement);
|
|
611
|
+
containerElement.removeChild(styleElement);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
this.on = function(eventName, listener) {
|
|
618
|
+
if (destroyed) {
|
|
619
|
+
throw new Error('Video is destroyed');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
events.on(eventName, listener);
|
|
623
|
+
};
|
|
624
|
+
this.dispatch = function(action) {
|
|
625
|
+
if (destroyed) {
|
|
626
|
+
throw new Error('Video is destroyed');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (action) {
|
|
630
|
+
action = deepFreeze(cloneDeep(action));
|
|
631
|
+
switch (action.type) {
|
|
632
|
+
case 'observeProp': {
|
|
633
|
+
observeProp(action.propName);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
case 'setProp': {
|
|
637
|
+
setProp(action.propName, action.propValue);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
case 'command': {
|
|
641
|
+
command(action.commandName, action.commandArgs);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
throw new Error('Invalid action dispatched: ' + JSON.stringify(action));
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
TitanVideo.canPlayStream = function(stream) {
|
|
652
|
+
if (!stream) {
|
|
653
|
+
return Promise.resolve(false);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return Promise.resolve(true);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
TitanVideo.manifest = {
|
|
660
|
+
name: 'TitanVideo',
|
|
661
|
+
external: false,
|
|
662
|
+
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'volume', 'muted', 'playbackSpeed'],
|
|
663
|
+
commands: ['load', 'unload', 'destroy'],
|
|
664
|
+
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
module.exports = TitanVideo;
|
|
@@ -244,18 +244,20 @@ function TizenVideo(options) {
|
|
|
244
244
|
extra = JSON.parse(textTrack.extra_info);
|
|
245
245
|
} catch(e) {}
|
|
246
246
|
var textTrackLang = typeof extra.track_lang === 'string' && extra.track_lang.length > 0 ? extra.track_lang.trim() : null;
|
|
247
|
+
var textTrackLabel = null;
|
|
247
248
|
if (((tracksData || {}).subs || []).length) {
|
|
248
249
|
var extendedTrackData = tracksData.subs.find(function(el) {
|
|
249
250
|
return (el || {}).id-1 === textTrack.index;
|
|
250
251
|
});
|
|
251
252
|
if (extendedTrackData) {
|
|
252
253
|
textTrackLang = extendedTrackData.lang || 'eng';
|
|
254
|
+
textTrackLabel = extendedTrackData.label || null;
|
|
253
255
|
}
|
|
254
256
|
}
|
|
255
257
|
textTracks.push({
|
|
256
258
|
id: textTrackId,
|
|
257
259
|
lang: textTrackLang,
|
|
258
|
-
label:
|
|
260
|
+
label: textTrackLabel,
|
|
259
261
|
origin: 'EMBEDDED',
|
|
260
262
|
embedded: true,
|
|
261
263
|
mode: !disabledSubs && textTrackId === currentSubTrack ? 'showing' : 'disabled',
|
|
@@ -346,18 +348,20 @@ function TizenVideo(options) {
|
|
|
346
348
|
extra = JSON.parse(audioTrack.extra_info);
|
|
347
349
|
} catch(e) {}
|
|
348
350
|
var audioTrackLang = typeof extra.language === 'string' && extra.language.length > 0 ? extra.language : null;
|
|
351
|
+
var audioTrackLabel = null;
|
|
349
352
|
if (((tracksData || {}).audio || []).length) {
|
|
350
353
|
var extendedTrackData = tracksData.audio.find(function(el) {
|
|
351
354
|
return (el || {}).id-1 === audioTrack.index;
|
|
352
355
|
});
|
|
353
356
|
if (extendedTrackData) {
|
|
354
357
|
audioTrackLang = extendedTrackData.lang || 'eng';
|
|
358
|
+
audioTrackLabel = extendedTrackData.label || null;
|
|
355
359
|
}
|
|
356
360
|
}
|
|
357
361
|
audioTracks.push({
|
|
358
362
|
id: audioTrackId,
|
|
359
363
|
lang: audioTrackLang,
|
|
360
|
-
label:
|
|
364
|
+
label: audioTrackLabel,
|
|
361
365
|
origin: 'EMBEDDED',
|
|
362
366
|
embedded: true,
|
|
363
367
|
mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled',
|
|
@@ -370,7 +370,7 @@ function WebOsVideo(options) {
|
|
|
370
370
|
textTracks.push({
|
|
371
371
|
id: 'EMBEDDED_' + textTrackId,
|
|
372
372
|
lang: track.lang || 'eng',
|
|
373
|
-
label: track.
|
|
373
|
+
label: track.label || null,
|
|
374
374
|
origin: 'EMBEDDED',
|
|
375
375
|
embedded: true,
|
|
376
376
|
mode: textTrackId === currentSubTrack ? 'showing' : 'disabled',
|
|
@@ -392,7 +392,7 @@ function WebOsVideo(options) {
|
|
|
392
392
|
audioTracks.push({
|
|
393
393
|
id: 'EMBEDDED_' + audioTrackId,
|
|
394
394
|
lang: track.lang || 'eng',
|
|
395
|
-
label: track.
|
|
395
|
+
label: track.label || null,
|
|
396
396
|
origin: 'EMBEDDED',
|
|
397
397
|
embedded: true,
|
|
398
398
|
mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var platform = require('./platform');
|
|
2
2
|
|
|
3
3
|
function supportsTranscoding() {
|
|
4
|
-
if (['Tizen', 'webOS'].includes(platform.get()) || typeof window.qt !== 'undefined') {
|
|
4
|
+
if (['Tizen', 'webOS', 'Titan', 'NetTV'].includes(platform.get()) || typeof window.qt !== 'undefined') {
|
|
5
5
|
return Promise.resolve(false);
|
|
6
6
|
}
|
|
7
7
|
return Promise.resolve(true);
|
|
@@ -225,40 +225,48 @@ function withHTMLSubtitles(Video) {
|
|
|
225
225
|
if (selectedTrack) {
|
|
226
226
|
selectedTrackId = selectedTrack.id;
|
|
227
227
|
delay = 0;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
228
|
+
function loadSubtitleFromUrl(url, isFallback) {
|
|
229
|
+
fetch(url)
|
|
230
|
+
.then(function(resp) {
|
|
231
|
+
if (resp.ok) {
|
|
232
|
+
return resp.text();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
236
|
+
})
|
|
237
|
+
.then(function(text) {
|
|
238
|
+
return subtitlesConverter.convert(text);
|
|
239
|
+
})
|
|
240
|
+
.then(function(text) {
|
|
241
|
+
return subtitlesParser.parse(text);
|
|
242
|
+
})
|
|
243
|
+
.then(function(result) {
|
|
244
|
+
if (selectedTrackId !== selectedTrack.id) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
cuesByTime = result;
|
|
249
|
+
renderSubtitles();
|
|
250
|
+
events.emit('extraSubtitlesTrackLoaded', selectedTrack);
|
|
251
|
+
})
|
|
252
|
+
.catch(function(error) {
|
|
253
|
+
if (selectedTrackId !== selectedTrack.id) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!isFallback && typeof selectedTrack.fallbackUrl === 'string') {
|
|
258
|
+
loadSubtitleFromUrl(selectedTrack.fallbackUrl, true);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
onError(Object.assign({}, ERROR.WITH_HTML_SUBTITLES.LOAD_FAILED, {
|
|
263
|
+
error: error,
|
|
264
|
+
track: selectedTrack,
|
|
265
|
+
critical: false
|
|
266
|
+
}));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
loadSubtitleFromUrl(selectedTrack.url);
|
|
262
270
|
}
|
|
263
271
|
renderSubtitles();
|
|
264
272
|
onPropChanged('selectedExtraSubtitlesTrackId');
|
|
@@ -259,6 +259,8 @@ function withStreamingServer(Video) {
|
|
|
259
259
|
commandArgs: Object.assign({}, commandArgs, {
|
|
260
260
|
tracks: commandArgs.tracks.map(function(track) {
|
|
261
261
|
return Object.assign({}, track, {
|
|
262
|
+
// fallback is used in case server conversion fails (if server is offline)
|
|
263
|
+
fallbackUrl: track.url,
|
|
262
264
|
url: typeof track.url === 'string' ?
|
|
263
265
|
url.resolve(loadArgs.streamingServerURL, '/subtitles.vtt?' + new URLSearchParams([['from', track.url]]).toString())
|
|
264
266
|
:
|