From 47926e46b26b2b7bb192cde126f8d43326acbeb8 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 8 Jan 2025 13:20:39 +0200 Subject: [PATCH 01/25] Titan Player --- src/StremioVideo/selectVideoImplementation.js | 7 + src/TitanVideo/TitanVideo.js | 642 ++++++++++++++++++ src/TitanVideo/index.js | 3 + 3 files changed, 652 insertions(+) create mode 100644 src/TitanVideo/TitanVideo.js create mode 100644 src/TitanVideo/index.js diff --git a/src/StremioVideo/selectVideoImplementation.js b/src/StremioVideo/selectVideoImplementation.js index 33ced33..094ef6d 100644 --- a/src/StremioVideo/selectVideoImplementation.js +++ b/src/StremioVideo/selectVideoImplementation.js @@ -2,6 +2,7 @@ var ChromecastSenderVideo = require('../ChromecastSenderVideo'); var ShellVideo = require('../ShellVideo'); var HTMLVideo = require('../HTMLVideo'); var TizenVideo = require('../TizenVideo'); +var TitanVideo = require('../TitanVideo'); var WebOsVideo = require('../WebOsVideo'); var IFrameVideo = require('../IFrameVideo'); var YouTubeVideo = require('../YouTubeVideo'); @@ -37,6 +38,9 @@ function selectVideoImplementation(commandArgs, options) { if (commandArgs.platform === 'webOS') { return withStreamingServer(withHTMLSubtitles(WebOsVideo)); } + if (window.Titanos) { + return withStreamingServer(withHTMLSubtitles(TitanVideo)); + } return withStreamingServer(withHTMLSubtitles(HTMLVideo)); } @@ -47,6 +51,9 @@ function selectVideoImplementation(commandArgs, options) { if (commandArgs.platform === 'webOS') { return withVideoParams(withHTMLSubtitles(WebOsVideo)); } + if (window.Titanos) { + return withVideoParams(withHTMLSubtitles(TitanVideo)); + } return withVideoParams(withHTMLSubtitles(HTMLVideo)); } diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js new file mode 100644 index 0000000..c74822f --- /dev/null +++ b/src/TitanVideo/TitanVideo.js @@ -0,0 +1,642 @@ +var EventEmitter = require('eventemitter3'); +var cloneDeep = require('lodash.clonedeep'); +var deepFreeze = require('deep-freeze'); +var Color = require('color'); +var ERROR = require('../error'); +var HLS_CONFIG = require('./hlsConfig'); + +function TitanVideo(options) { + options = options || {}; + + var containerElement = options.containerElement; + if (!(containerElement instanceof HTMLElement)) { + throw new Error('Container element required to be instance of HTMLElement'); + } + + var styleElement = document.createElement('style'); + containerElement.appendChild(styleElement); + 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; }'); + var videoElement = document.createElement('video'); + videoElement.style.width = '100%'; + videoElement.style.height = '100%'; + videoElement.style.backgroundColor = 'black'; + videoElement.controls = false; + videoElement.playsInline = true; + videoElement.onerror = function() { + onVideoError(); + }; + videoElement.onended = function() { + onEnded(); + }; + videoElement.onpause = function() { + onPropChanged('paused'); + }; + videoElement.onplay = function() { + onPropChanged('paused'); + }; + videoElement.ontimeupdate = function() { + onPropChanged('time'); + onPropChanged('buffered'); + }; + videoElement.ondurationchange = function() { + onPropChanged('duration'); + }; + videoElement.onwaiting = function() { + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.onseeking = function() { + onPropChanged('time'); + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.onseeked = function() { + onPropChanged('time'); + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.onstalled = function() { + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.onplaying = function() { + onPropChanged('time'); + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.oncanplay = function() { + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.canplaythrough = function() { + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.onloadedmetadata = function() { + onPropChanged('loaded'); + }; + videoElement.onloadeddata = function() { + onPropChanged('buffering'); + onPropChanged('buffered'); + }; + videoElement.onvolumechange = function() { + onPropChanged('volume'); + onPropChanged('muted'); + }; + videoElement.onratechange = function() { + onPropChanged('playbackSpeed'); + }; + videoElement.textTracks.onchange = function() { + onPropChanged('subtitlesTracks'); + onPropChanged('selectedSubtitlesTrackId'); + onCueChange(); + Array.from(videoElement.textTracks).forEach(function(track) { + track.oncuechange = onCueChange; + }); + }; + containerElement.appendChild(videoElement); + + var hls = null; + var events = new EventEmitter(); + var destroyed = false; + var stream = null; + var subtitlesOffset = 0; + var observedProps = { + stream: false, + loaded: false, + paused: false, + time: false, + duration: false, + buffering: false, + buffered: false, + subtitlesTracks: false, + selectedSubtitlesTrackId: false, + subtitlesOffset: false, + subtitlesSize: false, + subtitlesTextColor: false, + subtitlesBackgroundColor: false, + subtitlesOutlineColor: false, + audioTracks: false, + selectedAudioTrackId: false, + volume: false, + muted: false, + playbackSpeed: false + }; + + function getProp(propName) { + switch (propName) { + case 'stream': { + return stream; + } + case 'loaded': { + if (stream === null) { + return null; + } + + return videoElement.readyState >= videoElement.HAVE_METADATA; + } + case 'paused': { + if (stream === null) { + return null; + } + + return !!videoElement.paused; + } + case 'time': { + if (stream === null || videoElement.currentTime === null || !isFinite(videoElement.currentTime)) { + return null; + } + + return Math.floor(videoElement.currentTime * 1000); + } + case 'duration': { + if (stream === null || videoElement.duration === null || !isFinite(videoElement.duration)) { + return null; + } + + return Math.floor(videoElement.duration * 1000); + } + case 'buffering': { + if (stream === null) { + return null; + } + + return videoElement.readyState < videoElement.HAVE_FUTURE_DATA; + } + case 'buffered': { + if (stream === null) { + return null; + } + + var time = videoElement.currentTime !== null && isFinite(videoElement.currentTime) ? videoElement.currentTime : 0; + for (var i = 0; i < videoElement.buffered.length; i++) { + if (videoElement.buffered.start(i) <= time && time <= videoElement.buffered.end(i)) { + return Math.floor(videoElement.buffered.end(i) * 1000); + } + } + + return Math.floor(time * 1000); + } + case 'subtitlesTracks': { + if (stream === null) { + return []; + } + + return Array.from(videoElement.textTracks) + .map(function(track, index) { + return Object.freeze({ + id: 'EMBEDDED_' + String(index), + lang: track.language, + label: track.label, + origin: 'EMBEDDED', + embedded: true + }); + }); + } + case 'selectedSubtitlesTrackId': { + if (stream === null) { + return null; + } + + return Array.from(videoElement.textTracks) + .reduce(function(result, track, index) { + if (result === null && track.mode === 'showing') { + return 'EMBEDDED_' + String(index); + } + + return result; + }, null); + } + case 'subtitlesOffset': { + if (destroyed) { + return null; + } + + return subtitlesOffset; + } + case 'subtitlesSize': { + if (destroyed) { + return null; + } + + return parseInt(styleElement.sheet.cssRules[0].style.fontSize, 10) * 25; + } + case 'subtitlesTextColor': { + if (destroyed) { + return null; + } + + return styleElement.sheet.cssRules[0].style.color; + } + case 'subtitlesBackgroundColor': { + if (destroyed) { + return null; + } + + return styleElement.sheet.cssRules[0].style.backgroundColor; + } + case 'subtitlesOutlineColor': { + if (destroyed) { + return null; + } + + return styleElement.sheet.cssRules[0].style.textShadow.slice(0, styleElement.sheet.cssRules[0].style.textShadow.indexOf(')') + 1); + } + case 'audioTracks': { + if (hls === null || !Array.isArray(hls.audioTracks)) { + return []; + } + + return hls.audioTracks + .map(function(track) { + return Object.freeze({ + id: 'EMBEDDED_' + String(track.id), + lang: typeof track.lang === 'string' && track.lang.length > 0 ? + track.lang + : + typeof track.name === 'string' && track.name.length > 0 ? + track.name + : + String(track.id), + label: typeof track.name === 'string' && track.name.length > 0 ? + track.name + : + typeof track.lang === 'string' && track.lang.length > 0 ? + track.lang + : + String(track.id), + origin: 'EMBEDDED', + embedded: true + }); + }); + } + case 'selectedAudioTrackId': { + if (hls === null || hls.audioTrack === null || !isFinite(hls.audioTrack) || hls.audioTrack === -1) { + return null; + } + + return 'EMBEDDED_' + String(hls.audioTrack); + } + case 'volume': { + if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) { + return null; + } + + return Math.floor(videoElement.volume * 100); + } + case 'muted': { + if (destroyed) { + return null; + } + + return !!videoElement.muted; + } + case 'playbackSpeed': { + if (destroyed || videoElement.playbackRate === null || !isFinite(videoElement.playbackRate)) { + return null; + } + + return videoElement.playbackRate; + } + default: { + return null; + } + } + } + function onCueChange() { + Array.from(videoElement.textTracks).forEach(function(track) { + Array.from(track.cues || []).forEach(function(cue) { + cue.snapToLines = false; + cue.line = 100 - subtitlesOffset; + }); + }); + } + function onVideoError() { + if (destroyed) { + return; + } + + var error; + switch (videoElement.error.code) { + case 1: { + error = ERROR.HTML_VIDEO.MEDIA_ERR_ABORTED; + break; + } + case 2: { + error = ERROR.HTML_VIDEO.MEDIA_ERR_NETWORK; + break; + } + case 3: { + error = ERROR.HTML_VIDEO.MEDIA_ERR_DECODE; + break; + } + case 4: { + error = ERROR.HTML_VIDEO.MEDIA_ERR_SRC_NOT_SUPPORTED; + break; + } + default: { + error = ERROR.UNKNOWN_ERROR; + } + } + onError(Object.assign({}, error, { + critical: true, + error: videoElement.error + })); + } + function onError(error) { + events.emit('error', error); + if (error.critical) { + command('unload'); + } + } + function onEnded() { + events.emit('ended'); + } + function onPropChanged(propName) { + if (observedProps[propName]) { + events.emit('propChanged', propName, getProp(propName)); + } + } + function observeProp(propName) { + if (observedProps.hasOwnProperty(propName)) { + events.emit('propValue', propName, getProp(propName)); + observedProps[propName] = true; + } + } + function setProp(propName, propValue) { + switch (propName) { + case 'paused': { + if (stream !== null) { + propValue ? videoElement.pause() : videoElement.play(); + onPropChanged('paused'); + } + + break; + } + case 'time': { + if (stream !== null && propValue !== null && isFinite(propValue)) { + videoElement.currentTime = parseInt(propValue, 10) / 1000; + onPropChanged('time'); + } + + break; + } + case 'selectedSubtitlesTrackId': { + if (stream !== null) { + Array.from(videoElement.textTracks) + .forEach(function(track, index) { + track.mode = 'EMBEDDED_' + String(index) === propValue ? 'showing' : 'disabled'; + }); + var selecterdSubtitlesTrack = getProp('subtitlesTracks') + .find(function(track) { + return track.id === propValue; + }); + if (selecterdSubtitlesTrack) { + onPropChanged('selectedSubtitlesTrackId'); + events.emit('subtitlesTrackLoaded', selecterdSubtitlesTrack); + } + } + + break; + } + case 'subtitlesOffset': { + if (propValue !== null && isFinite(propValue)) { + subtitlesOffset = Math.max(0, Math.min(100, parseInt(propValue, 10))); + onCueChange(); + onPropChanged('subtitlesOffset'); + } + + break; + } + case 'subtitlesSize': { + if (propValue !== null && isFinite(propValue)) { + styleElement.sheet.cssRules[0].style.fontSize = Math.floor(Math.max(0, parseInt(propValue, 10)) / 25) + 'vmin'; + onPropChanged('subtitlesSize'); + } + + break; + } + case 'subtitlesTextColor': { + if (typeof propValue === 'string') { + try { + styleElement.sheet.cssRules[0].style.color = Color(propValue).rgb().string(); + } catch (error) { + // eslint-disable-next-line no-console + console.error('TitanVideo', error); + } + + onPropChanged('subtitlesTextColor'); + } + + break; + } + case 'subtitlesBackgroundColor': { + if (typeof propValue === 'string') { + try { + styleElement.sheet.cssRules[0].style.backgroundColor = Color(propValue).rgb().string(); + } catch (error) { + // eslint-disable-next-line no-console + console.error('TitanVideo', error); + } + + onPropChanged('subtitlesBackgroundColor'); + } + + break; + } + case 'subtitlesOutlineColor': { + if (typeof propValue === 'string') { + try { + styleElement.sheet.cssRules[0].style.textShadow = Color(propValue).rgb().string() + ' 1px 1px 0.1em'; + } catch (error) { + // eslint-disable-next-line no-console + console.error('TitanVideo', error); + } + + onPropChanged('subtitlesOutlineColor'); + } + + break; + } + case 'selectedAudioTrackId': { + if (hls !== null) { + var selecterdAudioTrack = getProp('audioTracks') + .find(function(track) { + return track.id === propValue; + }); + hls.audioTrack = selecterdAudioTrack ? parseInt(selecterdAudioTrack.id.split('_').pop(), 10) : -1; + if (selecterdAudioTrack) { + onPropChanged('selectedAudioTrackId'); + events.emit('audioTrackLoaded', selecterdAudioTrack); + } + } + + break; + } + case 'volume': { + if (propValue !== null && isFinite(propValue)) { + videoElement.muted = false; + videoElement.volume = Math.max(0, Math.min(100, parseInt(propValue, 10))) / 100; + onPropChanged('muted'); + onPropChanged('volume'); + } + + break; + } + case 'muted': { + videoElement.muted = !!propValue; + onPropChanged('muted'); + break; + } + case 'playbackSpeed': { + if (propValue !== null && isFinite(propValue)) { + videoElement.playbackRate = parseFloat(propValue); + onPropChanged('playbackSpeed'); + } + + break; + } + } + } + function command(commandName, commandArgs) { + switch (commandName) { + case 'load': { + command('unload'); + if (commandArgs && commandArgs.stream && typeof commandArgs.stream.url === 'string') { + stream = commandArgs.stream; + onPropChanged('stream'); + onPropChanged('loaded'); + videoElement.autoplay = typeof commandArgs.autoplay === 'boolean' ? commandArgs.autoplay : true; + videoElement.currentTime = commandArgs.time !== null && isFinite(commandArgs.time) ? parseInt(commandArgs.time, 10) / 1000 : 0; + onPropChanged('paused'); + onPropChanged('time'); + onPropChanged('duration'); + onPropChanged('buffering'); + onPropChanged('buffered'); + onPropChanged('subtitlesTracks'); + onPropChanged('selectedSubtitlesTrackId'); + onPropChanged('audioTracks'); + onPropChanged('selectedAudioTrackId'); + videoElement.src = stream.url; + } else { + onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, { + critical: true, + stream: commandArgs ? commandArgs.stream : null + })); + } + break; + } + case 'unload': { + stream = null; + Array.from(videoElement.textTracks).forEach(function(track) { + track.oncuechange = null; + }); + if (hls !== null) { + hls.removeAllListeners(); + hls.detachMedia(videoElement); + hls.destroy(); + hls = null; + } + videoElement.removeAttribute('src'); + videoElement.load(); + videoElement.currentTime = 0; + onPropChanged('stream'); + onPropChanged('loaded'); + onPropChanged('paused'); + onPropChanged('time'); + onPropChanged('duration'); + onPropChanged('buffering'); + onPropChanged('buffered'); + onPropChanged('subtitlesTracks'); + onPropChanged('selectedSubtitlesTrackId'); + onPropChanged('audioTracks'); + onPropChanged('selectedAudioTrackId'); + break; + } + case 'destroy': { + command('unload'); + destroyed = true; + onPropChanged('subtitlesOffset'); + onPropChanged('subtitlesSize'); + onPropChanged('subtitlesTextColor'); + onPropChanged('subtitlesBackgroundColor'); + onPropChanged('subtitlesOutlineColor'); + onPropChanged('volume'); + onPropChanged('muted'); + onPropChanged('playbackSpeed'); + events.removeAllListeners(); + videoElement.onerror = null; + videoElement.onended = null; + videoElement.onpause = null; + videoElement.onplay = null; + videoElement.ontimeupdate = null; + videoElement.ondurationchange = null; + videoElement.onwaiting = null; + videoElement.onseeking = null; + videoElement.onseeked = null; + videoElement.onstalled = null; + videoElement.onplaying = null; + videoElement.oncanplay = null; + videoElement.canplaythrough = null; + videoElement.onloadeddata = null; + videoElement.onvolumechange = null; + videoElement.onratechange = null; + videoElement.textTracks.onchange = null; + containerElement.removeChild(videoElement); + containerElement.removeChild(styleElement); + break; + } + } + } + + this.on = function(eventName, listener) { + if (destroyed) { + throw new Error('Video is destroyed'); + } + + events.on(eventName, listener); + }; + this.dispatch = function(action) { + if (destroyed) { + throw new Error('Video is destroyed'); + } + + if (action) { + action = deepFreeze(cloneDeep(action)); + switch (action.type) { + case 'observeProp': { + observeProp(action.propName); + return; + } + case 'setProp': { + setProp(action.propName, action.propValue); + return; + } + case 'command': { + command(action.commandName, action.commandArgs); + return; + } + } + } + + throw new Error('Invalid action dispatched: ' + JSON.stringify(action)); + }; +} + +TitanVideo.canPlayStream = function(stream) { + if (!stream) { + return Promise.resolve(false); + } + + return Promise.resolve(true); +}; + +TitanVideo.manifest = { + name: 'TitanVideo', + external: false, + props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'volume', 'muted', 'playbackSpeed'], + commands: ['load', 'unload', 'destroy'], + events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded'] +}; + +module.exports = TitanVideo; diff --git a/src/TitanVideo/index.js b/src/TitanVideo/index.js new file mode 100644 index 0000000..5e6282e --- /dev/null +++ b/src/TitanVideo/index.js @@ -0,0 +1,3 @@ +var TitanVideo = require('./TitanVideo'); + +module.exports = TitanVideo; From 29251b566287fa0dd1e10dafab31b58372ee2bfe Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 8 Jan 2025 13:27:26 +0200 Subject: [PATCH 02/25] Fix Titan Player --- src/TitanVideo/TitanVideo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index c74822f..0cadb5a 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -3,7 +3,6 @@ var cloneDeep = require('lodash.clonedeep'); var deepFreeze = require('deep-freeze'); var Color = require('color'); var ERROR = require('../error'); -var HLS_CONFIG = require('./hlsConfig'); function TitanVideo(options) { options = options || {}; From 7943710dd10433e20a41a69aad9ae4bdb6b28d51 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Thu, 9 Jan 2025 10:08:25 +0200 Subject: [PATCH 03/25] Handle Text Tracks and Audio Tracks --- src/TitanVideo/TitanVideo.js | 82 ++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index 0cadb5a..19d0a4d 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -95,7 +95,6 @@ function TitanVideo(options) { }; containerElement.appendChild(videoElement); - var hls = null; var events = new EventEmitter(); var destroyed = false; var stream = null; @@ -181,12 +180,19 @@ function TitanVideo(options) { return []; } + if (!videoElement.textTracks || !Array.from(videoElement.textTracks).length) { + return []; + } + return Array.from(videoElement.textTracks) + .filter(function(track) { + return track.kind === 'subtitles' + }) .map(function(track, index) { return Object.freeze({ id: 'EMBEDDED_' + String(index), lang: track.language, - label: track.label, + label: track.label || track.language, origin: 'EMBEDDED', embedded: true }); @@ -197,6 +203,10 @@ function TitanVideo(options) { return null; } + if (!videoElement.textTracks || !Array.from(videoElement.textTracks).length) { + return null; + } + return Array.from(videoElement.textTracks) .reduce(function(result, track, index) { if (result === null && track.mode === 'showing') { @@ -242,39 +252,43 @@ function TitanVideo(options) { return styleElement.sheet.cssRules[0].style.textShadow.slice(0, styleElement.sheet.cssRules[0].style.textShadow.indexOf(')') + 1); } case 'audioTracks': { - if (hls === null || !Array.isArray(hls.audioTracks)) { + if (stream === null) { return []; } - return hls.audioTracks - .map(function(track) { + if (!videoElement.audioTracks || !Array.from(videoElement.audioTracks).length) { + return []; + } + + return Array.from(videoElement.audioTracks) + .map(function(track, index) { return Object.freeze({ - id: 'EMBEDDED_' + String(track.id), - lang: typeof track.lang === 'string' && track.lang.length > 0 ? - track.lang - : - typeof track.name === 'string' && track.name.length > 0 ? - track.name - : - String(track.id), - label: typeof track.name === 'string' && track.name.length > 0 ? - track.name - : - typeof track.lang === 'string' && track.lang.length > 0 ? - track.lang - : - String(track.id), + id: 'EMBEDDED_' + String(index), + lang: track.language, + label: track.label || track.language, origin: 'EMBEDDED', embedded: true }); }); } case 'selectedAudioTrackId': { - if (hls === null || hls.audioTrack === null || !isFinite(hls.audioTrack) || hls.audioTrack === -1) { + + if (stream === null) { return null; } - return 'EMBEDDED_' + String(hls.audioTrack); + if (!videoElement.audioTracks || !Array.from(videoElement.audioTracks).length) { + return null; + } + + return Array.from(videoElement.audioTracks) + .reduce(function(result, track, index) { + if (result === null && track.enabled) { + return 'EMBEDDED_' + String(index); + } + + return result; + }, null); } case 'volume': { if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) { @@ -386,13 +400,13 @@ function TitanVideo(options) { .forEach(function(track, index) { track.mode = 'EMBEDDED_' + String(index) === propValue ? 'showing' : 'disabled'; }); - var selecterdSubtitlesTrack = getProp('subtitlesTracks') + var selectedSubtitlesTrack = getProp('subtitlesTracks') .find(function(track) { return track.id === propValue; }); - if (selecterdSubtitlesTrack) { + if (selectedSubtitlesTrack) { onPropChanged('selectedSubtitlesTrackId'); - events.emit('subtitlesTrackLoaded', selecterdSubtitlesTrack); + events.emit('subtitlesTrackLoaded', selectedSubtitlesTrack); } } @@ -458,15 +472,9 @@ function TitanVideo(options) { break; } case 'selectedAudioTrackId': { - if (hls !== null) { - var selecterdAudioTrack = getProp('audioTracks') - .find(function(track) { - return track.id === propValue; - }); - hls.audioTrack = selecterdAudioTrack ? parseInt(selecterdAudioTrack.id.split('_').pop(), 10) : -1; - if (selecterdAudioTrack) { - onPropChanged('selectedAudioTrackId'); - events.emit('audioTrackLoaded', selecterdAudioTrack); + if (stream !== null) { + for (var index = 0; index < videoElement.audioTracks.length; index++) { + videoElement.audioTracks[i].enabled = !!('EMBEDDED_' + String(index) === propValue) } } @@ -530,12 +538,6 @@ function TitanVideo(options) { Array.from(videoElement.textTracks).forEach(function(track) { track.oncuechange = null; }); - if (hls !== null) { - hls.removeAllListeners(); - hls.detachMedia(videoElement); - hls.destroy(); - hls = null; - } videoElement.removeAttribute('src'); videoElement.load(); videoElement.currentTime = 0; From c638bba049ad5d8257ce78d2f615cb4fcf472945 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Thu, 9 Jan 2025 10:12:45 +0200 Subject: [PATCH 04/25] Lint --- src/TitanVideo/TitanVideo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index 19d0a4d..7ad2e2f 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -186,7 +186,7 @@ function TitanVideo(options) { return Array.from(videoElement.textTracks) .filter(function(track) { - return track.kind === 'subtitles' + return track.kind === 'subtitles'; }) .map(function(track, index) { return Object.freeze({ @@ -474,7 +474,7 @@ function TitanVideo(options) { case 'selectedAudioTrackId': { if (stream !== null) { for (var index = 0; index < videoElement.audioTracks.length; index++) { - videoElement.audioTracks[i].enabled = !!('EMBEDDED_' + String(index) === propValue) + videoElement.audioTracks[index].enabled = !!('EMBEDDED_' + String(index) === propValue); } } From ab4488ead3e1f9c6f88d262818cfa8965f01c1d0 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Thu, 9 Jan 2025 10:54:39 +0200 Subject: [PATCH 05/25] Fallback to Converting SRT to VTT Locally --- src/withHTMLSubtitles/withHTMLSubtitles.js | 76 ++++++++++--------- .../withStreamingServer.js | 2 + 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/withHTMLSubtitles/withHTMLSubtitles.js b/src/withHTMLSubtitles/withHTMLSubtitles.js index 8b715b5..aa834fe 100644 --- a/src/withHTMLSubtitles/withHTMLSubtitles.js +++ b/src/withHTMLSubtitles/withHTMLSubtitles.js @@ -225,40 +225,48 @@ function withHTMLSubtitles(Video) { if (selectedTrack) { selectedTrackId = selectedTrack.id; delay = 0; - fetch(selectedTrack.url) - .then(function(resp) { - if (resp.ok) { - return resp.text(); - } - - throw new Error(resp.status + ' (' + resp.statusText + ')'); - }) - .then(function(text) { - return subtitlesConverter.convert(text); - }) - .then(function(text) { - return subtitlesParser.parse(text); - }) - .then(function(result) { - if (selectedTrackId !== selectedTrack.id) { - return; - } - - cuesByTime = result; - renderSubtitles(); - events.emit('extraSubtitlesTrackLoaded', selectedTrack); - }) - .catch(function(error) { - if (selectedTrackId !== selectedTrack.id) { - return; - } - - onError(Object.assign({}, ERROR.WITH_HTML_SUBTITLES.LOAD_FAILED, { - error: error, - track: selectedTrack, - critical: false - })); - }); + function loadSubtitleFromUrl(url, isFallback) { + fetch(url) + .then(function(resp) { + if (resp.ok) { + return resp.text(); + } + + throw new Error(resp.status + ' (' + resp.statusText + ')'); + }) + .then(function(text) { + return subtitlesConverter.convert(text); + }) + .then(function(text) { + return subtitlesParser.parse(text); + }) + .then(function(result) { + if (selectedTrackId !== selectedTrack.id) { + return; + } + + cuesByTime = result; + renderSubtitles(); + events.emit('extraSubtitlesTrackLoaded', selectedTrack); + }) + .catch(function(error) { + if (selectedTrackId !== selectedTrack.id) { + return; + } + + if (!isFallback && typeof selectedTrack.fallbackUrl === 'string') { + loadSubtitleFromUrl(selectedTrack.fallbackUrl, true) + return; + } + + onError(Object.assign({}, ERROR.WITH_HTML_SUBTITLES.LOAD_FAILED, { + error: error, + track: selectedTrack, + critical: false + })); + }); + } + loadSubtitleFromUrl(selectedTrack.url) } renderSubtitles(); onPropChanged('selectedExtraSubtitlesTrackId'); diff --git a/src/withStreamingServer/withStreamingServer.js b/src/withStreamingServer/withStreamingServer.js index dd61b06..4a63dab 100644 --- a/src/withStreamingServer/withStreamingServer.js +++ b/src/withStreamingServer/withStreamingServer.js @@ -173,6 +173,8 @@ function withStreamingServer(Video) { subtitles: Array.isArray(commandArgs.stream.subtitles) ? commandArgs.stream.subtitles.map(function(track) { return Object.assign({}, track, { + // fallback is used in case server conversion fails (if server is offline) + fallbackUrl: track.url, url: typeof track.url === 'string' ? url.resolve(commandArgs.streamingServerURL, '/subtitles.vtt?' + new URLSearchParams([['from', track.url]]).toString()) : From 3a51927662b403e70ad84cf9d0bc97641769e8b4 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Thu, 9 Jan 2025 10:55:05 +0200 Subject: [PATCH 06/25] Rely on `commandArgs.platform` to Detect Titan --- src/StremioVideo/selectVideoImplementation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StremioVideo/selectVideoImplementation.js b/src/StremioVideo/selectVideoImplementation.js index 094ef6d..2478590 100644 --- a/src/StremioVideo/selectVideoImplementation.js +++ b/src/StremioVideo/selectVideoImplementation.js @@ -38,7 +38,7 @@ function selectVideoImplementation(commandArgs, options) { if (commandArgs.platform === 'webOS') { return withStreamingServer(withHTMLSubtitles(WebOsVideo)); } - if (window.Titanos) { + if (commandArgs.platform === 'TitanOS') { return withStreamingServer(withHTMLSubtitles(TitanVideo)); } return withStreamingServer(withHTMLSubtitles(HTMLVideo)); @@ -51,7 +51,7 @@ function selectVideoImplementation(commandArgs, options) { if (commandArgs.platform === 'webOS') { return withVideoParams(withHTMLSubtitles(WebOsVideo)); } - if (window.Titanos) { + if (commandArgs.platform === 'TitanOS') { return withVideoParams(withHTMLSubtitles(TitanVideo)); } return withVideoParams(withHTMLSubtitles(HTMLVideo)); From e19130c5f5ebbd3865594319922ebc0dcc5566eb Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Thu, 9 Jan 2025 10:56:54 +0200 Subject: [PATCH 07/25] Lint --- src/withHTMLSubtitles/withHTMLSubtitles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/withHTMLSubtitles/withHTMLSubtitles.js b/src/withHTMLSubtitles/withHTMLSubtitles.js index aa834fe..9711f4d 100644 --- a/src/withHTMLSubtitles/withHTMLSubtitles.js +++ b/src/withHTMLSubtitles/withHTMLSubtitles.js @@ -255,7 +255,7 @@ function withHTMLSubtitles(Video) { } if (!isFallback && typeof selectedTrack.fallbackUrl === 'string') { - loadSubtitleFromUrl(selectedTrack.fallbackUrl, true) + loadSubtitleFromUrl(selectedTrack.fallbackUrl, true); return; } @@ -266,7 +266,7 @@ function withHTMLSubtitles(Video) { })); }); } - loadSubtitleFromUrl(selectedTrack.url) + loadSubtitleFromUrl(selectedTrack.url); } renderSubtitles(); onPropChanged('selectedExtraSubtitlesTrackId'); From 42004e7dd503a3bcfba6cd4753f7e05396d2666c Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 14 Jan 2025 14:35:58 +0200 Subject: [PATCH 08/25] Fix Detecting Tizen 5 --- src/TizenVideo/TizenVideo.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TizenVideo/TizenVideo.js b/src/TizenVideo/TizenVideo.js index 5947902..c231b91 100644 --- a/src/TizenVideo/TizenVideo.js +++ b/src/TizenVideo/TizenVideo.js @@ -631,11 +631,11 @@ function TizenVideo(options) { } onPropChanged('buffering'); - var tizenVersion = false; + var TIZEN_MATCHES = navigator.userAgent.match(/Tizen (\d+\.\d+)/i) - try { - tizenVersion = parseFloat(global.tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version')); - } catch(e) {} + if (TIZEN_MATCHES && TIZEN_MATCHES[1]) { + tizenVersion = parseFloat(TIZEN_MATCHES[1]) + } if (!tizenVersion || tizenVersion >= 6) { retrieveExtendedTracks(); From 677efd170301dc20e842646364e5e521d8f0ca4c Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 14 Jan 2025 14:42:59 +0200 Subject: [PATCH 09/25] Use Window Instead of Global --- src/supportsTranscoding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supportsTranscoding.js b/src/supportsTranscoding.js index 0a6f8a1..690fc4a 100644 --- a/src/supportsTranscoding.js +++ b/src/supportsTranscoding.js @@ -1,5 +1,5 @@ function supportsTranscoding() { - if (typeof global.tizen !== 'undefined' || typeof global.webOS !== 'undefined' || typeof window.qt !== 'undefined') { + if (typeof window.tizen !== 'undefined' || typeof window.webOS !== 'undefined' || typeof window.qt !== 'undefined') { return Promise.resolve(false); } return Promise.resolve(true); From c045e43a56c9ece134b25f286285a76e37a1ae40 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 14 Jan 2025 14:55:09 +0200 Subject: [PATCH 10/25] Lint --- src/TizenVideo/TizenVideo.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/TizenVideo/TizenVideo.js b/src/TizenVideo/TizenVideo.js index c231b91..21b0b78 100644 --- a/src/TizenVideo/TizenVideo.js +++ b/src/TizenVideo/TizenVideo.js @@ -631,10 +631,12 @@ function TizenVideo(options) { } onPropChanged('buffering'); - var TIZEN_MATCHES = navigator.userAgent.match(/Tizen (\d+\.\d+)/i) + var tizenVersion = false; + + var TIZEN_MATCHES = navigator.userAgent.match(/Tizen (\d+\.\d+)/i); if (TIZEN_MATCHES && TIZEN_MATCHES[1]) { - tizenVersion = parseFloat(TIZEN_MATCHES[1]) + tizenVersion = parseFloat(TIZEN_MATCHES[1]); } if (!tizenVersion || tizenVersion >= 6) { From c123054b5a2a519609dd030721c22cec79b69e36 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 14 Jan 2025 16:04:19 +0200 Subject: [PATCH 11/25] Fix Detecting Platform --- src/StremioVideo/StremioVideo.js | 4 ++++ src/platform.js | 6 ++++++ src/supportsTranscoding.js | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/platform.js diff --git a/src/StremioVideo/StremioVideo.js b/src/StremioVideo/StremioVideo.js index 9428325..246fe5a 100644 --- a/src/StremioVideo/StremioVideo.js +++ b/src/StremioVideo/StremioVideo.js @@ -2,6 +2,7 @@ var EventEmitter = require('eventemitter3'); var cloneDeep = require('lodash.clonedeep'); var deepFreeze = require('deep-freeze'); var selectVideoImplementation = require('./selectVideoImplementation'); +var platform = require('../platform'); var ERROR = require('../error'); function StremioVideo() { @@ -25,6 +26,9 @@ function StremioVideo() { action = deepFreeze(cloneDeep(action)); options = options || {}; if (action.type === 'command' && action.commandName === 'load' && action.commandArgs) { + if (action.commandArgs.platform) { + platform.set(action.commandArgs.platform); + } var Video = selectVideoImplementation(action.commandArgs, options); if (video !== null && video.constructor !== Video) { video.dispatch({ type: 'command', commandName: 'destroy' }); diff --git a/src/platform.js b/src/platform.js new file mode 100644 index 0000000..37d2425 --- /dev/null +++ b/src/platform.js @@ -0,0 +1,6 @@ +var platform = null; + +module.exports = { + set: function(val) { platform = val; }, + get: function() { return platform; } +}; diff --git a/src/supportsTranscoding.js b/src/supportsTranscoding.js index 690fc4a..3970174 100644 --- a/src/supportsTranscoding.js +++ b/src/supportsTranscoding.js @@ -1,5 +1,7 @@ +var platform = require('./platform'); + function supportsTranscoding() { - if (typeof window.tizen !== 'undefined' || typeof window.webOS !== 'undefined' || typeof window.qt !== 'undefined') { + if (['Tizen', 'webOS'].includes(platform.get()) || typeof window.qt !== 'undefined') { return Promise.resolve(false); } return Promise.resolve(true); From 7677390ce302e032fc5e059a0cc7c584ae543437 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 14 Jan 2025 17:01:21 +0200 Subject: [PATCH 12/25] v0.0.49 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c062767..5ed1306 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stremio/stremio-video", - "version": "0.0.48", + "version": "0.0.49", "description": "Abstraction layer on top of different media players", "author": "Smart Code OOD", "main": "src/index.js", From 94b4eab64dd34c5969b05830070a8960b1ac30b0 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 12:04:43 +0200 Subject: [PATCH 13/25] Check for Both Titan and NetTV --- src/StremioVideo/selectVideoImplementation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StremioVideo/selectVideoImplementation.js b/src/StremioVideo/selectVideoImplementation.js index 2478590..09ad141 100644 --- a/src/StremioVideo/selectVideoImplementation.js +++ b/src/StremioVideo/selectVideoImplementation.js @@ -38,7 +38,7 @@ function selectVideoImplementation(commandArgs, options) { if (commandArgs.platform === 'webOS') { return withStreamingServer(withHTMLSubtitles(WebOsVideo)); } - if (commandArgs.platform === 'TitanOS') { + if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') { return withStreamingServer(withHTMLSubtitles(TitanVideo)); } return withStreamingServer(withHTMLSubtitles(HTMLVideo)); @@ -51,7 +51,7 @@ function selectVideoImplementation(commandArgs, options) { if (commandArgs.platform === 'webOS') { return withVideoParams(withHTMLSubtitles(WebOsVideo)); } - if (commandArgs.platform === 'TitanOS') { + if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') { return withVideoParams(withHTMLSubtitles(TitanVideo)); } return withVideoParams(withHTMLSubtitles(HTMLVideo)); From 54cd8eaa485d2a79a5daafff6bc78cf576e87473 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 12:32:29 +0200 Subject: [PATCH 14/25] Add Debug Logs --- src/StremioVideo/selectVideoImplementation.js | 5 +++++ src/withHTMLSubtitles/withHTMLSubtitles.js | 1 + 2 files changed, 6 insertions(+) diff --git a/src/StremioVideo/selectVideoImplementation.js b/src/StremioVideo/selectVideoImplementation.js index 09ad141..22b8bea 100644 --- a/src/StremioVideo/selectVideoImplementation.js +++ b/src/StremioVideo/selectVideoImplementation.js @@ -11,6 +11,9 @@ var withHTMLSubtitles = require('../withHTMLSubtitles'); var withVideoParams = require('../withVideoParams'); function selectVideoImplementation(commandArgs, options) { + + console.log('selectVideoImplementation platform', commandArgs.platform); + if (!commandArgs.stream || typeof commandArgs.stream.externalUrl === 'string') { return null; } @@ -39,6 +42,7 @@ function selectVideoImplementation(commandArgs, options) { return withStreamingServer(withHTMLSubtitles(WebOsVideo)); } if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') { + console.log('selectVideoImplementation using Titan Player with server'); return withStreamingServer(withHTMLSubtitles(TitanVideo)); } return withStreamingServer(withHTMLSubtitles(HTMLVideo)); @@ -52,6 +56,7 @@ function selectVideoImplementation(commandArgs, options) { return withVideoParams(withHTMLSubtitles(WebOsVideo)); } if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') { + console.log('selectVideoImplementation using Titan Player'); return withVideoParams(withHTMLSubtitles(TitanVideo)); } return withVideoParams(withHTMLSubtitles(HTMLVideo)); diff --git a/src/withHTMLSubtitles/withHTMLSubtitles.js b/src/withHTMLSubtitles/withHTMLSubtitles.js index 9711f4d..9bdf4a0 100644 --- a/src/withHTMLSubtitles/withHTMLSubtitles.js +++ b/src/withHTMLSubtitles/withHTMLSubtitles.js @@ -266,6 +266,7 @@ function withHTMLSubtitles(Video) { })); }); } + console.log('selectedTrack', selectedTrack); loadSubtitleFromUrl(selectedTrack.url); } renderSubtitles(); From 20f733e9b168c676198615c70c66073c07f5ae0f Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 12:35:22 +0200 Subject: [PATCH 15/25] Fix Fallback URL for Subtitles --- src/withStreamingServer/withStreamingServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/withStreamingServer/withStreamingServer.js b/src/withStreamingServer/withStreamingServer.js index 4a63dab..9cfedbe 100644 --- a/src/withStreamingServer/withStreamingServer.js +++ b/src/withStreamingServer/withStreamingServer.js @@ -173,8 +173,6 @@ function withStreamingServer(Video) { subtitles: Array.isArray(commandArgs.stream.subtitles) ? commandArgs.stream.subtitles.map(function(track) { return Object.assign({}, track, { - // fallback is used in case server conversion fails (if server is offline) - fallbackUrl: track.url, url: typeof track.url === 'string' ? url.resolve(commandArgs.streamingServerURL, '/subtitles.vtt?' + new URLSearchParams([['from', track.url]]).toString()) : @@ -261,6 +259,8 @@ function withStreamingServer(Video) { commandArgs: Object.assign({}, commandArgs, { tracks: commandArgs.tracks.map(function(track) { return Object.assign({}, track, { + // fallback is used in case server conversion fails (if server is offline) + fallbackUrl: track.url, url: typeof track.url === 'string' ? url.resolve(loadArgs.streamingServerURL, '/subtitles.vtt?' + new URLSearchParams([['from', track.url]]).toString()) : From 2176457ad18d65fe113bb498b6256e68ed7cd40f Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 12:39:32 +0200 Subject: [PATCH 16/25] More Debug Logs --- src/TitanVideo/TitanVideo.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index 7ad2e2f..495a8af 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -176,11 +176,18 @@ function TitanVideo(options) { return Math.floor(time * 1000); } case 'subtitlesTracks': { + console.log('subtitlesTracks 1') if (stream === null) { + console.log('subtitlesTracks 2') return []; } + console.log('subtitlesTracks 3') + console.log(videoElement.textTracks) + console.log(typeof videoElement.textTracks) + if (!videoElement.textTracks || !Array.from(videoElement.textTracks).length) { + console.log('subtitlesTracks 4') return []; } @@ -252,11 +259,17 @@ function TitanVideo(options) { return styleElement.sheet.cssRules[0].style.textShadow.slice(0, styleElement.sheet.cssRules[0].style.textShadow.indexOf(')') + 1); } case 'audioTracks': { + console.log('audioTracks 1') if (stream === null) { + console.log('audioTracks 2') return []; } + console.log('audioTracks 3') + console.log(videoElement.audioTracks) + console.log(typeof videoElement.audioTracks) if (!videoElement.audioTracks || !Array.from(videoElement.audioTracks).length) { + console.log('audioTracks 4') return []; } From 799e9686f2ba370d2d6008d663372523399a849b Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 12:46:59 +0200 Subject: [PATCH 17/25] Fix Subs / Audio Tracks --- src/TitanVideo/TitanVideo.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index 495a8af..84394b3 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -58,7 +58,15 @@ function TitanVideo(options) { onPropChanged('buffering'); onPropChanged('buffered'); }; + var loadedSubsAudio = false; videoElement.onplaying = function() { + if (!loadedSubsAudio) { + loadedSubsAudio = true; + onPropChanged('subtitlesTracks'); + onPropChanged('selectedSubtitlesTrackId'); + onPropChanged('audioTracks'); + onPropChanged('selectedAudioTrackId'); + } onPropChanged('time'); onPropChanged('buffering'); onPropChanged('buffered'); @@ -533,10 +541,6 @@ function TitanVideo(options) { onPropChanged('duration'); onPropChanged('buffering'); onPropChanged('buffered'); - onPropChanged('subtitlesTracks'); - onPropChanged('selectedSubtitlesTrackId'); - onPropChanged('audioTracks'); - onPropChanged('selectedAudioTrackId'); videoElement.src = stream.url; } else { onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, { From 410137725681530afe3be42583f32943246641a1 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 13:03:53 +0200 Subject: [PATCH 18/25] Fixes for Subs / Audio --- src/TitanVideo/TitanVideo.js | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index 84394b3..e054ca9 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -58,15 +58,7 @@ function TitanVideo(options) { onPropChanged('buffering'); onPropChanged('buffered'); }; - var loadedSubsAudio = false; videoElement.onplaying = function() { - if (!loadedSubsAudio) { - loadedSubsAudio = true; - onPropChanged('subtitlesTracks'); - onPropChanged('selectedSubtitlesTrackId'); - onPropChanged('audioTracks'); - onPropChanged('selectedAudioTrackId'); - } onPropChanged('time'); onPropChanged('buffering'); onPropChanged('buffered'); @@ -499,6 +491,16 @@ function TitanVideo(options) { } } + var selectedAudioTrack = getProp('audioTracks') + .find(function(track) { + return track.id === propValue; + }); + + if (selectedAudioTrack) { + onPropChanged('selectedAudioTrackId'); + events.emit('audioTrackLoaded', selectedAudioTrack); + } + break; } case 'volume': { @@ -541,6 +543,24 @@ function TitanVideo(options) { onPropChanged('duration'); onPropChanged('buffering'); onPropChanged('buffered'); + if (videoElement.textTracks) { + videoElement.textTracks.onaddtrack = function() { + videoElement.textTracks.onaddtrack = null; + setTimeout(function() { + onPropChanged('subtitlesTracks'); + onPropChanged('selectedSubtitlesTrackId'); + }); + } + } + if (videoElement.audioTracks) { + videoElement.audioTracks.onaddtrack = function() { + videoElement.audioTracks.onaddtrack = null; + setTimeout(function() { + onPropChanged('audioTracks'); + onPropChanged('selectedAudioTrackId'); + }); + } + } videoElement.src = stream.url; } else { onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, { From e2f22535fc21091fd70ffe518658ba11c47776a0 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 13:25:13 +0200 Subject: [PATCH 19/25] Remove Debug Logs --- src/StremioVideo/selectVideoImplementation.js | 4 ---- src/TitanVideo/TitanVideo.js | 13 ------------- 2 files changed, 17 deletions(-) diff --git a/src/StremioVideo/selectVideoImplementation.js b/src/StremioVideo/selectVideoImplementation.js index 22b8bea..d5ef8af 100644 --- a/src/StremioVideo/selectVideoImplementation.js +++ b/src/StremioVideo/selectVideoImplementation.js @@ -11,9 +11,6 @@ var withHTMLSubtitles = require('../withHTMLSubtitles'); var withVideoParams = require('../withVideoParams'); function selectVideoImplementation(commandArgs, options) { - - console.log('selectVideoImplementation platform', commandArgs.platform); - if (!commandArgs.stream || typeof commandArgs.stream.externalUrl === 'string') { return null; } @@ -56,7 +53,6 @@ function selectVideoImplementation(commandArgs, options) { return withVideoParams(withHTMLSubtitles(WebOsVideo)); } if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') { - console.log('selectVideoImplementation using Titan Player'); return withVideoParams(withHTMLSubtitles(TitanVideo)); } return withVideoParams(withHTMLSubtitles(HTMLVideo)); diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index e054ca9..cdb7d07 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -176,18 +176,11 @@ function TitanVideo(options) { return Math.floor(time * 1000); } case 'subtitlesTracks': { - console.log('subtitlesTracks 1') if (stream === null) { - console.log('subtitlesTracks 2') return []; } - console.log('subtitlesTracks 3') - console.log(videoElement.textTracks) - console.log(typeof videoElement.textTracks) - if (!videoElement.textTracks || !Array.from(videoElement.textTracks).length) { - console.log('subtitlesTracks 4') return []; } @@ -259,17 +252,11 @@ function TitanVideo(options) { return styleElement.sheet.cssRules[0].style.textShadow.slice(0, styleElement.sheet.cssRules[0].style.textShadow.indexOf(')') + 1); } case 'audioTracks': { - console.log('audioTracks 1') if (stream === null) { - console.log('audioTracks 2') return []; } - console.log('audioTracks 3') - console.log(videoElement.audioTracks) - console.log(typeof videoElement.audioTracks) if (!videoElement.audioTracks || !Array.from(videoElement.audioTracks).length) { - console.log('audioTracks 4') return []; } From f5f6f5e7f87357cdc7bbc42d9d1429d8112f82bd Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 13:33:12 +0200 Subject: [PATCH 20/25] Lint --- src/StremioVideo/selectVideoImplementation.js | 1 - src/TitanVideo/TitanVideo.js | 4 ++-- src/withHTMLSubtitles/withHTMLSubtitles.js | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/StremioVideo/selectVideoImplementation.js b/src/StremioVideo/selectVideoImplementation.js index d5ef8af..09ad141 100644 --- a/src/StremioVideo/selectVideoImplementation.js +++ b/src/StremioVideo/selectVideoImplementation.js @@ -39,7 +39,6 @@ function selectVideoImplementation(commandArgs, options) { return withStreamingServer(withHTMLSubtitles(WebOsVideo)); } if (commandArgs.platform === 'Titan' || commandArgs.platform === 'NetTV') { - console.log('selectVideoImplementation using Titan Player with server'); return withStreamingServer(withHTMLSubtitles(TitanVideo)); } return withStreamingServer(withHTMLSubtitles(HTMLVideo)); diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index cdb7d07..7a5c178 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -537,7 +537,7 @@ function TitanVideo(options) { onPropChanged('subtitlesTracks'); onPropChanged('selectedSubtitlesTrackId'); }); - } + }; } if (videoElement.audioTracks) { videoElement.audioTracks.onaddtrack = function() { @@ -546,7 +546,7 @@ function TitanVideo(options) { onPropChanged('audioTracks'); onPropChanged('selectedAudioTrackId'); }); - } + }; } videoElement.src = stream.url; } else { diff --git a/src/withHTMLSubtitles/withHTMLSubtitles.js b/src/withHTMLSubtitles/withHTMLSubtitles.js index 9bdf4a0..9711f4d 100644 --- a/src/withHTMLSubtitles/withHTMLSubtitles.js +++ b/src/withHTMLSubtitles/withHTMLSubtitles.js @@ -266,7 +266,6 @@ function withHTMLSubtitles(Video) { })); }); } - console.log('selectedTrack', selectedTrack); loadSubtitleFromUrl(selectedTrack.url); } renderSubtitles(); From 151dfe88aaf3735ba6f4cc2e603378a6849d8fb0 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 17:03:27 +0200 Subject: [PATCH 21/25] Add Titan / NetTV to supportsTranscoding Logic --- src/supportsTranscoding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supportsTranscoding.js b/src/supportsTranscoding.js index 3970174..be10018 100644 --- a/src/supportsTranscoding.js +++ b/src/supportsTranscoding.js @@ -1,7 +1,7 @@ var platform = require('./platform'); function supportsTranscoding() { - if (['Tizen', 'webOS'].includes(platform.get()) || typeof window.qt !== 'undefined') { + if (['Tizen', 'webOS', 'Titan', 'NetTV'].includes(platform.get()) || typeof window.qt !== 'undefined') { return Promise.resolve(false); } return Promise.resolve(true); From 54b39301a1d71792ad11a042c9b58038fd280e1b Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Wed, 15 Jan 2025 17:05:36 +0200 Subject: [PATCH 22/25] v0.0.50 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ed1306..243aa1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stremio/stremio-video", - "version": "0.0.49", + "version": "0.0.50", "description": "Abstraction layer on top of different media players", "author": "Smart Code OOD", "main": "src/index.js", From 0046fefaab0e3d603ce1ab792635e2ae29d778f9 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 16 Jan 2025 10:00:13 +0100 Subject: [PATCH 23/25] feat: return tracks labels --- src/HTMLVideo/HTMLVideo.js | 4 ++-- src/TitanVideo/TitanVideo.js | 4 ++-- src/TizenVideo/TizenVideo.js | 22 ++++++++++++++++++++-- src/WebOsVideo/WebOsVideo.js | 4 ++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/HTMLVideo/HTMLVideo.js b/src/HTMLVideo/HTMLVideo.js index 2ee6a79..91cab91 100644 --- a/src/HTMLVideo/HTMLVideo.js +++ b/src/HTMLVideo/HTMLVideo.js @@ -188,8 +188,8 @@ function HTMLVideo(options) { .map(function(track, index) { return Object.freeze({ id: 'EMBEDDED_' + String(index), - lang: track.language, - label: track.label, + lang: track.language.length > 0 ? track.language : null, + label: track.label.length > 0 ? track.label : null, origin: 'EMBEDDED', embedded: true }); diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index 7a5c178..ca92fa2 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -192,7 +192,7 @@ function TitanVideo(options) { return Object.freeze({ id: 'EMBEDDED_' + String(index), lang: track.language, - label: track.label || track.language, + label: track.label.length > 0 ? track.label : null, origin: 'EMBEDDED', embedded: true }); @@ -265,7 +265,7 @@ function TitanVideo(options) { return Object.freeze({ id: 'EMBEDDED_' + String(index), lang: track.language, - label: track.label || track.language, + label: track.label.length > 0 ? track.label : null, origin: 'EMBEDDED', embedded: true }); diff --git a/src/TizenVideo/TizenVideo.js b/src/TizenVideo/TizenVideo.js index 21b0b78..0cc3166 100644 --- a/src/TizenVideo/TizenVideo.js +++ b/src/TizenVideo/TizenVideo.js @@ -252,10 +252,19 @@ function TizenVideo(options) { textTrackLang = extendedTrackData.lang || 'eng'; } } + var textTrackLabel = null; + if (((tracksData || {}).subs || []).length) { + var extendedTrackData = tracksData.subs.find(function(el) { + return (el || {}).id-1 === textTrack.index; + }); + if (extendedTrackData) { + textTrackLabel = extendedTrackData.label; + } + } textTracks.push({ id: textTrackId, lang: textTrackLang, - label: textTrackLang, + label: textTrackLabel, origin: 'EMBEDDED', embedded: true, mode: !disabledSubs && textTrackId === currentSubTrack ? 'showing' : 'disabled', @@ -354,10 +363,19 @@ function TizenVideo(options) { audioTrackLang = extendedTrackData.lang || 'eng'; } } + var audioTrackLabel = null; + if (((tracksData || {}).subs || []).length) { + var extendedTrackData = tracksData.subs.find(function(el) { + return (el || {}).id-1 === audioTrack.index; + }); + if (extendedTrackData) { + audioTrackLabel = extendedTrackData.label; + } + } audioTracks.push({ id: audioTrackId, lang: audioTrackLang, - label: audioTrackLang, + label: audioTrackLabel, origin: 'EMBEDDED', embedded: true, mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled', diff --git a/src/WebOsVideo/WebOsVideo.js b/src/WebOsVideo/WebOsVideo.js index 02ace2c..080b339 100644 --- a/src/WebOsVideo/WebOsVideo.js +++ b/src/WebOsVideo/WebOsVideo.js @@ -370,7 +370,7 @@ function WebOsVideo(options) { textTracks.push({ id: 'EMBEDDED_' + textTrackId, lang: track.lang || 'eng', - label: track.lang || 'eng', + label: track.label.length > 0 ? track.label : null, origin: 'EMBEDDED', embedded: true, mode: textTrackId === currentSubTrack ? 'showing' : 'disabled', @@ -392,7 +392,7 @@ function WebOsVideo(options) { audioTracks.push({ id: 'EMBEDDED_' + audioTrackId, lang: track.lang || 'eng', - label: track.lang || 'eng', + label: track.label.length > 0 ? track.label : null, origin: 'EMBEDDED', embedded: true, mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled', From f4ebfc96e7b261b15dd8628a89e5097f1fed5a62 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 16 Jan 2025 10:23:12 +0100 Subject: [PATCH 24/25] fix: tracks lang and label conditions --- src/HTMLVideo/HTMLVideo.js | 4 ++-- src/TitanVideo/TitanVideo.js | 4 ++-- src/WebOsVideo/WebOsVideo.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HTMLVideo/HTMLVideo.js b/src/HTMLVideo/HTMLVideo.js index 91cab91..2599d67 100644 --- a/src/HTMLVideo/HTMLVideo.js +++ b/src/HTMLVideo/HTMLVideo.js @@ -188,8 +188,8 @@ function HTMLVideo(options) { .map(function(track, index) { return Object.freeze({ id: 'EMBEDDED_' + String(index), - lang: track.language.length > 0 ? track.language : null, - label: track.label.length > 0 ? track.label : null, + lang: track.language, + label: track.label || null, origin: 'EMBEDDED', embedded: true }); diff --git a/src/TitanVideo/TitanVideo.js b/src/TitanVideo/TitanVideo.js index ca92fa2..ace082c 100644 --- a/src/TitanVideo/TitanVideo.js +++ b/src/TitanVideo/TitanVideo.js @@ -192,7 +192,7 @@ function TitanVideo(options) { return Object.freeze({ id: 'EMBEDDED_' + String(index), lang: track.language, - label: track.label.length > 0 ? track.label : null, + label: track.label || null, origin: 'EMBEDDED', embedded: true }); @@ -265,7 +265,7 @@ function TitanVideo(options) { return Object.freeze({ id: 'EMBEDDED_' + String(index), lang: track.language, - label: track.label.length > 0 ? track.label : null, + label: track.label || null, origin: 'EMBEDDED', embedded: true }); diff --git a/src/WebOsVideo/WebOsVideo.js b/src/WebOsVideo/WebOsVideo.js index 080b339..5cba082 100644 --- a/src/WebOsVideo/WebOsVideo.js +++ b/src/WebOsVideo/WebOsVideo.js @@ -370,7 +370,7 @@ function WebOsVideo(options) { textTracks.push({ id: 'EMBEDDED_' + textTrackId, lang: track.lang || 'eng', - label: track.label.length > 0 ? track.label : null, + label: track.label || null, origin: 'EMBEDDED', embedded: true, mode: textTrackId === currentSubTrack ? 'showing' : 'disabled', @@ -392,7 +392,7 @@ function WebOsVideo(options) { audioTracks.push({ id: 'EMBEDDED_' + audioTrackId, lang: track.lang || 'eng', - label: track.label.length > 0 ? track.label : null, + label: track.label || null, origin: 'EMBEDDED', embedded: true, mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled', From aba3de2982432e6691b313f773d1255030d1f434 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Thu, 16 Jan 2025 14:05:13 +0200 Subject: [PATCH 25/25] Fix Tizen Player Duplication --- src/TizenVideo/TizenVideo.js | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/TizenVideo/TizenVideo.js b/src/TizenVideo/TizenVideo.js index 0cc3166..51d54e8 100644 --- a/src/TizenVideo/TizenVideo.js +++ b/src/TizenVideo/TizenVideo.js @@ -244,21 +244,14 @@ function TizenVideo(options) { extra = JSON.parse(textTrack.extra_info); } catch(e) {} var textTrackLang = typeof extra.track_lang === 'string' && extra.track_lang.length > 0 ? extra.track_lang.trim() : null; - if (((tracksData || {}).subs || []).length) { - var extendedTrackData = tracksData.subs.find(function(el) { - return (el || {}).id-1 === textTrack.index; - }); - if (extendedTrackData) { - textTrackLang = extendedTrackData.lang || 'eng'; - } - } var textTrackLabel = null; if (((tracksData || {}).subs || []).length) { var extendedTrackData = tracksData.subs.find(function(el) { return (el || {}).id-1 === textTrack.index; }); if (extendedTrackData) { - textTrackLabel = extendedTrackData.label; + textTrackLang = extendedTrackData.lang || 'eng'; + textTrackLabel = extendedTrackData.label || null; } } textTracks.push({ @@ -355,21 +348,14 @@ function TizenVideo(options) { extra = JSON.parse(audioTrack.extra_info); } catch(e) {} var audioTrackLang = typeof extra.language === 'string' && extra.language.length > 0 ? extra.language : null; + var audioTrackLabel = null; if (((tracksData || {}).audio || []).length) { var extendedTrackData = tracksData.audio.find(function(el) { return (el || {}).id-1 === audioTrack.index; }); if (extendedTrackData) { audioTrackLang = extendedTrackData.lang || 'eng'; - } - } - var audioTrackLabel = null; - if (((tracksData || {}).subs || []).length) { - var extendedTrackData = tracksData.subs.find(function(el) { - return (el || {}).id-1 === audioTrack.index; - }); - if (extendedTrackData) { - audioTrackLabel = extendedTrackData.label; + audioTrackLabel = extendedTrackData.label || null; } } audioTracks.push({