update readme
[dotfiles/.git] / .config / BraveSoftware / Brave-Browser / Default / Extensions / cimiefiiaegbelhefglklhhakcgmhkai / 1.7.6_0 / extension-mpris.js
1 /*
2     Copyright (C) 2017-2019 Kai Uwe Broulik <kde@privat.broulik.de>
3
4     This program is free software; you can redistribute it and/or
5     modify it under the terms of the GNU General Public License as
6     published by the Free Software Foundation; either version 3 of
7     the License, or (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 let playerIds = [];
19
20 function currentPlayer() {
21     let playerId = playerIds[playerIds.length - 1];
22     if (!playerId) {
23         // Returning empty object instead of null so you can call player.id returning undefined instead of throwing
24         return {};
25     }
26
27     let segments = playerId.split("-");
28     return {
29         id: playerId,
30         tabId: parseInt(segments[0]),
31         frameId: parseInt(segments[1])
32     };
33 }
34
35 function playerIdFromSender(sender) {
36     return sender.tab.id + "-" + (sender.frameId || 0);
37 }
38
39 function sendPlayerTabMessage(player, action, payload) {
40     if (!player) {
41         return;
42     }
43
44     let message = {
45         subsystem: "mpris",
46         action: action
47     };
48     if (payload) {
49         message.payload = payload;
50     }
51
52     chrome.tabs.sendMessage(player.tabId, message, {
53         frameId: player.frameId
54     }, (resp) => {
55         const error = chrome.runtime.lastError;
56         // When player tab crashed, we get this error message.
57         // There's unfortunately no proper signal for this so we can really only know when we try to send a command
58         if (error && error.message === "Could not establish connection. Receiving end does not exist.") {
59             console.warn("Failed to send player command to tab", player.tabId, ", signalling player gone");
60             playerTabGone(player.tabId);
61         }
62     });
63 }
64
65 function playerTabGone(tabId) {
66     let players = playerIds;
67     players.forEach((playerId) => {
68         if (playerId.startsWith(tabId + "-")) {
69             playerGone(playerId);
70         }
71     });
72 }
73
74 function playerGone(playerId) {
75     let oldPlayer = currentPlayer();
76
77     var removedPlayerIdx = playerIds.indexOf(playerId);
78     if (removedPlayerIdx > -1) {
79         playerIds.splice(removedPlayerIdx, 1); // remove that player from the array
80     }
81
82     let newPlayer = currentPlayer();
83
84     if (oldPlayer.id === newPlayer.id) {
85         return;
86     }
87
88     // all players gone :(
89     if (!newPlayer.id) {
90         sendPortMessage("mpris", "gone");
91         return;
92     }
93
94     // ask the now current player to identify to us
95     // we can't just pretend "playing" as the other player might be paused
96     sendPlayerTabMessage(newPlayer, "identify");
97 }
98
99 // when tab is closed, tell the player is gone
100 // below we also have a "gone" signal listener from the content script
101 // which is invoked in the pagehide handler of the page
102 chrome.tabs.onRemoved.addListener((tabId) => {
103     // Since we only get the tab id, search for all players from this tab and signal a "gone"
104     playerTabGone(tabId);
105 });
106
107 // There's no signal for when a tab process crashes (only in browser dev builds).
108 // We watch for the tab becoming inaudible and check if it's still around.
109 // With this heuristic we can at least mitigate MPRIS remaining stuck in a playing state.
110 chrome.tabs.onUpdated.addListener((tabId, changes) => {
111     if (!changes.hasOwnProperty("audible") || changes.audible === true) {
112         return;
113     }
114
115     // Now check if the tab is actually gone
116     chrome.tabs.executeScript(tabId, {
117         code: `true`
118     }, (response) => {
119         const error = chrome.runtime.lastError;
120         // Chrome error in script_executor.cc "kRendererDestroyed"
121         if (error && error.message === "The tab was closed.") {
122             console.warn("Player tab", tabId, "became inaudible and was considered crashed, signalling player gone");
123             playerTabGone(tabId);
124         }
125     });
126 });
127
128 // callbacks from host (Plasma) to our extension
129 addCallback("mpris", "raise", function (message) {
130     let player = currentPlayer();
131     if (player.tabId) {
132         raiseTab(player.tabId);
133     }
134 });
135
136 addCallback("mpris", ["play", "pause", "playPause", "stop", "next", "previous"], function (message, action) {
137     sendPlayerTabMessage(currentPlayer(), action);
138 });
139
140 addCallback("mpris", "setFullscreen", (message) => {
141     sendPlayerTabMessage(currentPlayer(), "setFullscreen", {
142         fullscreen: message.fullscreen
143     });
144 });
145
146 addCallback("mpris", "setVolume", function (message) {
147     sendPlayerTabMessage(currentPlayer(), "setVolume", {
148         volume: message.volume
149     });
150 });
151
152 addCallback("mpris", "setLoop", function (message) {
153     sendPlayerTabMessage(currentPlayer(), "setLoop", {
154         loop: message.loop
155     });
156 });
157
158 addCallback("mpris", "setPosition", function (message) {
159     sendPlayerTabMessage(currentPlayer(), "setPosition", {
160         position: message.position
161     });
162 })
163
164 addCallback("mpris", "setPlaybackRate", function (message) {
165     sendPlayerTabMessage(currentPlayer(), "setPlaybackRate", {
166         playbackRate: message.playbackRate
167     });
168 });
169
170 // callbacks from a browser tab to our extension
171 addRuntimeCallback("mpris", "playing", function (message, sender) {
172     // Before Firefox 67 it ran extensions in incognito mode by default.
173     // However, after the update the extension keeps running in incognito mode.
174     // So we keep disabling media controls for them to prevent accidental private
175     // information leak on lock screen or now playing auto status in a messenger
176     if (IS_FIREFOX && sender.tab.incognito) {
177         return;
178     }
179
180     let playerId = playerIdFromSender(sender);
181
182     let idx = playerIds.indexOf(playerId);
183     if (idx > -1) {
184         // Move it to the end of the list so it becomes current
185         playerIds.push(playerIds.splice(idx, 1)[0]);
186     } else {
187         playerIds.push(playerId);
188     }
189
190     var payload = message || {};
191     payload.tabTitle = sender.tab.title;
192     payload.url = sender.tab.url;
193
194     sendPortMessage("mpris", "playing", payload);
195 });
196
197 addRuntimeCallback("mpris", "gone", function (message, sender) {
198     playerGone(playerIdFromSender(sender));
199 });
200
201 addRuntimeCallback("mpris", "stopped", function (message, sender) {
202     // When player stopped, check if there's another one we could control now instead
203     let playerId = playerIdFromSender(sender);
204     if (currentPlayer().id === playerId) {
205         if (playerIds.length > 1) {
206             playerGone(playerId);
207         }
208     }
209 });
210
211 addRuntimeCallback("mpris", ["paused", "waiting", "canplay"], function (message, sender, action) {
212     if (currentPlayer().id === playerIdFromSender(sender)) {
213         sendPortMessage("mpris", action);
214     }
215 });
216
217 addRuntimeCallback("mpris", ["duration", "timeupdate", "seeking", "seeked", "ratechange", "volumechange", "titlechange", "fullscreenchange"], function (message, sender, action) {
218     if (currentPlayer().id === playerIdFromSender(sender)) {
219         sendPortMessage("mpris", action, message);
220     }
221 });
222
223 addRuntimeCallback("mpris", ["metadata", "callbacks"], function (message, sender, action) {
224     if (currentPlayer().id === playerIdFromSender(sender)) {
225         var payload = {};
226         payload[action] = message;
227
228         sendPortMessage("mpris", action, payload);
229     }
230 });
231
232 addRuntimeCallback("mpris", "hasTabPlayer", (message) => {
233     const playersOnTab = playerIds.filter((playerId) => {
234         return playerId.startsWith(message.tabId + "-");
235     });
236
237     return Promise.resolve(playersOnTab);
238 });