Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEPRECATED] Chromecast Plugin #296

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
6 changes: 6 additions & 0 deletions config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ const defaultConfig = {
},
savedVolume: undefined //plugin save volume between session here
},
chromecast: {
enabled: false,
syncVolume: true,
syncStartTime: true,
syncSeek: true
},
sponsorblock: {
enabled: false,
apiURL: "https://sponsor.ajay.app",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"async-mutex": "^0.3.1",
"browser-id3-writer": "^4.4.0",
"chokidar": "^3.5.1",
"chromecast-api": "^0.3.4",
"custom-electron-titlebar": "^3.2.6",
"discord-rpc": "^3.2.0",
"electron-debug": "^3.2.0",
Expand Down
105 changes: 105 additions & 0 deletions plugins/chromecast/back.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const registerCallback = require("../../providers/song-info");
const getSongControls = require('../../providers/song-controls');

const { ipcMain } = require('electron')
const ChromecastAPI = require('chromecast-api');
const { setOptions } = require("../../config/plugins");

let client;
let deviceList = [];

let play, pause;

let options;

module.exports = (win, initialOptions) => {
const { playPause } = getSongControls(win);
play = () => playPause(true);
pause = () => playPause(false);

options = initialOptions;

client = new ChromecastAPI();

client.on('device', (device) => {
if (!deviceList.includes(device.name)) {
registerDevice(device);
}
});
ipcMain.on('volume-change', (_, v) => setVolume(v));
ipcMain.on('seeked-to', (_, s) => seekTo(s + 1.5));
};

function setVolume(volume) {
if (options.syncVolume) {
for (const device of client.devices) {
device.setVolume(volume);
}
}
}

function seekTo(seconds) {
if (options.syncSeek) {
for (const device of client.devices) {
device.seekTo(seconds);
}
}
}

function registerDevice(device) {
deviceList.push(device.name);
let currentStatus;
device.on('status', async (status) => {
currentStatus = status.playerState;
if (options.syncStartTime) {
currentStatus === "PLAYING" ? play() : pause();
}
})

let currentUrl;
let isPaused;
registerCallback(songInfo => {
if (!songInfo?.title) {
return;
}
if (currentUrl !== songInfo.url) { //new song
currentUrl = songInfo.url;
if (options.syncStartTime) {
isPaused = true;
pause();
} else {
isPaused = songInfo.isPaused;
}
device.play(transformURL(songInfo.url));

} else if (isPaused !== songInfo.isPaused) { //paused status changed
isPaused = songInfo.isPaused;
if (isPaused && currentStatus === "PLAYING") {
device.pause();
} else {
device.resume();
}
}
});
}

function transformURL(url) {// will not be needed after https://github.com/alxhotel/chromecast-api/pull/69 (chromecastAPI v0.3.5)
const videoId = url.match(/(?:http(?:s?):\/\/)?(?:www\.)?(?:music\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)/);
// videoId[1] should always be valid since regex should always be valid
return "https://youtube.com/watch?v=" + (videoId.length > 1 ? videoId[1] : "dQw4w9WgXcQ");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice Rickroll 🙃 Maybe worth a comment in code, I wondered why this hardcoded ID was there at first 😅

Copy link
Collaborator Author

@Araxeus Araxeus Jun 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a comment already 😅
I thought the module would update its version faster - and this method will be unneeded
but sadly it hasn't happened yet - the fix pr was merged but the npm version wasn't updated yet

(method should only be called when songInfo is updated which means the url regex is valid - which means you should never get rick-rolled 😝)

}

module.exports.setOption = (value, ...keys) => {
for (const key of keys) {
options[key] = value;
}
setOptions("chromecast", options);
};

module.exports.menuCheck = (options_) => {
if (!options) options = options_;
}

module.exports.refreshChromecast = () => {
if (client) client.update();
}
20 changes: 20 additions & 0 deletions plugins/chromecast/front.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { ipcRenderer } = require('electron');

const sendVolume = (volume) => ipcRenderer.send('volume-change', volume);
const sendTime = (seconds) => ipcRenderer.send('seeked-to', seconds);

module.exports = function checkVideoLoaded() {
const video = document.querySelector("video");
video ? setup(video) : setTimeout(checkVideoLoaded, 500);
}

function setup(video) {

sendVolume(video.volume);

video.addEventListener('volumechange', e =>
sendVolume(e.target.muted ? 0 : e.target.volume));

video.addEventListener('seeking', e =>
sendTime(e.target.currentTime));
}
30 changes: 30 additions & 0 deletions plugins/chromecast/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { menuCheck, setOption, refreshChromecast } = require("./back");

module.exports = (_win, options) => {
menuCheck(options);
return [
{
label: "Sync Volume",
type: "checkbox",
checked: !!options.syncVolume,
click: (item) => setOption(item.checked, "syncVolume")
},
{
label: "Sync Start Time",
type: "checkbox",
checked: !!options.syncStartTime,
click: (item) => setOption(item.checked, "syncStartTime")
},
{
label: "Sync Seek",
type: "checkbox",
checked: !!options.syncSeek,
click: (item) => setOption(item.checked, "syncSeek")
},
{ type: "separator" },
{
label: "Refresh Device List",
click: refreshChromecast
}
]
};
51 changes: 38 additions & 13 deletions providers/song-controls-front.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
const { ipcRenderer } = require("electron");

let videoStream = document.querySelector(".video-stream");
let video = document.querySelector("video");

module.exports = () => {
ipcRenderer.on("playPause", () => {
if (!videoStream) {
videoStream = document.querySelector(".video-stream");
}
ipcRenderer.on("playPause", (_e, toPlay) => playPause(toPlay));
};
module.exports.playPause = playPause;

if (videoStream.paused) {
videoStream.play();
} else {
videoStream.yns_pause ?
videoStream.yns_pause() :
videoStream.pause();
function playPause(toPlay = undefined) {
if (!checkVideo()) return;

switch(toPlay) {
case undefined:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could also be the default case for the switch, to be defensive

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 👍🏼

video.paused ? play() : pause();
break;
case true:
play();
break;
case false:
pause();
}

function play() {
video.play();
}

function pause() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it might not be necessary to define the 2 inner functions if they are a one-liner and only used once!

Copy link
Collaborator Author

@Araxeus Araxeus Jun 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that play() is unnecessary, but pause() is called in 2 different places

(I originally added play() only because pause() was there - but I removed it as requested)

video.yns_pause ?
video.yns_pause() :
video.pause();
}

}

function checkVideo() {
if (!video) {
video = document.querySelector("video");
if (!video) {
return false;
}
});
};
} return true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it can be simplified into

function checkVideo() {
    if (!video) {
        video = document.querySelector("video");
    }

    return !!video;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 👍🏼

}
3 changes: 1 addition & 2 deletions providers/song-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ module.exports = (win) => {
// Playback
previous: () => pressKey(win, "k"),
next: () => pressKey(win, "j"),
playPause: () => win.webContents.send("playPause"),
like: () => pressKey(win, "_"),
th-ch marked this conversation as resolved.
Show resolved Hide resolved
playPause: (toPlay = undefined) => win.webContents.send("playPause", toPlay),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting a Error: Failed to serialize arguments (song-controls.js:15:54) when using the play/pause media key, is it expected?

Copy link
Collaborator Author

@Araxeus Araxeus Jun 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It happened because shortcut plugin call the method with win.webContents argument.
I just fixed it by making play, pause directly part of song controls
sorry about that

dislike: () => pressKey(win, "+"),
go10sBack: () => pressKey(win, "h"),
go10sForward: () => pressKey(win, "l"),
Expand Down
Loading