@stremio/stremio-video 0.0.77 → 0.0.79
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 +67 -14
- package/src/ShellVideo/ShellVideo.js +3 -8
- package/src/WebOsVideo/WebOsVideo.js +3 -1
- package/src/withHTMLSubtitles/withHTMLSubtitles.js +94 -0
- package/src/withStreamingServer/convertStream.js +22 -2
- package/src/withStreamingServer/fetchVideoParams.js +47 -5
- package/src/withStreamingServer/withStreamingServer.js +11 -1
package/package.json
CHANGED
|
@@ -99,6 +99,13 @@ function HTMLVideo(options) {
|
|
|
99
99
|
};
|
|
100
100
|
containerElement.appendChild(videoElement);
|
|
101
101
|
|
|
102
|
+
function onFullscreenChanged() {
|
|
103
|
+
onPropChanged('fullscreen');
|
|
104
|
+
}
|
|
105
|
+
videoElement.addEventListener('webkitbeginfullscreen', onFullscreenChanged);
|
|
106
|
+
videoElement.addEventListener('webkitendfullscreen', onFullscreenChanged);
|
|
107
|
+
videoElement.addEventListener('fullscreenchange', onFullscreenChanged);
|
|
108
|
+
|
|
102
109
|
var hls = null;
|
|
103
110
|
var events = new EventEmitter();
|
|
104
111
|
var destroyed = false;
|
|
@@ -125,7 +132,8 @@ function HTMLVideo(options) {
|
|
|
125
132
|
volume: false,
|
|
126
133
|
muted: false,
|
|
127
134
|
playbackSpeed: false,
|
|
128
|
-
videoScale: false
|
|
135
|
+
videoScale: false,
|
|
136
|
+
fullscreen: false
|
|
129
137
|
};
|
|
130
138
|
|
|
131
139
|
function getProp(propName) {
|
|
@@ -255,39 +263,49 @@ function HTMLVideo(options) {
|
|
|
255
263
|
return Math.round(subtitlesOpacity * 100);
|
|
256
264
|
}
|
|
257
265
|
case 'audioTracks': {
|
|
258
|
-
if (hls === null || !Array.isArray(hls.
|
|
266
|
+
if (hls === null || !Array.isArray(hls.allAudioTracks)) {
|
|
259
267
|
return [];
|
|
260
268
|
}
|
|
261
269
|
|
|
262
|
-
return hls.
|
|
263
|
-
.map(function(track) {
|
|
270
|
+
return hls.allAudioTracks
|
|
271
|
+
.map(function(track, index) {
|
|
264
272
|
return Object.freeze({
|
|
265
|
-
id: 'EMBEDDED_' + String(
|
|
273
|
+
id: 'EMBEDDED_' + String(index),
|
|
266
274
|
lang: typeof track.lang === 'string' && track.lang.length > 0 ?
|
|
267
275
|
track.lang
|
|
268
276
|
:
|
|
269
277
|
typeof track.name === 'string' && track.name.length > 0 ?
|
|
270
278
|
track.name
|
|
271
279
|
:
|
|
272
|
-
String(
|
|
280
|
+
String(index),
|
|
273
281
|
label: typeof track.name === 'string' && track.name.length > 0 ?
|
|
274
282
|
track.name
|
|
275
283
|
:
|
|
276
284
|
typeof track.lang === 'string' && track.lang.length > 0 ?
|
|
277
285
|
track.lang
|
|
278
286
|
:
|
|
279
|
-
String(
|
|
287
|
+
String(index),
|
|
280
288
|
origin: 'EMBEDDED',
|
|
281
289
|
embedded: true
|
|
282
290
|
});
|
|
283
291
|
});
|
|
284
292
|
}
|
|
285
293
|
case 'selectedAudioTrackId': {
|
|
286
|
-
if (hls === null || hls.audioTrack ===
|
|
294
|
+
if (hls === null || hls.audioTrack === -1) {
|
|
287
295
|
return null;
|
|
288
296
|
}
|
|
289
297
|
|
|
290
|
-
|
|
298
|
+
var currentGroupTrack = hls.audioTracks[hls.audioTrack];
|
|
299
|
+
if (!currentGroupTrack) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
var allTracksIndex = hls.allAudioTracks.indexOf(currentGroupTrack);
|
|
304
|
+
if (allTracksIndex === -1) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return 'EMBEDDED_' + String(allTracksIndex);
|
|
291
309
|
}
|
|
292
310
|
case 'volume': {
|
|
293
311
|
if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) {
|
|
@@ -313,6 +331,12 @@ function HTMLVideo(options) {
|
|
|
313
331
|
case 'videoScale': {
|
|
314
332
|
return videoElement.style.objectFit || 'contain';
|
|
315
333
|
}
|
|
334
|
+
case 'fullscreen': {
|
|
335
|
+
if (stream === null) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
return videoElement.webkitDisplayingFullscreen === true || document.fullscreenElement === videoElement;
|
|
339
|
+
}
|
|
316
340
|
default: {
|
|
317
341
|
return null;
|
|
318
342
|
}
|
|
@@ -491,14 +515,18 @@ function HTMLVideo(options) {
|
|
|
491
515
|
}
|
|
492
516
|
case 'selectedAudioTrackId': {
|
|
493
517
|
if (hls !== null) {
|
|
494
|
-
var
|
|
518
|
+
var selectedAudioTrack = getProp('audioTracks')
|
|
495
519
|
.find(function(track) {
|
|
496
520
|
return track.id === propValue;
|
|
497
521
|
});
|
|
498
|
-
|
|
499
|
-
|
|
522
|
+
if (selectedAudioTrack) {
|
|
523
|
+
var trackIndex = parseInt(selectedAudioTrack.id.split('_').pop(), 10);
|
|
524
|
+
var allTracks = hls.allAudioTracks;
|
|
525
|
+
if (trackIndex >= 0 && trackIndex < allTracks.length) {
|
|
526
|
+
hls.setAudioOption(allTracks[trackIndex]);
|
|
527
|
+
}
|
|
500
528
|
onPropChanged('selectedAudioTrackId');
|
|
501
|
-
events.emit('audioTrackLoaded',
|
|
529
|
+
events.emit('audioTrackLoaded', selectedAudioTrack);
|
|
502
530
|
}
|
|
503
531
|
}
|
|
504
532
|
|
|
@@ -536,6 +564,23 @@ function HTMLVideo(options) {
|
|
|
536
564
|
|
|
537
565
|
break;
|
|
538
566
|
}
|
|
567
|
+
case 'fullscreen': {
|
|
568
|
+
if (stream === null) break;
|
|
569
|
+
if (propValue) {
|
|
570
|
+
if (typeof videoElement.webkitEnterFullscreen === 'function') {
|
|
571
|
+
videoElement.webkitEnterFullscreen();
|
|
572
|
+
} else if (typeof videoElement.requestFullscreen === 'function') {
|
|
573
|
+
videoElement.requestFullscreen();
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
576
|
+
if (typeof videoElement.webkitExitFullscreen === 'function' && videoElement.webkitDisplayingFullscreen) {
|
|
577
|
+
videoElement.webkitExitFullscreen();
|
|
578
|
+
} else if (document.fullscreenElement === videoElement) {
|
|
579
|
+
document.exitFullscreen();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
539
584
|
}
|
|
540
585
|
}
|
|
541
586
|
function command(commandName, commandArgs) {
|
|
@@ -557,6 +602,7 @@ function HTMLVideo(options) {
|
|
|
557
602
|
onPropChanged('selectedSubtitlesTrackId');
|
|
558
603
|
onPropChanged('audioTracks');
|
|
559
604
|
onPropChanged('selectedAudioTrackId');
|
|
605
|
+
onPropChanged('fullscreen');
|
|
560
606
|
getContentType(stream)
|
|
561
607
|
.then(function(contentType) {
|
|
562
608
|
if (stream !== commandArgs.stream) {
|
|
@@ -573,6 +619,9 @@ function HTMLVideo(options) {
|
|
|
573
619
|
onPropChanged('audioTracks');
|
|
574
620
|
onPropChanged('selectedAudioTrackId');
|
|
575
621
|
});
|
|
622
|
+
hls.on(Hls.Events.MANIFEST_LOADING, function() {
|
|
623
|
+
hls.subtitleTrack = -1;
|
|
624
|
+
});
|
|
576
625
|
hls.loadSource(stream.url);
|
|
577
626
|
hls.attachMedia(videoElement);
|
|
578
627
|
} else {
|
|
@@ -619,6 +668,7 @@ function HTMLVideo(options) {
|
|
|
619
668
|
onPropChanged('selectedSubtitlesTrackId');
|
|
620
669
|
onPropChanged('audioTracks');
|
|
621
670
|
onPropChanged('selectedAudioTrackId');
|
|
671
|
+
onPropChanged('fullscreen');
|
|
622
672
|
break;
|
|
623
673
|
}
|
|
624
674
|
case 'destroy': {
|
|
@@ -651,6 +701,9 @@ function HTMLVideo(options) {
|
|
|
651
701
|
videoElement.onvolumechange = null;
|
|
652
702
|
videoElement.onratechange = null;
|
|
653
703
|
videoElement.textTracks.onchange = null;
|
|
704
|
+
videoElement.removeEventListener('webkitbeginfullscreen', onFullscreenChanged);
|
|
705
|
+
videoElement.removeEventListener('webkitendfullscreen', onFullscreenChanged);
|
|
706
|
+
videoElement.removeEventListener('fullscreenchange', onFullscreenChanged);
|
|
654
707
|
containerElement.removeChild(videoElement);
|
|
655
708
|
containerElement.removeChild(styleElement);
|
|
656
709
|
break;
|
|
@@ -710,7 +763,7 @@ HTMLVideo.canPlayStream = function(stream) {
|
|
|
710
763
|
HTMLVideo.manifest = {
|
|
711
764
|
name: 'HTMLVideo',
|
|
712
765
|
external: false,
|
|
713
|
-
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'subtitlesOpacity', 'volume', 'muted', 'playbackSpeed', 'videoScale'],
|
|
766
|
+
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'subtitlesOpacity', 'volume', 'muted', 'playbackSpeed', 'videoScale', 'fullscreen'],
|
|
714
767
|
commands: ['load', 'unload', 'destroy'],
|
|
715
768
|
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
|
|
716
769
|
};
|
|
@@ -413,14 +413,9 @@ function ShellVideo(options) {
|
|
|
413
413
|
var hwdecValue = commandArgs.hardwareDecoding ? 'auto-copy' : 'no';
|
|
414
414
|
ipc.send('mpv-set-prop', ['hwdec', hwdecValue]);
|
|
415
415
|
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
var isMac = platformLower.indexOf('mac') !== -1;
|
|
420
|
-
if (!isMac) {
|
|
421
|
-
var videoOutput = platformLower === 'windows' ? (commandArgs.videoMode === null ? 'gpu-next' : 'gpu') : 'libmpv';
|
|
422
|
-
ipc.send('mpv-set-prop', ['vo', videoOutput]);
|
|
423
|
-
}
|
|
416
|
+
// Video output
|
|
417
|
+
var videoOutput = commandArgs.platform === 'windows' ? (commandArgs.videoMode === null ? 'gpu-next' : 'gpu') : 'libmpv';
|
|
418
|
+
ipc.send('mpv-set-prop', ['vo', videoOutput]);
|
|
424
419
|
|
|
425
420
|
var separateWindow = options.mpvSeparateWindow ? 'yes' : 'no';
|
|
426
421
|
ipc.send('mpv-set-prop', ['osc', separateWindow]);
|
|
@@ -352,7 +352,9 @@ function WebOsVideo(options) {
|
|
|
352
352
|
mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled',
|
|
353
353
|
});
|
|
354
354
|
});
|
|
355
|
-
currentAudioTrack
|
|
355
|
+
if (!currentAudioTrack) {
|
|
356
|
+
currentAudioTrack = 'EMBEDDED_0';
|
|
357
|
+
}
|
|
356
358
|
onPropChanged('audioTracks');
|
|
357
359
|
onPropChanged('selectedAudioTrackId');
|
|
358
360
|
}
|
|
@@ -40,6 +40,63 @@ function withHTMLSubtitles(Video) {
|
|
|
40
40
|
containerElement.style.zIndex = '0';
|
|
41
41
|
containerElement.appendChild(subtitlesElement);
|
|
42
42
|
|
|
43
|
+
var videoElement = containerElement.querySelector('video');
|
|
44
|
+
var nativeTextTrack = null;
|
|
45
|
+
var syntheticNativeTextTracks = [];
|
|
46
|
+
|
|
47
|
+
function createNativeTrack() {
|
|
48
|
+
removeNativeTrack();
|
|
49
|
+
if (cuesByTime === null || selectedTrackId === null) return false;
|
|
50
|
+
var selectedTrack = tracks.find(function(track) { return track.id === selectedTrackId; });
|
|
51
|
+
if (!selectedTrack) return false;
|
|
52
|
+
var delayMs = delay || 0;
|
|
53
|
+
nativeTextTrack = videoElement.addTextTrack('subtitles', selectedTrack.label || selectedTrack.lang, selectedTrack.lang || '');
|
|
54
|
+
syntheticNativeTextTracks.push(nativeTextTrack);
|
|
55
|
+
cuesByTime.times.forEach(function(time) {
|
|
56
|
+
cuesByTime[time].forEach(function(cue) {
|
|
57
|
+
if (cue.startTime !== time) return;
|
|
58
|
+
var start = (cue.startTime + delayMs) / 1000;
|
|
59
|
+
var end = (cue.endTime + delayMs) / 1000;
|
|
60
|
+
if (start < 0) start = 0;
|
|
61
|
+
if (end <= start) return;
|
|
62
|
+
nativeTextTrack.addCue(new VTTCue(start, end, cue.text));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
nativeTextTrack.mode = 'showing';
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function removeNativeTrack() {
|
|
69
|
+
if (nativeTextTrack !== null) {
|
|
70
|
+
nativeTextTrack.mode = 'disabled';
|
|
71
|
+
nativeTextTrack = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function isNativeTextTrack(track) {
|
|
75
|
+
return syntheticNativeTextTracks.includes(track);
|
|
76
|
+
}
|
|
77
|
+
function getEmbeddedTrackIndex(trackId) {
|
|
78
|
+
if (typeof trackId !== 'string' || !trackId.startsWith('EMBEDDED_')) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
var index = parseInt(trackId.replace('EMBEDDED_', ''), 10);
|
|
82
|
+
return isNaN(index) ? null : index;
|
|
83
|
+
}
|
|
84
|
+
function isWebkitDisplayingFullscreen() {
|
|
85
|
+
return videoElement && videoElement.webkitDisplayingFullscreen === true;
|
|
86
|
+
}
|
|
87
|
+
function onWebkitBeginFullscreen() {
|
|
88
|
+
createNativeTrack();
|
|
89
|
+
subtitlesElement.style.display = 'none';
|
|
90
|
+
}
|
|
91
|
+
function onWebkitEndFullscreen() {
|
|
92
|
+
removeNativeTrack();
|
|
93
|
+
subtitlesElement.style.display = '';
|
|
94
|
+
}
|
|
95
|
+
if (videoElement) {
|
|
96
|
+
videoElement.addEventListener('webkitbeginfullscreen', onWebkitBeginFullscreen);
|
|
97
|
+
videoElement.addEventListener('webkitendfullscreen', onWebkitEndFullscreen);
|
|
98
|
+
}
|
|
99
|
+
|
|
43
100
|
var videoState = {
|
|
44
101
|
time: null,
|
|
45
102
|
paused: false,
|
|
@@ -189,6 +246,10 @@ function withHTMLSubtitles(Video) {
|
|
|
189
246
|
}
|
|
190
247
|
|
|
191
248
|
events.emit(eventName, propName, getProp(propName, propValue));
|
|
249
|
+
|
|
250
|
+
if (propName === 'selectedSubtitlesTrackId' && propValue !== null && selectedTrackId !== null && nativeTextTrack === null) {
|
|
251
|
+
setProp('selectedExtraSubtitlesTrackId', null);
|
|
252
|
+
}
|
|
192
253
|
}
|
|
193
254
|
function onOtherVideoEvent(eventName) {
|
|
194
255
|
return function() {
|
|
@@ -272,6 +333,24 @@ function withHTMLSubtitles(Video) {
|
|
|
272
333
|
|
|
273
334
|
return opacity;
|
|
274
335
|
}
|
|
336
|
+
case 'subtitlesTracks': {
|
|
337
|
+
if (Array.isArray(videoPropValue) && videoElement && videoElement.textTracks) {
|
|
338
|
+
return videoPropValue.filter(function(track) {
|
|
339
|
+
var index = getEmbeddedTrackIndex(track.id);
|
|
340
|
+
return index === null || !isNativeTextTrack(videoElement.textTracks[index]);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return videoPropValue;
|
|
345
|
+
}
|
|
346
|
+
case 'selectedSubtitlesTrackId': {
|
|
347
|
+
if (typeof videoPropValue === 'string' && videoElement && videoElement.textTracks) {
|
|
348
|
+
var index = getEmbeddedTrackIndex(videoPropValue);
|
|
349
|
+
return index !== null && isNativeTextTrack(videoElement.textTracks[index]) ? null : videoPropValue;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return videoPropValue;
|
|
353
|
+
}
|
|
275
354
|
default: {
|
|
276
355
|
return videoPropValue;
|
|
277
356
|
}
|
|
@@ -300,6 +379,13 @@ function withHTMLSubtitles(Video) {
|
|
|
300
379
|
function setProp(propName, propValue) {
|
|
301
380
|
switch (propName) {
|
|
302
381
|
case 'selectedExtraSubtitlesTrackId': {
|
|
382
|
+
if (propValue !== null) {
|
|
383
|
+
video.dispatch({
|
|
384
|
+
type: 'setProp',
|
|
385
|
+
propName: 'selectedSubtitlesTrackId',
|
|
386
|
+
propValue: null,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
303
389
|
if (propValue !== null && selectedTrackId === propValue) {
|
|
304
390
|
return true;
|
|
305
391
|
}
|
|
@@ -358,6 +444,9 @@ function withHTMLSubtitles(Video) {
|
|
|
358
444
|
|
|
359
445
|
cuesByTime = result;
|
|
360
446
|
startRenderLoop();
|
|
447
|
+
if (isWebkitDisplayingFullscreen() && nativeTextTrack === null) {
|
|
448
|
+
createNativeTrack();
|
|
449
|
+
}
|
|
361
450
|
events.emit('extraSubtitlesTrackLoaded', selectedTrack);
|
|
362
451
|
})
|
|
363
452
|
.catch(function(error) {
|
|
@@ -545,6 +634,7 @@ function withHTMLSubtitles(Video) {
|
|
|
545
634
|
return false;
|
|
546
635
|
}
|
|
547
636
|
case 'unload': {
|
|
637
|
+
removeNativeTrack();
|
|
548
638
|
stopRenderLoop();
|
|
549
639
|
lastTimeIndex = null;
|
|
550
640
|
cuesByTime = null;
|
|
@@ -560,6 +650,10 @@ function withHTMLSubtitles(Video) {
|
|
|
560
650
|
case 'destroy': {
|
|
561
651
|
command('unload');
|
|
562
652
|
destroyed = true;
|
|
653
|
+
if (videoElement) {
|
|
654
|
+
videoElement.removeEventListener('webkitbeginfullscreen', onWebkitBeginFullscreen);
|
|
655
|
+
videoElement.removeEventListener('webkitendfullscreen', onWebkitEndFullscreen);
|
|
656
|
+
}
|
|
563
657
|
onPropChanged('extraSubtitlesSize');
|
|
564
658
|
onPropChanged('extraSubtitlesOffset');
|
|
565
659
|
onPropChanged('extraSubtitlesTextColor');
|
|
@@ -46,13 +46,33 @@ function convertStream(streamingServerURL, stream, seriesInfo, streamingServerSe
|
|
|
46
46
|
} else {
|
|
47
47
|
var proxyStreamsEnabled = streamingServerSettings && streamingServerSettings.proxyStreamsEnabled;
|
|
48
48
|
var proxyHeaders = stream.behaviorHints && stream.behaviorHints.proxyHeaders;
|
|
49
|
+
var resolved;
|
|
49
50
|
if (proxyStreamsEnabled || proxyHeaders) {
|
|
50
51
|
var requestHeaders = proxyHeaders && proxyHeaders.request ? proxyHeaders.request : {};
|
|
51
52
|
var responseHeaders = proxyHeaders && proxyHeaders.response ? proxyHeaders.response : {};
|
|
52
|
-
|
|
53
|
+
resolved = { url: buildProxyUrl(streamingServerURL, stream.url, requestHeaders, responseHeaders) };
|
|
53
54
|
} else {
|
|
54
|
-
|
|
55
|
+
resolved = { url: stream.url };
|
|
55
56
|
}
|
|
57
|
+
// Propagate infoHash/fileIdx so fetchFilename can hit stats.json
|
|
58
|
+
// instead of leaking the URL fragment as the filename.
|
|
59
|
+
if (typeof stream.infoHash === 'string' && stream.infoHash.length > 0) {
|
|
60
|
+
resolved.infoHash = stream.infoHash.toLowerCase();
|
|
61
|
+
if (stream.fileIdx !== null && stream.fileIdx !== undefined && isFinite(stream.fileIdx)) {
|
|
62
|
+
resolved.fileIdx = stream.fileIdx;
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// Fallback for addons shipping pre-computed streaming-server URLs.
|
|
66
|
+
try {
|
|
67
|
+
var parsed = new URL(stream.url);
|
|
68
|
+
var parts = parsed.pathname.split('/').filter(Boolean);
|
|
69
|
+
if (parts.length === 2 && /^[a-f0-9]{40}$/i.test(parts[0]) && /^-?\d+$/.test(parts[1])) {
|
|
70
|
+
resolved.infoHash = parts[0].toLowerCase();
|
|
71
|
+
resolved.fileIdx = parseInt(parts[1], 10);
|
|
72
|
+
}
|
|
73
|
+
} catch (_e) { /* unparsable URL */ }
|
|
74
|
+
}
|
|
75
|
+
resolve(resolved);
|
|
56
76
|
}
|
|
57
77
|
|
|
58
78
|
return;
|
|
@@ -46,7 +46,30 @@ function fetchFilename(streamingServerURL, mediaURL, infoHash, fileIdx, behavior
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (infoHash) {
|
|
49
|
-
|
|
49
|
+
// fileIdx -1 means auto-pick; /-1/stats.json has no streamName,
|
|
50
|
+
// so resolve via engine guessedFileIdx instead.
|
|
51
|
+
var fileIdxNum = typeof fileIdx === 'string' ? parseInt(fileIdx, 10) : fileIdx;
|
|
52
|
+
var hasSpecificFileIdx = fileIdxNum !== null && fileIdxNum !== -1 && isFinite(fileIdxNum);
|
|
53
|
+
|
|
54
|
+
if (hasSpecificFileIdx) {
|
|
55
|
+
return fetch(url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/' + encodeURIComponent(fileIdx) + '/stats.json'))
|
|
56
|
+
.then(function(resp) {
|
|
57
|
+
if (resp.ok) {
|
|
58
|
+
return resp.json();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
62
|
+
})
|
|
63
|
+
.then(function(resp) {
|
|
64
|
+
if (!resp || typeof resp.streamName !== 'string') {
|
|
65
|
+
throw new Error('Could not retrieve filename from torrent');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return resp.streamName;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return fetch(url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/stats.json'))
|
|
50
73
|
.then(function(resp) {
|
|
51
74
|
if (resp.ok) {
|
|
52
75
|
return resp.json();
|
|
@@ -54,12 +77,31 @@ function fetchFilename(streamingServerURL, mediaURL, infoHash, fileIdx, behavior
|
|
|
54
77
|
|
|
55
78
|
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
56
79
|
})
|
|
57
|
-
.then(function(
|
|
58
|
-
if (!
|
|
59
|
-
throw new Error('Could not retrieve
|
|
80
|
+
.then(function(stats) {
|
|
81
|
+
if (!stats || !Array.isArray(stats.files)) {
|
|
82
|
+
throw new Error('Could not retrieve file list from torrent');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prefer guessedFileIdx — the file the engine is streaming.
|
|
86
|
+
var guessed = typeof stats.guessedFileIdx === 'number' ? stats.files[stats.guessedFileIdx] : null;
|
|
87
|
+
if (guessed && typeof guessed.name === 'string') {
|
|
88
|
+
return guessed.name;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback: largest video file (mirrors server's GuessFileIdx for movies).
|
|
92
|
+
var videoExt = /\.(mp4|mkv|avi|mov|wmv|flv|webm|m4v|mpg|mpeg|ts|m2ts)$/i;
|
|
93
|
+
var pool = stats.files.filter(function(f) { return f && typeof f.name === 'string' && videoExt.test(f.name); });
|
|
94
|
+
if (pool.length === 0) {
|
|
95
|
+
pool = stats.files.filter(function(f) { return f && typeof f.name === 'string'; });
|
|
96
|
+
}
|
|
97
|
+
var largest = pool.reduce(function(best, f) {
|
|
98
|
+
return (!best || (f.length || 0) > (best.length || 0)) ? f : best;
|
|
99
|
+
}, null);
|
|
100
|
+
if (largest && typeof largest.name === 'string') {
|
|
101
|
+
return largest.name;
|
|
60
102
|
}
|
|
61
103
|
|
|
62
|
-
|
|
104
|
+
throw new Error('Could not retrieve filename from torrent');
|
|
63
105
|
});
|
|
64
106
|
}
|
|
65
107
|
|
|
@@ -365,6 +365,7 @@ function withStreamingServer(Video) {
|
|
|
365
365
|
var isFormatSupported = options.formats.some(function(format) {
|
|
366
366
|
return probe.format.name.indexOf(format) !== -1;
|
|
367
367
|
});
|
|
368
|
+
|
|
368
369
|
var areStreamsSupported = probe.streams.every(function(stream) {
|
|
369
370
|
if (stream.track === 'audio') {
|
|
370
371
|
return stream.channels <= options.maxAudioChannels &&
|
|
@@ -375,7 +376,16 @@ function withStreamingServer(Video) {
|
|
|
375
376
|
|
|
376
377
|
return true;
|
|
377
378
|
});
|
|
378
|
-
|
|
379
|
+
var hasEmbeddedSubtitles = probe.streams.some(function(stream) {
|
|
380
|
+
return stream.track === 'subtitle';
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// HTML5 video doesn't support multiple audio tracks, so we can't switch languages
|
|
384
|
+
var supportedAudioTracks = probe.streams.filter(function(stream) {
|
|
385
|
+
return stream.track === 'audio' && options.audioCodecs.indexOf(stream.codec) !== -1;
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return isFormatSupported && areStreamsSupported && !hasEmbeddedSubtitles && supportedAudioTracks.length < 2;
|
|
379
389
|
})
|
|
380
390
|
.catch(function() {
|
|
381
391
|
// this uses content-type header in HTMLVideo which
|