Skip to content

Commit b80b673

Browse files
committed
Merge master into feat/ass-subtitles
2 parents 9df3e61 + 0eb615d commit b80b673

8 files changed

Lines changed: 321 additions & 41 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stremio/stremio-video",
3-
"version": "0.0.77",
3+
"version": "0.0.85",
44
"description": "Abstraction layer on top of different media players",
55
"author": "Smart Code OOD",
66
"main": "src/index.js",

src/HTMLVideo/HTMLVideo.js

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ function HTMLVideo(options) {
9999
};
100100
containerElement.appendChild(videoElement);
101101

102+
function onFullscreenChanged() {
103+
onPropChanged('fullscreen');
104+
}
105+
videoElement.addEventListener('webkitbeginfullscreen', onFullscreenChanged);
106+
videoElement.addEventListener('webkitendfullscreen', onFullscreenChanged);
107+
videoElement.addEventListener('fullscreenchange', onFullscreenChanged);
108+
102109
var hls = null;
103110
var events = new EventEmitter();
104111
var destroyed = false;
@@ -125,7 +132,8 @@ function HTMLVideo(options) {
125132
volume: false,
126133
muted: false,
127134
playbackSpeed: false,
128-
videoScale: false
135+
videoScale: false,
136+
fullscreen: false
129137
};
130138

