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://greasyfork.org/en/users/85671-jcunews
// @version 1.0.1
// @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_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:
javascript:shmao_ujs()
Note:
HTML5 audio output selection is currently supported only on Chrome browser and its clones, and when the
HTML page is not accessed using "file://" URL.
*/
((si, ds, sl, err, ec, pc) => {
function errSet(e) {
alert(`Failed to set audio output device to "${sl}".\n${e}`);
}
function setOutput(el, single) {
if (si) {
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;
}
function refDevInfo() {
if (location.protocol === "file:") return err = true;
navigator.mediaDevices.getUserMedia({audio: true}).catch(() => err = true).then(() => {
if (err) return;
navigator.mediaDevices.enumerateDevices().catch(() => err = true).then((dis, ns, k) => {
if (err || (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();
});
});
}
if (!document.documentElement) return;
ds = [];
if (location.protocol !== "file:") {
sl = localStorage.shmaoAudioOutput ? localStorage.shmaoAudioOutput : "";
} else sl = "";
refDevInfo();
GM_registerMenuCommand("Select audio output device for this site", unsafeWindow.shmao_ujs = (a, j, k, l, s, t) => {
if (err) {
if (location.protocol !== "file:") {
alert("Audio output device can not be selected at this time.");
} else alert('Audio output device can not be selected for HTML pages accessed using "file://" URL.');
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]);
if (location.protocol !== "file:") localStorage.shmaoAudioOutput = sl;
si = ds[a - 1].deviceId;
} else {
sl = "";
si = "default";
if (location.protocol !== "file:") delete localStorage.shmaoAudioOutput;
}
setAll();
});
if (err) return;
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});
})();