diff --git a/plugins/shortcuts/back.js b/plugins/shortcuts/back.js index c32d0f8d..06d899d7 100644 --- a/plugins/shortcuts/back.js +++ b/plugins/shortcuts/back.js @@ -2,10 +2,7 @@ const { globalShortcut } = require("electron"); const is = require("electron-is"); const electronLocalshortcut = require("electron-localshortcut"); const getSongControls = require("../../providers/song-controls"); -const { setupMPRIS } = require("./mpris"); -const registerCallback = require("../../providers/song-info"); - -let player; +const registerMPRIS = require("./mpris"); function _registerGlobalShortcut(webContents, shortcut, action) { globalShortcut.register(shortcut, () => { @@ -31,54 +28,8 @@ function registerShortcuts(win, options) { _registerLocalShortcut(win, "CommandOrControl+F", search); _registerLocalShortcut(win, "CommandOrControl+L", search); - registerCallback(songInfo => { - if (player) { - player.metadata = { - 'mpris:length': songInfo.songDuration * 60 * 1000 * 1000, // In microseconds - 'mpris:artUrl': songInfo.imageSrc, - 'xesam:title': songInfo.title, - 'xesam:artist': songInfo.artist - }; - if (!songInfo.isPaused) { - player.playbackStatus = "Playing" - } - } - } - ) - if (is.linux()) { - try { - const MPRISPlayer = setupMPRIS(); - - MPRISPlayer.on("raise", () => { - win.setSkipTaskbar(false); - win.show(); - }); - MPRISPlayer.on("play", () => { - if (MPRISPlayer.playbackStatus !== 'Playing') { - MPRISPlayer.playbackStatus = 'Playing'; - playPause() - } - }); - MPRISPlayer.on("pause", () => { - if (MPRISPlayer.playbackStatus !== 'Paused') { - MPRISPlayer.playbackStatus = 'Paused'; - playPause() - } - }); - MPRISPlayer.on("next", () => { - next() - }); - MPRISPlayer.on("previous", () => { - previous() - }); - - player = MPRISPlayer - - } catch (e) { - console.warn("Error in MPRIS", e); - } - } + if (is.linux()) registerMPRIS(win); const { global, local } = options; const shortcutOptions = { global, local }; diff --git a/plugins/shortcuts/mpris.js b/plugins/shortcuts/mpris.js index 705599c0..e6c6539f 100644 --- a/plugins/shortcuts/mpris.js +++ b/plugins/shortcuts/mpris.js @@ -1,4 +1,6 @@ const mpris = require("mpris-service"); +const { ipcMain } = require("electron"); +const registerCallback = require("../../providers/song-info"); function setupMPRIS() { const player = mpris({ @@ -14,6 +16,69 @@ function setupMPRIS() { return player; } -module.exports = { - setupMPRIS, -}; +function registerMPRIS(win) { + try { + const secToMicro = n => Math.round(Number(n) * 1e6); + const microToSec = n => Math.round(Number(n) / 1e6); + + const seekTo = e => win.webContents.send("seekTo", microToSec(e.position)); + const seekBy = o => win.webContents.send("seekBy", microToSec(o)); + + const player = setupMPRIS(); + + const mprisSeek = player.seeked; + + ipcMain.on('seeked', (_, t) => mprisSeek(secToMicro(t))); + + let currentSeconds = 0; + ipcMain.on('timeChanged', (_, t) => currentSeconds = t); + + player.getPosition = () => secToMicro(currentSeconds) + + player.on("raise", () => { + win.setSkipTaskbar(false); + win.show(); + }); + + player.on("play", () => { + if (player.playbackStatus !== 'Playing') { + player.playbackStatus = 'Playing'; + playPause() + } + }); + player.on("pause", () => { + if (player.playbackStatus !== 'Paused') { + player.playbackStatus = 'Paused'; + playPause() + } + }); + + player.on("playpause", playPause); + player.on("next", next); + player.on("previous", previous); + + player.on('seek', seekBy); + player.on('position', seekTo); + + registerCallback(songInfo => { + if (player) { + const data = { + 'mpris:length': secToMicro(songInfo.songDuration), + 'mpris:artUrl': songInfo.imageSrc, + 'xesam:title': songInfo.title, + 'xesam:artist': songInfo.artist, + 'mpris:trackid': '/' + }; + if (songInfo.album) data['xesam:album'] = songInfo.album; + player.metadata = data; + mprisSeek(secToMicro(songInfo.elapsedSeconds)) + player.playbackStatus = songInfo.isPaused ? "Paused" : "Playing" + } + }) + + } catch (e) { + console.warn("Error in MPRIS", e); + } +} + +module.exports = registerMPRIS; diff --git a/plugins/tuna-obs/back.js b/plugins/tuna-obs/back.js index d9882e4d..0a2bc3a3 100644 --- a/plugins/tuna-obs/back.js +++ b/plugins/tuna-obs/back.js @@ -1,33 +1,52 @@ -const { ipcRenderer } = require("electron"); +const { ipcMain } = require("electron"); const fetch = require('node-fetch'); const registerCallback = require("../../providers/song-info"); -const post = (data) => { +const secToMilisec = t => Math.round(Number(t) * 1e3); +const data = { + cover_url: '', + title: '', + artists: [], + status: '', + progress: 0, + duration: 0, + album_url: '', + album: undefined +}; + +const post = async (data) => { const port = 1608; - headers = {'Content-Type': 'application/json', + headers = { + 'Content-Type': 'application/json', 'Accept': 'application/json', 'Access-Control-Allow-Headers': '*', - 'Access-Control-Allow-Origin': '*'} + 'Access-Control-Allow-Origin': '*' + } const url = `http://localhost:${port}/`; - fetch(url, {method: 'POST', headers, body:JSON.stringify({data})}); + fetch(url, { method: 'POST', headers, body: JSON.stringify({ data }) }).catch(e => console.log(`Error: '${e.code || e.errno}' - when trying to access obs-tuna webserver at port ${port}`)); } module.exports = async (win) => { - registerCallback((songInfo) => { + ipcMain.on('timeChanged', async (_, t) => { + if (!data.title) return; + data.progress = secToMilisec(t); + post(data); + }); - // Register the callback - if (songInfo.title.length === 0 && songInfo.artist.length === 0) { + registerCallback((songInfo) => { + if (!songInfo.title && !songInfo.artist) { return; } - const duration = Number(songInfo.songDuration)*1000 - const progress = Number(songInfo.elapsedSeconds)*1000 - const cover_url = songInfo.imageSrc - const album_url = songInfo.imageSrc - const title = songInfo.title - const artists = [songInfo.artist] - const status = !songInfo.isPaused ? 'Playing': 'Paused' - post({ cover_url, title, artists, status, progress, duration, album_url}); + data.duration = secToMilisec(songInfo.songDuration) + data.progress = secToMilisec(songInfo.elapsedSeconds) + data.cover_url = songInfo.imageSrc; + data.album_url = songInfo.imageSrc; + data.title = songInfo.title; + data.artists = [songInfo.artist]; + data.status = songInfo.isPaused ? 'stopped' : 'playing'; + data.album = songInfo.album; + post(data); }) } diff --git a/preload.js b/preload.js index 6c7c1b16..daffe887 100644 --- a/preload.js +++ b/preload.js @@ -6,6 +6,7 @@ const config = require("./config"); const { fileExists } = require("./plugins/utils"); const setupFrontLogger = require("./providers/front-logger"); const setupSongInfo = require("./providers/song-info-front"); +const { setupSongControls } = require("./providers/song-controls-front"); const plugins = config.plugins.getEnabled(); @@ -45,6 +46,9 @@ document.addEventListener("DOMContentLoaded", () => { // inject song-info provider setupSongInfo(); + // inject song-controls + setupSongControls(); + // inject front logger setupFrontLogger(); diff --git a/providers/song-controls-front.js b/providers/song-controls-front.js new file mode 100644 index 00000000..19031388 --- /dev/null +++ b/providers/song-controls-front.js @@ -0,0 +1,15 @@ +const { ipcRenderer } = require("electron"); +const config = require("../config"); +const is = require("electron-is"); + +module.exports.setupSongControls = () => { + document.addEventListener('apiLoaded', e => { + ipcRenderer.on("seekTo", (_, t) => e.target.seekTo(t)); + ipcRenderer.on("seekBy", (_, t) => e.target.seekBy(t)); + + }, { once: true, passive: true }) + + if (is.linux() && config.plugins.isEnabled('shortcuts')) { // MPRIS Enabled + document.querySelector('video').addEventListener('seeked', () => ipcRenderer.send('seeked', v.currentTime)); + } +}; diff --git a/providers/song-info-front.js b/providers/song-info-front.js index acd41daf..6e0349b1 100644 --- a/providers/song-info-front.js +++ b/providers/song-info-front.js @@ -2,8 +2,12 @@ const { ipcRenderer } = require("electron"); const { getImage } = require("./song-info"); +const config = require("../config"); + global.songInfo = {}; +function $(selector) { return document.querySelector(selector); } + ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => { global.songInfo = JSON.parse(extractedSongInfo); global.songInfo.image = await getImage(global.songInfo.imageSrc); @@ -14,7 +18,10 @@ const srcChangedEvent = new CustomEvent('srcChanged'); module.exports = () => { document.addEventListener('apiLoaded', apiEvent => { - const video = document.querySelector('video'); + if (config.plugins.isEnabled('tuna-obs')) { + setupTimeChangeListener(); + } + const video = $('video'); // name = "dataloaded" and abit later "dataupdated" apiEvent.detail.addEventListener('videodatachange', (name, _dataEvent) => { if (name !== 'dataloaded') return; @@ -24,7 +31,7 @@ module.exports = () => { for (const status of ['playing', 'pause']) { video.addEventListener(status, e => { - if (Math.floor(e.target.currentTime) > 0) { + if (Math.round(e.target.currentTime) > 0) { ipcRenderer.send("playPaused", { isPaused: status === 'pause', elapsedSeconds: Math.floor(e.target.currentTime) @@ -35,9 +42,18 @@ module.exports = () => { function sendSongInfo() { const data = apiEvent.detail.getPlayerResponse(); + data.videoDetails.album = $('ytmusic-player-page')?.__data?.playerPageWatchMetadata?.albumName?.runs[0].text data.videoDetails.elapsedSeconds = Math.floor(video.currentTime); data.videoDetails.isPaused = false; ipcRenderer.send("video-src-changed", JSON.stringify(data)); } }, { once: true, passive: true }); }; + +function setupTimeChangeListener() { + const progressObserver = new MutationObserver(mutations => { + ipcRenderer.send('timeChanged', mutations[0].target.value); + global.songInfo.elapsedSeconds = mutations[0].target.value; + }); + progressObserver.observe($('#progress-bar'), { attributeFilter: ["value"] }) +} diff --git a/providers/song-info.js b/providers/song-info.js index ddeee4cd..330e0d7c 100644 --- a/providers/song-info.js +++ b/providers/song-info.js @@ -19,6 +19,7 @@ const songInfo = { songDuration: 0, elapsedSeconds: 0, url: "", + album: undefined, videoId: "", playlistId: "", }; @@ -57,6 +58,7 @@ const handleData = async (responseText, win) => { songInfo.elapsedSeconds = videoDetails.elapsedSeconds; songInfo.isPaused = videoDetails.isPaused; songInfo.videoId = videoDetails.videoId; + songInfo.album = data?.videoDetails?.album; // Will be undefined if video exist const oldUrl = songInfo.imageSrc; songInfo.imageSrc = videoDetails.thumbnail?.thumbnails?.pop()?.url.split("?")[0];