From a689980049db93167a9d48acf6e22235f3788fcb Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Tue, 13 Feb 2024 11:39:23 +0900 Subject: [PATCH] feat: start implementing chromecast API --- package.json | 4 +- patches/@astronautlabs__mdns@1.0.10.patch | 33 ++++++++ pnpm-lock.yaml | 98 +++++++++++++++++++++++ src/electron-chromecast.d.ts | 14 ++++ src/preload.ts | 4 + src/renderer.ts | 2 + src/reset.d.ts | 1 + src/youtube-music.css | 5 -- 8 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 patches/@astronautlabs__mdns@1.0.10.patch create mode 100644 src/electron-chromecast.d.ts diff --git a/package.json b/package.json index fa828506..786ff154 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,8 @@ }, "patchedDependencies": { "vudio@2.1.1": "patches/vudio@2.1.1.patch", - "@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch" + "@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch", + "@astronautlabs/mdns@1.0.10": "patches/@astronautlabs__mdns@1.0.10.patch" } }, "dependencies": { @@ -145,6 +146,7 @@ "@floating-ui/dom": "1.6.3", "@foobar404/wave": "2.0.5", "@jellybrick/electron-better-web-request": "1.0.4", + "@jellybrick/electron-chromecast": "1.2.1", "@jellybrick/mpris-service": "2.1.4", "@xhayper/discord-rpc": "1.1.2", "async-mutex": "0.4.1", diff --git a/patches/@astronautlabs__mdns@1.0.10.patch b/patches/@astronautlabs__mdns@1.0.10.patch new file mode 100644 index 00000000..2f500087 --- /dev/null +++ b/patches/@astronautlabs__mdns@1.0.10.patch @@ -0,0 +1,33 @@ +diff --git a/dist/Responder.js b/dist/Responder.js +index 7bb0e4e51f131cf257efc44190cf892ca0f1414e..f71688b8d7dc85bb6e23c9ec2aeec5cb98adc70a 100644 +--- a/dist/Responder.js ++++ b/dist/Responder.js +@@ -32,6 +32,7 @@ const StateMachine_1 = require("./StateMachine"); + const Probe_1 = require("./Probe"); + const Response_1 = require("./Response"); + const constants_1 = require("./constants"); ++const { setTimeout, clearTimeout } = require('node:timers'); + const ONE_SECOND = 1000; + /** + * Make ids, just to keep track of which responder is which in debug messages +@@ -43,7 +44,7 @@ const uniqueId = () => `id#${++counter}`; + * a responder has more than 15 conflicts in a small window then the responder + * should be throttled to prevent it from spamming everyone. Conflict count + * gets cleared after 15s w/o any conflicts +- */ ++*/ + class ConflictCounter { + constructor() { + this._count = 0; +diff --git a/dist/sleep.js b/dist/sleep.js +index 8e11b3900747a68814697943ec16af3280bca8b3..7896d16b43d3eb8fff175c30ea5903d6237cc634 100644 +--- a/dist/sleep.js ++++ b/dist/sleep.js +@@ -5,6 +5,7 @@ + Object.defineProperty(exports, "__esModule", { value: true }); + exports.resetSleep = exports.sleep = void 0; + const node_events_1 = require("node:events"); ++const { setInterval, clearInterval } = require('node:timers'); + exports.sleep = new node_events_1.EventEmitter(); + exports.sleep.setMaxListeners(100); + let interval; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ddc4f1e7..c7217edd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ overrides: '@babel/runtime': 7.23.8 patchedDependencies: + '@astronautlabs/mdns@1.0.10': + hash: 7fg7wajlckigozdqgqhsj6kfd4 + path: patches/@astronautlabs__mdns@1.0.10.patch '@xhayper/discord-rpc@1.1.2': hash: 7eeaht6k4r7cw3nunras7mx7iu path: patches/@xhayper__discord-rpc@1.1.2.patch @@ -48,6 +51,9 @@ dependencies: '@jellybrick/electron-better-web-request': specifier: 1.0.4 version: 1.0.4 + '@jellybrick/electron-chromecast': + specifier: 1.2.1 + version: 1.2.1 '@jellybrick/mpris-service': specifier: 2.1.4 version: 2.1.4 @@ -294,6 +300,11 @@ packages: resolution: {integrity: sha512-+PVTOfla/0XMLRTQLJFPg4u40XcdTfon6GGea70hBGi8Pd7ZymIXyVUR+vK8wt5Jb4MVKTKPIz43Myyebw5mZA==} dev: false + /@astronautlabs/mdns@1.0.10(patch_hash=7fg7wajlckigozdqgqhsj6kfd4): + resolution: {integrity: sha512-9bVdQ15Tbzpor1z/bteaemC9NvRDZgMD4Cfbu1/r/6Zv6fCBXvIJJoJ6Dh/BMvTlAiT2Xewm36i0VWRnZ4yw6A==} + dev: false + patched: true + /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -1234,6 +1245,15 @@ packages: uuid: 9.0.1 dev: false + /@jellybrick/electron-chromecast@1.2.1: + resolution: {integrity: sha512-3w/Z9Q9/A/HLWOAE6yBCm0EzDPi1Tk6MoCtE0Fn7/g+g8P9XPlOyzMf9Q/VQaZ+p6JR3iO7iDiYo5uv/ULmWRQ==} + dependencies: + '@astronautlabs/mdns': 1.0.10(patch_hash=7fg7wajlckigozdqgqhsj6kfd4) + castv2: github.com/metaquanta/node-castv2/71419bd736ac859b18fb84a2c459756f2b9ccb5a + transitivePeerDependencies: + - supports-color + dev: false + /@jellybrick/mpris-service@2.1.4: resolution: {integrity: sha512-OwSxYeRRss7+ZhZs/n6D0LjUMWp1QIrAfzBZA6zGs62x80QIQlpeMXO2GKxC6UNyi87wJTiSWsUGDM1jO4eCtQ==} dependencies: @@ -1362,6 +1382,49 @@ packages: resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} dev: true + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + /@remusao/guess-url-type@1.2.1: resolution: {integrity: sha512-rbOqre2jW8STjheOsOaQHLgYBaBZ9Owbdt8NO7WvNZftJlaG3y/K9oOkl8ZUpuFBisIhmBuMEW6c+YrQl5inRA==} dev: false @@ -1696,6 +1759,10 @@ packages: dependencies: '@types/node': 20.11.0 + /@types/long@4.0.2: + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + dev: false + /@types/minimist@1.2.5: resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} dev: true @@ -5598,6 +5665,26 @@ packages: err-code: 2.0.3 retry: 0.12.0 + /protobufjs@6.11.4: + resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} + hasBin: true + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/long': 4.0.2 + '@types/node': 20.11.0 + long: 4.0.0 + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -6899,6 +6986,17 @@ packages: undici: 5.28.2 dev: false + github.com/metaquanta/node-castv2/71419bd736ac859b18fb84a2c459756f2b9ccb5a: + resolution: {tarball: https://codeload.github.com/metaquanta/node-castv2/tar.gz/71419bd736ac859b18fb84a2c459756f2b9ccb5a} + name: castv2 + version: 0.1.10 + dependencies: + debug: 4.3.4 + protobufjs: 6.11.4 + transitivePeerDependencies: + - supports-color + dev: false + github.com/organization/Simple-YouTube-Age-Restriction-Bypass/4e2db89ccb2fb880c5110add9ff3f1dfb78d0ff6: resolution: {tarball: https://codeload.github.com/organization/Simple-YouTube-Age-Restriction-Bypass/tar.gz/4e2db89ccb2fb880c5110add9ff3f1dfb78d0ff6} name: simple-youtube-age-restriction-bypass diff --git a/src/electron-chromecast.d.ts b/src/electron-chromecast.d.ts new file mode 100644 index 00000000..2431d01c --- /dev/null +++ b/src/electron-chromecast.d.ts @@ -0,0 +1,14 @@ +declare module '@jellybrick/electron-chromecast' { + export const chrome: typeof window.chrome; + export const requestHandler: (receiverList: Array) => Promise; + export const castSetting: { + devMode: boolean; + }; + export const castConsole: { + log: (message: unknown[]) => void; + info: (message: unknown[]) => void; + warn: (message: unknown[]) => void; + error: (message: unknown[]) => void; + }; + export const injectChromeCompatToObject: (obj: object) => void; +} diff --git a/src/preload.ts b/src/preload.ts index 0283190a..06110f22 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,6 +1,8 @@ import { contextBridge, ipcRenderer, IpcRendererEvent, webFrame } from 'electron'; import is from 'electron-is'; +import { injectChromeCompatToObject, chrome } from '@jellybrick/electron-chromecast'; + import config from './config'; import { @@ -53,6 +55,8 @@ contextBridge.exposeInMainWorld( 'ELECTRON_RENDERER_URL', process.env.ELECTRON_RENDERER_URL, ); +injectChromeCompatToObject(global); +contextBridge.exposeInMainWorld('caster', chrome); const [path, script] = ipcRenderer.sendSync('get-renderer-script') as [string | null, string]; let blocked = true; diff --git a/src/renderer.ts b/src/renderer.ts index c023d93f..1b4b510f 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -16,6 +16,8 @@ import { loadI18n, setLanguage, t as i18t } from '@/i18n'; import type { PluginConfig } from '@/types/plugins'; import type { YoutubePlayer } from '@/types/youtube-player'; +window.chrome.cast = window.caster.cast; + let api: (Element & YoutubePlayer) | null = null; let isPluginLoaded = false; let isApiLoaded = false; diff --git a/src/reset.d.ts b/src/reset.d.ts index f9d0390b..53801838 100644 --- a/src/reset.d.ts +++ b/src/reset.d.ts @@ -22,6 +22,7 @@ declare global { ipcRenderer: typeof electronIpcRenderer; mainConfig: typeof config; electronIs: typeof is; + caster: typeof window.chrome; ELECTRON_RENDERER_URL: string | undefined; /** * YouTube Music internal variable (Last interaction time) diff --git a/src/youtube-music.css b/src/youtube-music.css index 14471188..ff4fd6d2 100644 --- a/src/youtube-music.css +++ b/src/youtube-music.css @@ -35,11 +35,6 @@ img { user-select: none; } -/* Hide cast button which doesn't work */ -ytmusic-cast-button { - display: none !important; -} - /* Remove useless inaccessible button on top-right corner of the video player */ .ytp-chrome-top-buttons { display: none !important;