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 %>
diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js
index e08157426..b16334c49 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 { chromecast } = useServices();
+ const { chromecast, shell } = useServices();
const [forceTranscoding, maxAudioChannels] = React.useMemo(() => {
return [
queryParams.has('forceTranscoding'),
@@ -258,6 +258,7 @@ const Player = ({ urlParams, queryParams }) => {
}
}, {
chromecastTransport: chromecast.active ? chromecast.transport : null,
+ shellTransport: shell.active ? shell.transport : null,
});
}
}, [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..1e0c31943 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 = new 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() {
diff --git a/src/services/Shell/ShellTransport.js b/src/services/Shell/ShellTransport.js
new file mode 100644
index 000000000..ff2ac845c
--- /dev/null
+++ b/src/services/Shell/ShellTransport.js
@@ -0,0 +1,121 @@
+// 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 = () => {
+ if(!window.qt) return Promise.reject('Qt API not found');
+ 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;