您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows users to select specific audio output device for HTML5 audio / video in a per site basis.
当前为
// ==UserScript== // @name Selective HTML5 Media Audio Output for Chrome // @namespace https://gf.qytechs.cn/en/users/85671-jcunews // @version 1.1.2 // @license AGPLv3 // @author jcunews // @description Allows users to select specific audio output device for HTML5 audio / video in a per site basis. // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant unsafeWindow // @run-at document-start // ==/UserScript== /* Audio output selection can be accessed by clicking on the "Select audio output device for this site" menu of this script's entry on the Tampermonkey/Violentmonkey/Greasemonkey browser extension's popup menu. Alternatively, the audio selection menu can be provided as a bookmarklet and be added onto the browser's bookmark toolbar. The bookmark URL should be specified as: javascript:shmao_ujs() Because of the microphone permission issue noted below, this script provides additional layer of privacy protection to guard against access to microphone (and optionally the camera). The microphone permission setting can be accessed from the "Microphone permission for this site" menu of this script's entry on the Tampermonkey/Violentmonkey/Greasemonkey browser extension's popup menu. Alternatively, the microphone permission menu can be provided as a bookmarklet and be added onto the browser's bookmark toolbar. The bookmark URL should be specified as: javascript:shmaop_ujs() Notes: - HTML5 audio output selection is currently supported only on Chrome browser and its clones, and only when the HTML page is accessed using "http://" or "https://" URLs. - Due to either unclear Web API specifications, or incorrect browser implementation, permission to access microphone is required for selecting audio output device. In current browser implementation, the microphone permission covers both the audio input and output devices, regardless of the device type such as microphone or line-in. - This script has not been tested under platforms other than Windows. */ (async (u, gum, sl, sps, ds, ps, si, err, ec, pc, eas) => { function errSet(e) { alert(`Failed to set audio output device to "${sl}".\n${e}`); } function setOutput(el, single) { if (si && (ps.state === "granted")) { if (single) ec = null; el.setSinkId(si).catch(e => ec = e).finally(() => (single || !--pc) && ec && errSet(ec)); } } function setAll(es) { pc = (es = document.querySelectorAll('audio,video')).length; ec = null; es.forEach(e => setOutput(e)); } function devLabel(d) { return (/[0-9a-f]{64}/).test(d.deviceId) ? d.label : d.deviceId; } async function refDevInfo() { return gum.call(navigator.mediaDevices, {audio: true}).then(() => { return navigator.mediaDevices.enumerateDevices().then((dis, ns, k) => { if (dis.length && !dis[0].label) return err = true; ns = {}; k = ""; ds = dis.reduce((r, di, i, s) => { if (di.kind === "audiooutput") { if (ns[di.label] && (ns[di.label] !== di.groupId)) { for (i = 2; i < 99; i++) { s = di.label + " #" + i; if (!ns[s]) { di = Object.keys(di).reduce((r, k) => { r[k] = di[k]; return r; }, {}); di.label = s; break; } } } ns[di.label] = di.groupId; if (sl && (devLabel(di) === sl)) k = di.deviceId; r.push(di); } return r; }, []); if (!ds.length) return; si = k ? k : "default"; setAll(); return err = false; }, () => err = true); }, () => err = true); } function getSetting(key) { if (sps[u]) return sps[u][key]; } function setSetting(key, v) { sps[u] = sps[u] || {}; sps[u][key] = v; GM_setValue("siteSettings", JSON.stringify(sps)); } function delSetting(key) { if (!sps[u]) return; delete sps[u][key]; if (!Object.keys(sps[u]).length) delete sps[u]; GM_setValue("siteSettings", JSON.stringify(sps)); } if (!(u = location.href.match(/^https?:[/]*[^/]+/)) || !document.documentElement) { unsafeWindow.shmaop_ujs = unsafeWindow.shmao_ujs = () => {}; return; } u = u[0]; gum = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = (function(cs) { var a; if (cs.audio) { if ((!sps[u] || !("microphone" in sps[u]))) { a = prompt(`${u} wants permission to access microphone and other audio devices.\nDo you want to allow it?\n Enter "yes" and press "OK" to allow. Enter "no" and press "OK" to block. Enter anything else and press "OK" to allow this time only. Press "Cancel" to block this time only.`); if (a !== null) { if (a = a.match(/^\s*(yes|no)\s*$/i)) { if (a[1] === "no") { setSetting("microphone", false); a = false; } else if (a = a[1] === "yes") { setSetting("microphone", true); } } else a = true; } } else a = sps[u].microphone; } if (a) { return gum.apply(navigator.mediaDevices, arguments); } else { return new Promise(function(res, rej) { rej(new DOMException("Permission denied.", "NotAllowedError")); }); } }).bind(navigator.mediaDevices); GM_registerMenuCommand("Select audio output device for this site", unsafeWindow.shmao_ujs = (function(a, j, k, l, s, t) { if (err) { alert("Audio output device selection permission has not been granted."); return; } l = ds.map((d, i) => { if ((sl && (devLabel(d) === sl)) || (!sl && (d.deviceId === "default"))) j = i + 1; if (d.deviceId === "default") k = i + 1; return `[${i + 1}] ${d.label}`; }).join("\n") + "\n[0] <<Clear user selection (use the default device)>>"; if (isNaN(j)) { s = `User selected audio output device is no longer installed:\n ${sl}\n\n`; t = `[${k}] ${ds[k - 1].label}`; } else { s = ""; t = `[${j}] ${ds[j - 1].label}`; } a = prompt(`${s}Currently selected audio output device:\n ${t}\n\nPlease enter a device number to use:\n\n${l}`); if ((a === null) || !(a = a.replace(/^\s+|\s+$/g, "")) || isNaN(a = parseInt(a)) || (a < 0) || (a > ds.length)) return; if (a) { sl = devLabel(ds[a - 1]); setSetting("audioOutput", sl); si = ds[a - 1].deviceId; } else { sl = ""; si = "default"; delSetting("audioOutput"); } setAll(); }).bind(unsafeWindow)); GM_registerMenuCommand("Microphone permission for this site", unsafeWindow.shmaop_ujs = (function(a) { if (err) { alert("Audio output device selection permission has not been granted.\nThis script's microphone permission is not yet applicable."); return; } if (sps[u] && ("microphone" in sps[u])) { a = sps[u].microphone ? "Allow" : "Block"; } else a = "Ask"; a = prompt(`Microphone and other audio devices permission for\n${u} is currently set to ${a}.\n Do you want to set it to Allowed?\n Enter "yes" and press "OK" to set it to Allow. Enter "no" and press "OK" to set it to Block. Enter anything else and press "OK" to set it to Ask.`); if (a === null) return; if (a = a.match(/^\s*(yes|no)\s*$/i)) { setSetting("microphone", a[1] === "yes"); } else delSetting("microphone"); }).bind(unsafeWindow)); sps = JSON.parse(GM_getValue("siteSettings", "{}")); sl = getSetting("audioOutput", ""); ds = []; await navigator.permissions.query({name: "microphone"}).then(s => { (ps = s).addEventListener("change", () => { if (!(err = (s.state !== "granted"))) refDevInfo(); }) }); if (err === undefined) await refDevInfo(); navigator.mediaDevices.addEventListener("devicechange", refDevInfo); (new MutationObserver( recs => recs.forEach( rec => rec.addedNodes.forEach(node => { if (["AUDIO", "VIDEO"].indexOf(node.tagName) >= 0) { setOutput(node, true); node.addEventListener("play", function() { setOutput(this, true); }); } }) ) )).observe(document.documentElement, {childList: true, subtree: true}); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址