From bcff6e51348645395549c206717225fb16a29cda Mon Sep 17 00:00:00 2001 From: TC Date: Wed, 11 Nov 2020 11:35:58 +0100 Subject: [PATCH] Add notifications plugin (notify of song on play event) --- plugins/notifications/actions.js | 18 +++++++ plugins/notifications/back.js | 33 ++++++++++++ plugins/notifications/front.js | 86 ++++++++++++++++++++++++++++++++ plugins/utils.js | 4 +- 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 plugins/notifications/actions.js create mode 100644 plugins/notifications/back.js create mode 100644 plugins/notifications/front.js diff --git a/plugins/notifications/actions.js b/plugins/notifications/actions.js new file mode 100644 index 00000000..15e3f429 --- /dev/null +++ b/plugins/notifications/actions.js @@ -0,0 +1,18 @@ +const { triggerAction } = require("../utils"); + +const CHANNEL = "notification"; +const ACTIONS = { + NOTIFICATION: "notification", +}; + +function notify(info) { + triggerAction(CHANNEL, ACTIONS.NOTIFICATION, info); +} + +module.exports = { + CHANNEL, + ACTIONS, + global: { + notify, + }, +}; diff --git a/plugins/notifications/back.js b/plugins/notifications/back.js new file mode 100644 index 00000000..54dac482 --- /dev/null +++ b/plugins/notifications/back.js @@ -0,0 +1,33 @@ +const { nativeImage, Notification } = require("electron"); + +const { listenAction } = require("../utils"); +const { ACTIONS, CHANNEL } = require("./actions.js"); + +function notify(info) { + let notificationImage = "assets/youtube-music.png"; + if (info.image) { + notificationImage = nativeImage.createFromDataURL(info.image); + } + + const notification = { + title: info.title || "Playing", + body: info.artist, + icon: notificationImage, + silent: true, + }; + new Notification(notification).show(); +} + +function listenAndNotify() { + listenAction(CHANNEL, (event, action, imageSrc) => { + switch (action) { + case ACTIONS.NOTIFICATION: + notify(imageSrc); + break; + default: + console.log("Unknown action: " + action); + } + }); +} + +module.exports = listenAndNotify; diff --git a/plugins/notifications/front.js b/plugins/notifications/front.js new file mode 100644 index 00000000..ed07c701 --- /dev/null +++ b/plugins/notifications/front.js @@ -0,0 +1,86 @@ +let videoElement = null; +let image = null; + +const observer = new MutationObserver((mutations, observer) => { + if (!videoElement) { + videoElement = document.querySelector("video"); + } + + if (!image) { + image = document.querySelector(".ytmusic-player-bar.image"); + } + + if (videoElement !== null && image !== null) { + observer.disconnect(); + let notificationImage = null; + + videoElement.addEventListener("play", () => { + notify({ + title: getTitle(), + artist: getArtist(), + image: notificationImage, + }); + }); + + image.addEventListener("load", () => { + notificationImage = null; + const imageInBase64 = convertImageToBase64(image); + if (image && image.complete && image.naturalHeight !== 0) { + notificationImage = imageInBase64; + } + }); + } +}); + +// Convert an image (DOM element) to base64 string +const convertImageToBase64 = (image, size = 256) => { + image.setAttribute("crossorigin", "anonymous"); + + const c = document.createElement("canvas"); + c.height = size; + c.width = size; + + const ctx = c.getContext("2d"); + ctx.drawImage( + image, + 0, + 0, + image.naturalWidth, + image.naturalHeight, + 0, + 0, + c.width, + c.height + ); + + const imageInBase64 = c.toDataURL(); + return imageInBase64; +}; + +const getTitle = () => { + const title = document.querySelector(".title.ytmusic-player-bar").textContent; + return title; +}; + +const getArtist = () => { + const bar = document.querySelectorAll(".subtitle.ytmusic-player-bar")[0]; + let artist; + + if (bar.querySelectorAll(".yt-simple-endpoint.yt-formatted-string")[0]) { + artist = bar.querySelectorAll(".yt-simple-endpoint.yt-formatted-string")[0] + .textContent; + } else if (bar.querySelectorAll(".byline.ytmusic-player-bar")[0]) { + artist = bar.querySelectorAll(".byline.ytmusic-player-bar")[0].textContent; + } + + return artist; +}; + +const observeVideoAndThumbnail = () => { + observer.observe(document, { + childList: true, + subtree: true, + }); +}; + +module.exports = observeVideoAndThumbnail; diff --git a/plugins/utils.js b/plugins/utils.js index 491f1548..7a8693bc 100644 --- a/plugins/utils.js +++ b/plugins/utils.js @@ -20,8 +20,8 @@ module.exports.templatePath = (pluginPath, name) => { return path.join(pluginPath, "templates", name); }; -module.exports.triggerAction = (channel, action) => { - return ipcRenderer.send(channel, action); +module.exports.triggerAction = (channel, action, ...args) => { + return ipcRenderer.send(channel, action, ...args); }; module.exports.listenAction = (channel, callback) => {