From 1ba83f42bb4c630efb1048222f22a24dde7b4501 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 26 Jul 2022 13:40:54 +0300 Subject: [PATCH 1/7] Implement shell IPC and pass it to the player --- src/routes/Player/Player.js | 3 +- src/services/Shell/Shell.js | 81 +++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 913037b0e..2ff92a03a 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -18,7 +18,7 @@ const useSettings = require('./useSettings'); const styles = require('./styles'); const Player = ({ urlParams, queryParams }) => { - const { core, chromecast } = useServices(); + const { core, chromecast, shell } = useServices(); const [forceTranscoding, maxAudioChannels] = React.useMemo(() => { return [ queryParams.has('forceTranscoding'), @@ -263,6 +263,7 @@ const Player = ({ urlParams, queryParams }) => { } }, { chromecastTransport: chromecast.active ? chromecast.transport : null, + shell, }); } }, [streamingServer.baseUrl, player.selected, player.metaItem, forceTranscoding, maxAudioChannels, casting]); diff --git a/src/services/Shell/Shell.js b/src/services/Shell/Shell.js index bb4d9051f..8dc97d24e 100644 --- a/src/services/Shell/Shell.js +++ b/src/services/Shell/Shell.js @@ -59,6 +59,87 @@ function Shell() { this.off = function(name, listener) { events.off(name, listener); }; + + this.props = {}; + const shell = this; + window.initShellComm = function () { + var transport = window.qt && window.qt.webChannelTransport; + if (!transport) throw 'no viable transport found (qt.webChannelTransport)'; + + active = false; + starting = true; + error = null; + + var QtMsgTypes = { + signal: 1, + propertyUpdate: 2, + init: 3, + idle: 4, + debug: 5, + invokeMethod: 6, + connectToSignal: 7, + disconnectFromSignal: 8, + setProperty: 9, + response: 10, + }; + var QtObjId = 'transport'; // the ID of our transport object + + var id = 0; + function send(msg) { + msg.id = id++; + transport.send(JSON.stringify(msg)); + } + + transport.onmessage = function (message) { + var msg = JSON.parse(message.data); + if (msg.id === 0) { + var obj = msg.data[QtObjId]; + + obj.properties.slice(1).forEach(function (prop) { + shell.props[prop[1]] = prop[3]; + }); + if (typeof shell.props.shellVersion === 'string') { + shell.shellVersionArr = ( + shell.props.shellVersion.match(/(\d+)\.(\d+)\.(\d+)/) || [] + ) + .slice(1, 4) + .map(Number); + } + events.emit('received-props', shell.props); + + obj.signals.forEach(function (sig) { + send({ + type: QtMsgTypes.connectToSignal, + object: QtObjId, + signal: sig[1], + }); + }); + + var onEvent = obj.methods.filter(function (x) { + return x[0] === 'onEvent'; + })[0]; + + shell.send = function (ev, args) { + send({ + type: QtMsgTypes.invokeMethod, + object: QtObjId, + method: onEvent[1], + args: [ev, args || {}], + }); + }; + starting = false; + active = true; + error = null; + + shell.send('app-ready', {}); // signal that we're ready to take events + } + + if (msg.object === QtObjId && msg.type === QtMsgTypes.signal) + events.emit(msg.args[0], msg.args[1]); + onStateChanged(); + }; + send({ type: QtMsgTypes.init }); + }; } module.exports = Shell; From c535a200e112b3bac5f504fdc94f4ffce610e893 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Wed, 27 Jul 2022 12:22:51 +0300 Subject: [PATCH 2/7] Move the business logic to ShellTransport --- src/services/Shell/Shell.js | 111 +++++++------------------ src/services/Shell/ShellTransport.js | 120 +++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 83 deletions(-) create mode 100644 src/services/Shell/ShellTransport.js diff --git a/src/services/Shell/Shell.js b/src/services/Shell/Shell.js index 8dc97d24e..99fc44721 100644 --- a/src/services/Shell/Shell.js +++ b/src/services/Shell/Shell.js @@ -1,14 +1,31 @@ // Copyright (C) 2017-2022 Smart code 203358507 const EventEmitter = require('eventemitter3'); +const ShellTransport = require('./ShellTransport'); function Shell() { let active = false; let error = null; let starting = false; + let transport = null; const events = new EventEmitter(); + function onTransportInit() { + active = true; + error = null; + starting = false; + onStateChanged(); + } + function onTransportInitError(err) { + console.error(err); + active = false; + error = err; + starting = false; + onStateChanged(); + transport = null; + } + function onStateChanged() { events.emit('stateChanged'); } @@ -34,6 +51,13 @@ function Shell() { get: function() { return starting; } + }, + transport: { + configurable: false, + enumerable: true, + get: function() { + return transport; + } } }); @@ -43,8 +67,10 @@ function Shell() { } active = false; - error = new Error('Stremio Shell API not available'); - starting = false; + starting = true; + transport = new ShellTransport(); + transport.on('init', onTransportInit); + transport.on('init-error', onTransportInitError); onStateChanged(); }; this.stop = function() { @@ -59,87 +85,6 @@ function Shell() { this.off = function(name, listener) { events.off(name, listener); }; - - this.props = {}; - const shell = this; - window.initShellComm = function () { - var transport = window.qt && window.qt.webChannelTransport; - if (!transport) throw 'no viable transport found (qt.webChannelTransport)'; - - active = false; - starting = true; - error = null; - - var QtMsgTypes = { - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - response: 10, - }; - var QtObjId = 'transport'; // the ID of our transport object - - var id = 0; - function send(msg) { - msg.id = id++; - transport.send(JSON.stringify(msg)); - } - - transport.onmessage = function (message) { - var msg = JSON.parse(message.data); - if (msg.id === 0) { - var obj = msg.data[QtObjId]; - - obj.properties.slice(1).forEach(function (prop) { - shell.props[prop[1]] = prop[3]; - }); - if (typeof shell.props.shellVersion === 'string') { - shell.shellVersionArr = ( - shell.props.shellVersion.match(/(\d+)\.(\d+)\.(\d+)/) || [] - ) - .slice(1, 4) - .map(Number); - } - events.emit('received-props', shell.props); - - obj.signals.forEach(function (sig) { - send({ - type: QtMsgTypes.connectToSignal, - object: QtObjId, - signal: sig[1], - }); - }); - - var onEvent = obj.methods.filter(function (x) { - return x[0] === 'onEvent'; - })[0]; - - shell.send = function (ev, args) { - send({ - type: QtMsgTypes.invokeMethod, - object: QtObjId, - method: onEvent[1], - args: [ev, args || {}], - }); - }; - starting = false; - active = true; - error = null; - - shell.send('app-ready', {}); // signal that we're ready to take events - } - - if (msg.object === QtObjId && msg.type === QtMsgTypes.signal) - events.emit(msg.args[0], msg.args[1]); - onStateChanged(); - }; - send({ type: QtMsgTypes.init }); - }; } module.exports = Shell; diff --git a/src/services/Shell/ShellTransport.js b/src/services/Shell/ShellTransport.js new file mode 100644 index 000000000..6014ef9d4 --- /dev/null +++ b/src/services/Shell/ShellTransport.js @@ -0,0 +1,120 @@ +// Copyright (C) 2017-2022 Smart code 203358507 + +const EventEmitter = require('eventemitter3'); + +let shellAvailable = false; +const shellEvents = new EventEmitter(); + +const QtMsgTypes = { + signal: 1, + propertyUpdate: 2, + init: 3, + idle: 4, + debug: 5, + invokeMethod: 6, + connectToSignal: 7, + disconnectFromSignal: 8, + setProperty: 9, + response: 10, +}; +const QtObjId = 'transport'; // the ID of our transport object + +window.initShellComm = function () { + delete window.initShellComm; + shellEvents.emit('availabilityChanged'); +}; + +const initialize = () => { + return new Promise((resolve) => { + function onShellAvailabilityChanged() { + shellEvents.off('availabilityChanged', onShellAvailabilityChanged); + shellAvailable = true; + resolve(); + } + if (shellAvailable) { + onShellAvailabilityChanged(); + } else { + shellEvents.on('availabilityChanged', onShellAvailabilityChanged); + } + }); +}; + +function ShellTransport() { + const events = new EventEmitter(); + + this.props = {}; + + const shell = this; + initialize() + .then(() => { + const transport = window.qt && window.qt.webChannelTransport; + if (!transport) throw 'no viable transport found (qt.webChannelTransport)'; + + let id = 0; + function send(msg) { + msg.id = id++; + transport.send(JSON.stringify(msg)); + } + + transport.onmessage = function (message) { + const msg = JSON.parse(message.data); + if (msg.id === 0) { + const obj = msg.data[QtObjId]; + + obj.properties.slice(1).forEach(function (prop) { + shell.props[prop[1]] = prop[3]; + }); + if (typeof shell.props.shellVersion === 'string') { + shell.shellVersionArr = ( + shell.props.shellVersion.match(/(\d+)\.(\d+)\.(\d+)/) || [] + ) + .slice(1, 4) + .map(Number); + } + events.emit('received-props', shell.props); + + obj.signals.forEach(function (sig) { + send({ + type: QtMsgTypes.connectToSignal, + object: QtObjId, + signal: sig[1], + }); + }); + + const onEvent = obj.methods.filter(function (x) { + return x[0] === 'onEvent'; + })[0]; + + shell.send = function (ev, args) { + send({ + type: QtMsgTypes.invokeMethod, + object: QtObjId, + method: onEvent[1], + args: [ev, args || {}], + }); + }; + + shell.send('app-ready', {}); // signal that we're ready to take events + } + + if (msg.object === QtObjId && msg.type === QtMsgTypes.signal) + events.emit(msg.args[0], msg.args[1]); + events.emit('init'); + }; + send({ type: QtMsgTypes.init }); + }) .catch((error) => { + events.emit('init-error', error); + }); + + this.on = function(name, listener) { + events.on(name, listener); + }; + this.off = function(name, listener) { + events.off(name, listener); + }; + this.removeAllListeners = function() { + events.removeAllListeners(); + }; +} + +module.exports = ShellTransport; From 1a912c9dc49e14407f2e81ee60cf1bfb55231b7f Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 29 Jul 2022 11:35:01 +0300 Subject: [PATCH 3/7] Do not send the shell sercie to stremio-video if shell is inactive --- src/routes/Player/Player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 2ff92a03a..1d9179ab8 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -263,7 +263,7 @@ const Player = ({ urlParams, queryParams }) => { } }, { chromecastTransport: chromecast.active ? chromecast.transport : null, - shell, + shell: shell.active ? shell : null, }); } }, [streamingServer.baseUrl, player.selected, player.metaItem, forceTranscoding, maxAudioChannels, casting]); From f9e35e4b0a293301a01a23fb6321aea1bb784335 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 29 Jul 2022 11:42:00 +0300 Subject: [PATCH 4/7] Send only the shell transport to stremio-video --- src/routes/Player/Player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 1d9179ab8..43d99f2b4 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -263,7 +263,7 @@ const Player = ({ urlParams, queryParams }) => { } }, { chromecastTransport: chromecast.active ? chromecast.transport : null, - shell: shell.active ? shell : null, + shellTransport: shell.active ? shell.transport : null, }); } }, [streamingServer.baseUrl, player.selected, player.metaItem, forceTranscoding, maxAudioChannels, casting]); From 16aa087021ef6d1cd38717c4144955ea6262df70 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 29 Jul 2022 12:41:40 +0300 Subject: [PATCH 5/7] Throw an error if the Qt API is not found --- src/services/Shell/ShellTransport.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/Shell/ShellTransport.js b/src/services/Shell/ShellTransport.js index 6014ef9d4..ff2ac845c 100644 --- a/src/services/Shell/ShellTransport.js +++ b/src/services/Shell/ShellTransport.js @@ -25,6 +25,7 @@ window.initShellComm = function () { }; const initialize = () => { + if(!window.qt) return Promise.reject('Qt API not found'); return new Promise((resolve) => { function onShellAvailabilityChanged() { shellEvents.off('availabilityChanged', onShellAvailabilityChanged); From e20207b1ef02959a471100dd3a9a7e422b443489 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 29 Jul 2022 13:55:05 +0300 Subject: [PATCH 6/7] Cast shell message to error --- src/services/Shell/Shell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/Shell/Shell.js b/src/services/Shell/Shell.js index 99fc44721..1e0c31943 100644 --- a/src/services/Shell/Shell.js +++ b/src/services/Shell/Shell.js @@ -20,7 +20,7 @@ function Shell() { function onTransportInitError(err) { console.error(err); active = false; - error = err; + error = new Error(err); starting = false; onStateChanged(); transport = null; From d245259ca947a9fd238dfb05a6873b1e4de5ba26 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Sun, 17 Jul 2022 20:58:50 +0200 Subject: [PATCH 7/7] add ios pwa support --- src/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.html b/src/index.html index 552e72414..bfd3f278a 100644 --- a/src/index.html +++ b/src/index.html @@ -4,7 +4,10 @@ + + + Stremio - All you can watch! <%= htmlWebpackPlugin.tags.headTags %>