您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
2/23/2025, 11:35:22 AM
// ==UserScript== // @name YTMusic Audio Device Selector // @namespace Violentmonkey Scripts // @match https://music.youtube.com/* // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @grant none // @version 1.2 // @author DoKM (https://github.com/DoKM) // @description 2/23/2025, 11:35:22 AM // @run-at document-end // @homepageURL https://github.com/DoKM/Youtube-Music-Audio-Device-Selector // @license MIT // ==/UserScript== (async function () { let audioDevices = []; let defaultAudioDevice; let defaultSpeaker; let currentDevice; let dropdown; const menuID = window.location.hostname === "music.youtube.com" ? "right-content" : "end"; const starFilled = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg>`; const speakerFilled = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>`; async function init() { return new Promise(async (resolve, reject) => { if (window.trustedTypes && window.trustedTypes.createPolicy) { window.trustedTypes.createPolicy('default', { createHTML: string => string }); } const constrains = { audio: true, video: true } await navigator.mediaDevices.getUserMedia(constrains) await updateDevices(); navigator.mediaDevices.addEventListener("devicechange", async () => { await updateDevices(); }); resolve(); }) } await init(); async function updateDevices() { return new Promise(async (resolve, reject) => { let tempDevices = await navigator.mediaDevices.enumerateDevices(); let getDevices = function (deviceList) { let outputDevices = []; // Fixed typo let defaultDevice = undefined; for (let device of deviceList) { if (device.kind === "audiooutput") { if (device.deviceId === "default") { defaultDevice = device; } else if (device.deviceId !== "communications" && device.deviceId != undefined) { outputDevices.push(device); } } } return { defaultDevice: defaultDevice, outputDevices: outputDevices // Fixed typo }; }; let { defaultDevice, outputDevices } = getDevices(tempDevices); audioDevices = outputDevices; defaultAudioDevice = defaultDevice; console.log(outputDevices); resolve(); }); } const musicPlayer = document.getElementsByTagName("video")[0]; { const audioDeviceID = localStorage.getItem("dokm-audio-device-favoriteDevice"); const speakerDeviceID = localStorage.getItem("dokm-audio-device-favoriteSpeaker"); if (audioDeviceID || speakerDeviceID) { for (let device of audioDevices) { if (device.deviceId === audioDeviceID) { defaultAudioDevice = device; musicPlayer.setSinkId(device.deviceId); } if (device.deviceId === speakerDeviceID) { defaultSpeaker = device; if (!audioDeviceID) { musicPlayer.setSinkId(device.deviceId); } } } } if (!currentDevice) { currentDevice = defaultAudioDevice; } } createMenu() function setCurrentDevice(device) { if (!device) { return; } currentDevice = device; musicPlayer.setSinkId(device.deviceId); } function setFavorite(device) { // save the device ID to local storage localStorage.setItem("dokm-audio-device-favoriteDevice", device.deviceId); defaultAudioDevice = device; } function setFavoriteSpeaker(device) { // save the device ID to local storage localStorage.setItem("dokm-audio-device-favoriteSpeaker", device.deviceId); defaultSpeaker = device; } function createButton(elementName, innerHTML, colour = "#fff") { // Create the button element const button = document.createElement("button"); button.classList.add(elementName); button.innerHTML = innerHTML; // Style the button for dark mode Object.assign(button.style, { background: "#222", color: "#fff", border: "none", padding: "8px", cursor: "pointer", borderRadius: "5px", display: "flex", alignItems: "center", justifyContent: "center", transition: "background 0.3s", position: "relative", width: "40px", // Default width height: "40px", // Default height marginRight: "5px", }); if (innerHTML.includes("svg")) { button.style.fill = colour; } button.addEventListener("mouseenter", () => (button.style.background = "#333")); button.addEventListener("mouseleave", () => (button.style.background = "#222")); return button; } function createDropdown(audioDevices) { const dropdown = document.createElement("div"); dropdown.classList.add("dropdown-menu"); // sort the devices, default device first, default speaker second and the rest in alphabetical order let sorted = audioDevices.sort((a, b) => { if (a.deviceId === defaultAudioDevice?.deviceId) { return -1; } else if (b.deviceId === defaultAudioDevice?.deviceId) { return 1; } else if (a.deviceId === defaultSpeaker?.deviceId) { return -1; } else if (b.deviceId === defaultSpeaker?.deviceId) { return 1; } else if (a.label < b.label) { return -1; } else if (a.label > b.label) { return 1; } else { return 0; } }); // Style the dropdown menu Object.assign(dropdown.style, { position: "absolute", top: "0", right: "110%", // Move to the left of the button background: "#333", color: "#fff", padding: "10px", borderRadius: "5px", boxShadow: "0px 2px 10px rgba(0, 0, 0, 0.2)", display: "none", minWidth: "220px", }); // Create list container const table = document.createElement("table"); table.style.width = "100%"; table.style.borderCollapse = "collapse"; table.style.marginTop = "10px"; table.style.color = "#fff"; table.style.backgroundColor = "#333"; table.style.border = "0px solid #444"; sorted.forEach((device, index) => { const tr = document.createElement("tr"); tr.setAttribute("data-device-id", device.deviceId); tr.style.cursor = "pointer"; tr.style.borderBottom = "1px solid #444"; // Highlight on hover tr.addEventListener("mouseenter", () => (tr.style.background = "#444")); tr.addEventListener("mouseleave", () => (tr.style.background = "transparent")); // Device label cell const labelCell = document.createElement("td"); const labelContainer = document.createElement("span"); labelContainer.style.display = "flex"; labelContainer.style.alignItems = "center"; const label = document.createElement("span"); label.textContent = device.label; label.style.padding = "8px"; label.style.display = "block"; labelContainer.appendChild(label); if (device.deviceId === currentDevice.deviceId) { const currentDeviceIcon = document.createElement("span"); currentDeviceIcon.innerHTML = speakerFilled; currentDeviceIcon.style.width = "40px"; currentDeviceIcon.style.fill = "#DAA520"; currentDeviceIcon.style.display = "inline-block"; currentDeviceIcon.style.verticalAlign = "middle"; // Center vertically labelContainer.style.display = "flex"; // Ensure flex container labelContainer.style.alignItems = "center"; // Center vertically labelContainer.prepend(currentDeviceIcon); } labelCell.appendChild(labelContainer); labelCell.addEventListener("click", () => { setCurrentDevice(device); }); // Star button cell const starCell = document.createElement("td"); const starButton = createButton("button", starFilled, (device === defaultAudioDevice) ? "#DAA520" : "#fff"); starButton.addEventListener("click", (e) => { //e.stopPropagation(); setFavorite(device); }); starCell.appendChild(starButton); // Speaker button cell const speakerCell = document.createElement("td"); const speakerButton = createButton("button", speakerFilled, (device === defaultSpeaker) ? "#DAA520" : "#fff"); speakerButton.addEventListener("click", (e) => { //e.stopPropagation(); setFavoriteSpeaker(device); }); speakerCell.appendChild(speakerButton); // Assemble table row tr.appendChild(labelCell); tr.appendChild(starCell); tr.appendChild(speakerCell); table.appendChild(tr); }); dropdown.appendChild(table); return dropdown; } function createMenu() { const menuLocation = document.getElementById(menuID); if (!menuLocation) { console.error(`Element with ID '${menuID}' not found.`); return; } // Example array of audio devices const outputSelectorButton = createButton("output-selector-button", `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon> <path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path> <path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path> </svg>`); // Toggle dropdown visibility on button click outputSelectorButton.addEventListener("click", (event) => { event.stopPropagation(); if (dropdown) { dropdown.remove(); dropdown = null; } else { dropdown = createDropdown(audioDevices); outputSelectorButton.appendChild(dropdown); dropdown.style.display = "block"; } }); // Close dropdown when clicking outside document.addEventListener("click", (event) => { if (dropdown && !outputSelectorButton.contains(event.target) && !dropdown.contains(event.target)) { dropdown.remove(); dropdown = null; } }); const selectDefaultButton = createButton("select-default-button", starFilled); const selectSpeakerButton = createButton("select-speaker-button", speakerFilled); selectDefaultButton.addEventListener("click", () => { setCurrentDevice(defaultAudioDevice); }); selectSpeakerButton.addEventListener("click", () => { setCurrentDevice(defaultSpeaker); }); // Append dropdown to button and insert into menu menuLocation.prepend(selectDefaultButton, selectSpeakerButton, outputSelectorButton); } })();