@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stremio/stremio-video",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "Abstraction layer on top of different media players",
5
5
  "author": "Smart Code OOD",
6
6
  "main": "src/index.js",
@@ -189,7 +189,7 @@ function HTMLVideo(options) {
189
189
  return Object.freeze({
190
190
  id: 'EMBEDDED_' + String(index),
191
191
  lang: track.language,
192
- label: track.label,
192
+ label: track.label || null,
193
193
  origin: 'EMBEDDED',
194
194
  embedded: true
195
195
  });
@@ -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;
@@ -0,0 +1,3 @@
1
+ var TitanVideo = require('./TitanVideo');
2
+
3
+ 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: textTrackLang,
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: audioTrackLang,
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.lang || 'eng',
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.lang || 'eng',
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
- fetch(selectedTrack.url)
229
- .then(function(resp) {
230
- if (resp.ok) {
231
- return resp.text();
232
- }
233
-
234
- throw new Error(resp.status + ' (' + resp.statusText + ')');
235
- })
236
- .then(function(text) {
237
- return subtitlesConverter.convert(text);
238
- })
239
- .then(function(text) {
240
- return subtitlesParser.parse(text);
241
- })
242
- .then(function(result) {
243
- if (selectedTrackId !== selectedTrack.id) {
244
- return;
245
- }
246
-
247
- cuesByTime = result;
248
- renderSubtitles();
249
- events.emit('extraSubtitlesTrackLoaded', selectedTrack);
250
- })
251
- .catch(function(error) {
252
- if (selectedTrackId !== selectedTrack.id) {
253
- return;
254
- }
255
-
256
- onError(Object.assign({}, ERROR.WITH_HTML_SUBTITLES.LOAD_FAILED, {
257
- error: error,
258
- track: selectedTrack,
259
- critical: false
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
  :