@stremio/stremio-video 0.0.14 → 0.0.17-rc.1

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