131139
function getProp(propName) {
@@ -255,39 +263,49 @@ function HTMLVideo(options) {
255263
return Math.round(subtitlesOpacity * 100);
256264
}
257265
case 'audioTracks': {
258-
if (hls === null || !Array.isArray(hls.audioTracks)) {
266+
if (hls === null || !Array.isArray(hls.allAudioTracks)) {
259267
return [];
260268
}
261269

262-
return hls.audioTracks
263-
.map(function(track) {
270+
return hls.allAudioTracks
271+
.map(function(track, index) {
264272
return Object.freeze({
265-
id: 'EMBEDDED_' + String(track.id),
273+
id: 'EMBEDDED_' + String(index),
266274
lang: typeof track.lang === 'string' && track.lang.length > 0 ?
267275
track.lang
268276
:
269277
typeof track.name === 'string' && track.name.length > 0 ?
270278
track.name
271279
:
272-
String(track.id),
280+
String(index),
273281
label: typeof track.name === 'string' && track.name.length > 0 ?
274282
track.name
275283
:
276284
typeof track.lang === 'string' && track.lang.length > 0 ?
277285
track.lang
278286
:
279-
String(track.id),
287+
String(index),
280288
origin: 'EMBEDDED',
281289
embedded: true
282290
});
283291
});
284292
}
285293
case 'selectedAudioTrackId': {
286-
if (hls === null || hls.audioTrack === null || !isFinite(hls.audioTrack) || hls.audioTrack === -1) {
294+
if (hls === null || hls.audioTrack === -1) {
287295
return null;
288296
}
289297

290-
return 'EMBEDDED_' + String(hls.audioTrack);
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);
291309
}
292310
case 'volume': {
293311
if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) {
@@ -313,6 +331,12 @@ function HTMLVideo(options) {
313331
case 'videoScale': {
314332
return videoElement.style.objectFit || 'contain';
315333
}
334+
case 'fullscreen': {
335+
if (stream === null) {
336+
return null;
337+
}
338+
return videoElement.webkitDisplayingFullscreen === true || document.fullscreenElement === videoElement;
339+
}
316340
default: {
317341
return null;
318342
}
@@ -491,14 +515,18 @@ function HTMLVideo(options) {
491515
}
492516
case 'selectedAudioTrackId': {
493517
if (hls !== null) {
494-
var selecterdAudioTrack = getProp('audioTracks')
518+
var selectedAudioTrack = getProp('audioTracks')
495519
.find(function(track) {
496520
return track.id === propValue;
497521
});
498-
hls.audioTrack = selecterdAudioTrack ? parseInt(selecterdAudioTrack.id.split('_').pop(), 10) : -1;
499-
if (selecterdAudioTrack) {
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+
}
500528
onPropChanged('selectedAudioTrackId');
501-
events.emit('audioTrackLoaded', selecterdAudioTrack);
529+
events.emit('audioTrackLoaded', selectedAudioTrack);
502530
}
503531
}
504532

@@ -536,6 +564,23 @@ function HTMLVideo(options) {
536564

537565
break;
538566
}
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+
}
539584
}
540585
}
541586
function command(commandName, commandArgs) {
@@ -557,6 +602,7 @@ function HTMLVideo(options) {
557602
onPropChanged('selectedSubtitlesTrackId');
558603
onPropChanged('audioTracks');
559604
onPropChanged('selectedAudioTrackId');
605+
onPropChanged('fullscreen');
560606
getContentType(stream)
561607
.then(function(contentType) {
562608
if (stream !== commandArgs.stream) {
@@ -573,6 +619,9 @@ function HTMLVideo(options) {
573619
onPropChanged('audioTracks');
574620
onPropChanged('selectedAudioTrackId');
575621
});
622+
hls.on(Hls.Events.MANIFEST_LOADING, function() {
623+
hls.subtitleTrack = -1;
624+
});
576625
hls.loadSource(stream.url);
577626
hls.attachMedia(videoElement);
578627
} else {
@@ -619,6 +668,7 @@ function HTMLVideo(options) {
619668
onPropChanged('selectedSubtitlesTrackId');
620669
onPropChanged('audioTracks');
621670
onPropChanged('selectedAudioTrackId');
671+
onPropChanged('fullscreen');
622672
break;
623673
}
624674
case 'destroy': {
@@ -651,6 +701,9 @@ function HTMLVideo(options) {
651701
videoElement.onvolumechange = null;
652702
videoElement.onratechange = null;
653703
videoElement.textTracks.onchange = null;
704+
videoElement.removeEventListener('webkitbeginfullscreen', onFullscreenChanged);
705+
videoElement.removeEventListener('webkitendfullscreen', onFullscreenChanged);
706+
videoElement.removeEventListener('fullscreenchange', onFullscreenChanged);
654707
containerElement.removeChild(videoElement);
655708
containerElement.removeChild(styleElement);
656709
break;
@@ -710,7 +763,7 @@ HTMLVideo.canPlayStream = function(stream) {
710763
HTMLVideo.manifest = {
711764
name: 'HTMLVideo',
712765
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'],
714767
commands: ['load', 'unload', 'destroy'],
715768
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
716769
};

src/ShellVideo/ShellVideo.js

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var deepFreeze = require('deep-freeze');
44
var ERROR = require('../error');
55

66
var SUBS_SCALE_FACTOR = 0.0066;
7+
var EOF_END_TOLERANCE = 60000;
78

89
var stremioToMPVProps = {
910
'loaded': 'loaded',
@@ -94,6 +95,7 @@ function ShellVideo(options) {
9495
var stream = null;
9596

9697
var avgDuration = 0;
98+
var durationReady = false;
9799
var minClipDuration = 30;
98100

99101
function setBackground(visible) {
@@ -109,6 +111,13 @@ function ShellVideo(options) {
109111
}
110112
}
111113
}
114+
function updateLoaded() {
115+
if (!props.loaded && durationReady && props['video-params'] && props['paused-for-cache'] === false) {
116+
props.loaded = true;
117+
setBackground(false);
118+
onPropChanged('loaded');
119+
}
120+
}
112121
function logProp(args) {
113122
// eslint-disable-next-line no-console
114123
console.log(args.name+': '+args.data);
@@ -142,11 +151,8 @@ function ShellVideo(options) {
142151
// for bitwise maths so the maximum supported video duration is 1073741823 (2 ^ 30 - 1)
143152
// which is around 34 years of playback time.
144153
avgDuration = avgDuration ? (avgDuration + intDuration) >> 1 : intDuration;
145-
props.loaded = intDuration > 0;
146-
if(props.loaded) {
147-
setBackground(false);
148-
onPropChanged('loaded');
149-
}
154+
durationReady = intDuration > 0;
155+
updateLoaded();
150156
break;
151157
}
152158
case 'time-pos': {
@@ -175,6 +181,10 @@ function ShellVideo(options) {
175181
case 'paused-for-cache':
176182
case 'seeking':
177183
{
184+
if (args.name === 'paused-for-cache') {
185+
props[args.name] = args.data;
186+
updateLoaded();
187+
}
178188
if(props.buffering !== args.data) {
179189
props.buffering = args.data;
180190
onPropChanged('buffering');
@@ -195,6 +205,7 @@ function ShellVideo(options) {
195205
}
196206
case 'video-params': {
197207
props[args.name] = args.data;
208+
updateLoaded();
198209
var params = args.data || {};
199210
var gamma = typeof params.gamma === 'string' ? params.gamma : null;
200211
if (gamma === 'pq' || gamma === 'hlg') {
@@ -257,8 +268,11 @@ function ShellVideo(options) {
257268
}
258269
});
259270
ipc.on('mpv-event-ended', function(args) {
271+
// older shells report 'other' for every non-error reason, including eof
260272
if (args.error) onError(args.error);
261-
else onEnded();
273+
else if (!args.reason || args.reason === 'eof' || args.reason === 'other') {
274+
if (!isKnownEarlyEof()) onEnded();
275+
}
262276
});
263277

264278
function getProp(propName) {
@@ -278,6 +292,15 @@ function ShellVideo(options) {
278292
function onEnded() {
279293
events.emit('ended');
280294
}
295+
function isKnownEarlyEof() {
296+
var time = props['time-pos'];
297+
var duration = props.duration;
298+
return typeof time === 'number' &&
299+
isFinite(time) &&
300+
typeof duration === 'number' &&
301+
isFinite(duration) &&
302+
time + EOF_END_TOLERANCE < duration;
303+
}
281304
function onPropChanged(propName) {
282305
if (observedProps[propName]) {
283306
events.emit('propChanged', propName, getProp(propName));
@@ -409,19 +432,22 @@ function ShellVideo(options) {
409432
var subAssOverride = commandArgs.assSubtitlesStyling ? 'strip' : 'no';
410433
ipc.send('mpv-set-prop', ['sub-ass-override', subAssOverride]);
411434

435+
var gpuProcessing = !!commandArgs.gpuVideoProcessing &&
436+
!!commandArgs.hardwareDecoding;
437+
412438
// Hardware decoding
413-
var hwdecValue = commandArgs.hardwareDecoding ? 'auto-copy' : 'no';
439+
var hwdecValue = commandArgs.hardwareDecoding ? (gpuProcessing ? 'd3d11va' : 'auto-copy') : 'no';
414440
ipc.send('mpv-set-prop', ['hwdec', hwdecValue]);
415441

416-
// On macOS the shell manages vo and HDR/EDR configuration
417-
// directly — do not override vo here.
418-
var platformLower = String(commandArgs.platform || '').toLowerCase();
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]);
442+
// GPU video processing
443+
if (typeof commandArgs.gpuVideoProcessing === 'boolean') {
444+
ipc.send('mpv-set-gpu-video-processing', gpuProcessing);
423445
}
424446

447+
// Video output
448+
var videoOutput = commandArgs.platform === 'windows' ? (commandArgs.videoMode === null ? 'gpu-next' : 'gpu') : 'libmpv';
449+
ipc.send('mpv-set-prop', ['vo', videoOutput]);
450+
425451
var separateWindow = options.mpvSeparateWindow ? 'yes' : 'no';
426452
ipc.send('mpv-set-prop', ['osc', separateWindow]);
427453
ipc.send('mpv-set-prop', ['input-default-bindings', separateWindow]);
@@ -437,6 +463,7 @@ function ShellVideo(options) {
437463
} else {
438464
ipc.send('mpv-command', ['loadfile', stream.url]);
439465
}
466+
ipc.send('mpv-set-prop', ['sid', 'no']);
440467
ipc.send('mpv-set-prop', ['pause', false]);
441468
ipc.send('mpv-set-prop', ['speed', props.speed]);
442469
if (props.aid) {
@@ -479,6 +506,7 @@ function ShellVideo(options) {
479506
sid: null,
480507
};
481508
avgDuration = 0;
509+
durationReady = false;
482510
ipc.send('mpv-command', ['stop']);
483511
onPropChanged('loaded');
484512
onPropChanged('stream');

src/WebOsVideo/WebOsVideo.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,9 @@ function WebOsVideo(options) {
352352
mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled',
353353
});
354354
});
355-
currentAudioTrack = 'EMBEDDED_0';
355+
if (!currentAudioTrack) {
356+
currentAudioTrack = 'EMBEDDED_0';
357+
}
356358
onPropChanged('audioTracks');
357359
onPropChanged('selectedAudioTrackId');
358360
}

0 commit comments

Comments
 (0)