@stremio/stremio-video 0.0.16 → 0.0.17-rc.3

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.16",
3
+ "version": "0.0.17-rc.3",
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": "github:jaruba/vtt.js#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"
@@ -49,10 +49,18 @@ function ChromecastSenderVideo(options) {
49
49
  duration: false,
50
50
  buffering: false,
51
51
  buffered: false,
52
- volume: false,
53
- muted: false,
52
+ audioTracks: false,
53
+ selectedAudioTrackId: false,
54
54
  subtitlesTracks: false,
55
55
  selectedSubtitlesTrackId: false,
56
+ subtitlesOffset: false,
57
+ subtitlesSize: false,
58
+ subtitlesTextColor: false,
59
+ subtitlesBackgroundColor: false,
60
+ subtitlesOutlineColor: false,
61
+ volume: false,
62
+ muted: false,
63
+ playbackSpeed: false,
56
64
  extraSubtitlesTracks: false,
57
65
  selectedExtraSubtitlesTrackId: false,
58
66
  extraSubtitlesDelay: false,
@@ -60,7 +68,7 @@ function ChromecastSenderVideo(options) {
60
68
  extraSubtitlesOffset: false,
61
69
  extraSubtitlesTextColor: false,
62
70
  extraSubtitlesBackgroundColor: false,
63
- extraSubtitlesShadowColor: false
71
+ extraSubtitlesOutlineColor: false
64
72
  };
65
73
 
66
74
  function onTransportError(error, message) {
@@ -111,10 +119,18 @@ function ChromecastSenderVideo(options) {
111
119
  onPropChanged('duration', null);
112
120
  onPropChanged('buffering', null);
113
121
  onPropChanged('buffered', null);
114
- onPropChanged('volume', null);
115
- onPropChanged('muted', null);
122
+ onPropChanged('audioTracks', []);
123
+ onPropChanged('selectedAudioTrackId', []);
116
124
  onPropChanged('subtitlesTracks', []);
117
125
  onPropChanged('selectedSubtitlesTrackId', null);
126
+ onPropChanged('subtitlesOffset', null);
127
+ onPropChanged('subtitlesSize', null);
128
+ onPropChanged('subtitlesTextColor', null);
129
+ onPropChanged('subtitlesBackgroundColor', null);
130
+ onPropChanged('subtitlesOutlineColor', null);
131
+ onPropChanged('volume', null);
132
+ onPropChanged('muted', null);
133
+ onPropChanged('playbackSpeed', null);
118
134
  onPropChanged('extraSubtitlesTracks', []);
119
135
  onPropChanged('selectedExtraSubtitlesTrackId', null);
120
136
  onPropChanged('extraSubtitlesDelay', null);
@@ -122,7 +138,7 @@ function ChromecastSenderVideo(options) {
122
138
  onPropChanged('extraSubtitlesOffset', null);
123
139
  onPropChanged('extraSubtitlesTextColor', null);
124
140
  onPropChanged('extraSubtitlesBackgroundColor', null);
125
- onPropChanged('extraSubtitlesShadowColor', null);
141
+ onPropChanged('extraSubtitlesOutlineColor', null);
126
142
  events.removeAllListeners();
127
143
  chromecastTransport.off('message', onMessage);
128
144
  containerElement.removeChild(deviceNameContainerElement);
@@ -179,9 +195,9 @@ ChromecastSenderVideo.canPlayStream = function() {
179
195
  ChromecastSenderVideo.manifest = {
180
196
  name: 'ChromecastSenderVideo',
181
197
  external: true,
182
- props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesShadowColor'],
198
+ props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'volume', 'muted', 'playbackSpeed', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesOutlineColor'],
183
199
  commands: ['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'],
184
- events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'extraSubtitlesTrackLoaded', 'implementationChanged']
200
+ events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded', 'extraSubtitlesTrackLoaded', 'implementationChanged']
185
201
  };
186
202
 
187
203
  module.exports = ChromecastSenderVideo;
@@ -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
 
@@ -57,6 +57,9 @@ function StremioVideo(options) {
57
57
  video.on('subtitlesTrackLoaded', function(track) {
58
58
  events.emit('subtitlesTrackLoaded', track);
59
59
  });
60
+ video.on('audioTrackLoaded', function(track) {
61
+ events.emit('audioTrackLoaded', track);
62
+ });
60
63
  video.on('extraSubtitlesTrackLoaded', function(track) {
61
64
  events.emit('extraSubtitlesTrackLoaded', track);
62
65
  });
@@ -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',
@@ -10,7 +10,7 @@ function srt2webvtt(data) {
10
10
  var result = '';
11
11
  if (cuelist.length > 0) {
12
12
  result += 'WEBVTT\n\n';
13
- for (var i = 0; i < cuelist.length; i=i+1) {
13
+ for (var i = 0; i < cuelist.length; i = i + 1) {
14
14
  result += convertSrtCue(cuelist[i]);
15
15
  }
16
16
  }
@@ -41,8 +41,8 @@ function convertSrtCue(caption) {
41
41
  // convert time string
42
42
  var m = s[1].match(/(\d+):(\d+):(\d+)(?:,(\d+))?\s*--?>\s*(\d+):(\d+):(\d+)(?:,(\d+))?/);
43
43
  if (m) {
44
- cue += m[1]+':'+m[2]+':'+m[3]+'.'+m[4]+' --> '
45
- +m[5]+':'+m[6]+':'+m[7]+'.'+m[8]+'\n';
44
+ cue += m[1] + ':' + m[2] + ':' + m[3] + '.' + m[4] + ' --> '
45
+ + m[5] + ':' + m[6] + ':' + m[7] + '.' + m[8] + '\n';
46
46
  line += 1;
47
47
  } else {
48
48
  // Unrecognized timestring
@@ -62,6 +62,14 @@ function convertSrtCue(caption) {
62
62
  module.exports = {
63
63
  convert: function(text) {
64
64
  // presume all to be SRT if not WEBVTT
65
- return text.includes('WEBVTT') ? text : srt2webvtt(text);
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
+ }
66
74
  }
67
75
  };
@@ -1,6 +1,7 @@
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');
@@ -49,9 +50,9 @@ function withHTMLSubtitles(Video) {
49
50
  var delay = null;
50
51
  var size = 100;
51
52
  var offset = 0;
52
- var textColor = '#FFFFFFFF';
53
- var backgroundColor = '#00000000';
54
- 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)';
55
56
  var observedProps = {
56
57
  extraSubtitlesTracks: false,
57
58
  selectedExtraSubtitlesTrackId: false,
@@ -60,7 +61,7 @@ function withHTMLSubtitles(Video) {
60
61
  extraSubtitlesOffset: false,
61
62
  extraSubtitlesTextColor: false,
62
63
  extraSubtitlesBackgroundColor: false,
63
- extraSubtitlesShadowColor: false
64
+ extraSubtitlesOutlineColor: false
64
65
  };
65
66
 
66
67
  function renderSubtitles() {
@@ -79,8 +80,9 @@ function withHTMLSubtitles(Video) {
79
80
  cueNode.style.fontSize = Math.floor(size / 25) + 'vmin';
80
81
  cueNode.style.color = textColor;
81
82
  cueNode.style.backgroundColor = backgroundColor;
82
- cueNode.style.textShadow = '1px 1px 0.1em ' + shadowColor;
83
- 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'));
84
86
  });
85
87
  }
86
88
  function onVideoError(error) {
@@ -168,12 +170,12 @@ function withHTMLSubtitles(Video) {
168
170
 
169
171
  return backgroundColor;
170
172
  }
171
- case 'extraSubtitlesShadowColor': {
173
+ case 'extraSubtitlesOutlineColor': {
172
174
  if (destroyed) {
173
175
  return null;
174
176
  }
175
177
 
176
- return shadowColor;
178
+ return outlineColor;
177
179
  }
178
180
  default: {
179
181
  return videoPropValue;
@@ -189,7 +191,7 @@ function withHTMLSubtitles(Video) {
189
191
  case 'extraSubtitlesOffset':
190
192
  case 'extraSubtitlesTextColor':
191
193
  case 'extraSubtitlesBackgroundColor':
192
- case 'extraSubtitlesShadowColor': {
194
+ case 'extraSubtitlesOutlineColor': {
193
195
  events.emit('propValue', propName, getProp(propName, null));
194
196
  observedProps[propName] = true;
195
197
  return true;
@@ -213,7 +215,11 @@ function withHTMLSubtitles(Video) {
213
215
  delay = 0;
214
216
  fetch(selectedTrack.url)
215
217
  .then(function(resp) {
216
- return resp.text();
218
+ if (resp.ok) {
219
+ return resp.text();
220
+ }
221
+
222
+ throw new Error(resp.status + ' (' + resp.statusText + ')');
217
223
  })
218
224
  .then(function(text) {
219
225
  return subtitlesConverter.convert(text);
@@ -276,7 +282,13 @@ function withHTMLSubtitles(Video) {
276
282
  }
277
283
  case 'extraSubtitlesTextColor': {
278
284
  if (typeof propValue === 'string') {
279
- 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
+
280
292
  renderSubtitles();
281
293
  onPropChanged('extraSubtitlesTextColor');
282
294
  }
@@ -285,18 +297,30 @@ function withHTMLSubtitles(Video) {
285
297
  }
286
298
  case 'extraSubtitlesBackgroundColor': {
287
299
  if (typeof propValue === 'string') {
288
- 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
+
289
307
  renderSubtitles();
290
308
  onPropChanged('extraSubtitlesBackgroundColor');
291
309
  }
292
310
 
293
311
  return true;
294
312
  }
295
- case 'extraSubtitlesShadowColor': {
313
+ case 'extraSubtitlesOutlineColor': {
296
314
  if (typeof propValue === 'string') {
297
- 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
+
298
322
  renderSubtitles();
299
- onPropChanged('extraSubtitlesShadowColor');
323
+ onPropChanged('extraSubtitlesOutlineColor');
300
324
  }
301
325
 
302
326
  return true;
@@ -361,7 +385,7 @@ function withHTMLSubtitles(Video) {
361
385
  onPropChanged('extraSubtitlesOffset');
362
386
  onPropChanged('extraSubtitlesTextColor');
363
387
  onPropChanged('extraSubtitlesBackgroundColor');
364
- onPropChanged('extraSubtitlesShadowColor');
388
+ onPropChanged('extraSubtitlesOutlineColor');
365
389
  video.dispatch({ type: 'command', commandName: 'destroy' });
366
390
  events.removeAllListeners();
367
391
  containerElement.removeChild(subtitlesElement);
@@ -423,7 +447,7 @@ function withHTMLSubtitles(Video) {
423
447
  VideoWithHTMLSubtitles.manifest = {
424
448
  name: Video.manifest.name + 'WithHTMLSubtitles',
425
449
  external: Video.manifest.external,
426
- 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'])
427
451
  .filter(function(value, index, array) { return array.indexOf(value) === index; }),
428
452
  commands: Video.manifest.commands.concat(['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'])
429
453
  .filter(function(value, index, array) { return array.indexOf(value) === index; }),
@@ -1,9 +1,22 @@
1
1
  var url = require('url');
2
2
  var magnet = require('magnet-uri');
3
- var inferTorrentFileIdx = require('./inferTorrentFileIdx');
3
+ var createTorrent = require('./createTorrent');
4
4
 
5
5
  function convertStream(streamingServerURL, stream, seriesInfo) {
6
6
  return new Promise(function(resolve, reject) {
7
+ var guessFileIdx = false;
8
+ if (stream.fileIdx === null || !isFinite(stream.fileIdx)) {
9
+ guessFileIdx = {};
10
+ if (seriesInfo) {
11
+ if (seriesInfo.season !== null && isFinite(seriesInfo.season)) {
12
+ guessFileIdx.season = seriesInfo.season;
13
+ }
14
+ if (seriesInfo.episode !== null && isFinite(seriesInfo.episode)) {
15
+ guessFileIdx.episode = seriesInfo.episode;
16
+ }
17
+ }
18
+ }
19
+
7
20
  if (typeof stream.url === 'string') {
8
21
  if (stream.url.indexOf('magnet:') === 0) {
9
22
  var parsedMagnetURI;
@@ -24,8 +37,9 @@ function convertStream(streamingServerURL, stream, seriesInfo) {
24
37
  })
25
38
  :
26
39
  [];
27
- inferTorrentFileIdx(streamingServerURL, parsedMagnetURI.infoHash, sources, seriesInfo)
28
- .then(function(fileIdx) {
40
+ createTorrent(streamingServerURL, parsedMagnetURI.infoHash, sources, guessFileIdx)
41
+ .then(function(resp) {
42
+ var fileIdx = guessFileIdx ? resp.guessedFileIdx : stream.fileIdx;
29
43
  resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(parsedMagnetURI.infoHash) + '/' + encodeURIComponent(fileIdx)));
30
44
  })
31
45
  .catch(function(error) {
@@ -39,17 +53,14 @@ function convertStream(streamingServerURL, stream, seriesInfo) {
39
53
  }
40
54
 
41
55
  if (typeof stream.infoHash === 'string') {
42
- if (stream.fileIdx !== null && isFinite(stream.fileIdx)) {
43
- resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(stream.infoHash) + '/' + encodeURIComponent(stream.fileIdx)));
44
- } else {
45
- inferTorrentFileIdx(streamingServerURL, stream.infoHash, stream.announce, seriesInfo)
46
- .then(function(fileIdx) {
47
- resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(stream.infoHash) + '/' + encodeURIComponent(fileIdx)));
48
- })
49
- .catch(function(error) {
50
- reject(error);
51
- });
52
- }
56
+ createTorrent(streamingServerURL, stream.infoHash, stream.announce, guessFileIdx)
57
+ .then(function(resp) {
58
+ var fileIdx = guessFileIdx ? resp.guessedFileIdx : stream.fileIdx;
59
+ resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(stream.infoHash) + '/' + encodeURIComponent(fileIdx)));
60
+ })
61
+ .catch(function(error) {
62
+ reject(error);
63
+ });
53
64
 
54
65
  return;
55
66
  }
@@ -0,0 +1,30 @@
1
+ var url = require('url');
2
+
3
+ function createTorrent(streamingServerURL, infoHash, sources, guessFileIdx) {
4
+ var body = {
5
+ torrent: {
6
+ infoHash: infoHash,
7
+ }
8
+ };
9
+ if (Array.isArray(sources) && sources.length > 0) {
10
+ body.torrent.peerSearch = {
11
+ sources: ['dht:' + infoHash].concat(sources),
12
+ min: 40,
13
+ max: 150
14
+ };
15
+ }
16
+ if (guessFileIdx) {
17
+ body.guessFileIdx = guessFileIdx;
18
+ }
19
+ return fetch(url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/create'), {
20
+ method: 'POST',
21
+ headers: {
22
+ 'content-type': 'application/json'
23
+ },
24
+ body: JSON.stringify(body)
25
+ }).then(function(resp) {
26
+ return resp.json();
27
+ });
28
+ }
29
+
30
+ module.exports = createTorrent;
@@ -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 {
@@ -1,63 +0,0 @@
1
- var url = require('url');
2
- var parseVideoName = require('video-name-parser');
3
-
4
- var MEDIA_FILE_EXTENTIONS = /.mkv$|.avi$|.mp4$|.wmv$|.vp8$|.mov$|.mpg$|.ts$|.m3u8$|.webm$|.flac$|.mp3$|.wav$|.wma$|.aac$|.ogg$/i;
5
-
6
- function inferTorrentFileIdx(streamingServerURL, infoHash, sources, seriesInfo) {
7
- return fetch(url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/create'), {
8
- method: 'POST',
9
- headers: {
10
- 'content-type': 'application/json'
11
- },
12
- body: JSON.stringify({
13
- torrent: {
14
- infoHash: infoHash,
15
- peerSearch: {
16
- sources: ['dht:' + infoHash].concat(Array.isArray(sources) ? sources : []),
17
- min: 40,
18
- max: 150
19
- }
20
- }
21
- })
22
- }).then(function(resp) {
23
- return resp.json();
24
- }).then(function(resp) {
25
- if (!resp || !Array.isArray(resp.files) || resp.files.some(function(file) { return !file || typeof file.path !== 'string' || file.length === null || !isFinite(file.length); })) {
26
- throw new Error('No files found in the torrent');
27
- }
28
-
29
- var mediaFiles = resp.files.filter(function(file) {
30
- return file.path.match(MEDIA_FILE_EXTENTIONS);
31
- });
32
- if (mediaFiles.length === 0) {
33
- throw new Error('No media files found in the torrent');
34
- }
35
-
36
- var mediaFilesForEpisode = seriesInfo ?
37
- mediaFiles.filter(function(file) {
38
- try {
39
- var info = parseVideoName(file.path);
40
- return info.season !== null &&
41
- isFinite(info.season) &&
42
- info.season === seriesInfo.season &&
43
- Array.isArray(info.episode) &&
44
- info.episode.indexOf(seriesInfo.episode) !== -1;
45
- } catch (e) {
46
- return false;
47
- }
48
- })
49
- :
50
- [];
51
- var selectedFile = (mediaFilesForEpisode.length > 0 ? mediaFilesForEpisode : mediaFiles)
52
- .reduce(function(result, file) {
53
- if (!result || file.length > result.length) {
54
- return file;
55
- }
56
-
57
- return result;
58
- }, null);
59
- return resp.files.indexOf(selectedFile);
60
- });
61
- }
62
-
63
- module.exports = inferTorrentFileIdx;