media-utils
Version:
Opinionated collection of user media utils by @dapotatoman
196 lines (188 loc) • 6.52 kB
JavaScript
function destroyStream(stream, emitEvent) {
if (!stream)
return;
stream.getTracks().forEach((track) => {
track.stop();
stream.removeTrack(track);
if (emitEvent) {
track.dispatchEvent(new Event("ended"));
stream.dispatchEvent(new Event("removeTrack"));
}
});
}
function destroyStreams(streams, emitEvent) {
if (!(streams == null ? void 0 : streams.length))
throw new Error("No stream was passed");
streams.forEach((stream) => stream.getTracks().forEach((track) => {
track.stop();
stream.removeTrack(track);
if (emitEvent) {
track.dispatchEvent(new Event("ended"));
stream.dispatchEvent(new Event("removetrack"));
}
}));
}
function getStream(constraints) {
return navigator.mediaDevices.getUserMedia(constraints);
}
function amplifyStream(stream, level) {
const [audioTrack] = stream.getAudioTracks();
const streamClone = new MediaStream([audioTrack]);
const ctx = new AudioContext();
const source = ctx.createMediaStreamSource(streamClone);
const destination = ctx.createMediaStreamDestination();
const gainNode = ctx.createGain();
gainNode.gain.value = level;
[source, gainNode, destination].reduce((a, b) => a && a.connect(b));
const [newTrack] = destination.stream.getAudioTracks();
stream.removeTrack(audioTrack);
stream.addTrack(newTrack);
function destroy() {
destroyStream(stream);
destroyStream(streamClone);
destroyStream(destination.stream);
destination.disconnect();
source.disconnect();
if (ctx.state !== "closed")
ctx.close();
}
function setLevel(value) {
gainNode.gain.value = value;
}
return { destroy, setLevel };
}
function muxStreams(videoStream, ...audioStream) {
audioStream.forEach((stream) => stream.getAudioTracks().forEach((track) => videoStream.addTrack(track)));
return videoStream;
}
function onTracksEnded(stream, callback) {
const tracks = stream.getTracks();
const tracksRunning = tracks.map((i) => i.enabled && i.readyState === "live");
tracks.forEach((track, index) => track.addEventListener("ended", () => {
tracksRunning[index] = false;
if (!tracksRunning.find((i) => i === true))
callback();
}));
}
function onAnyTrackEnded(stream, callback, destroy) {
const tracks = stream.getTracks();
function unregister(i) {
tracks.forEach((e) => e.removeEventListener("ended", i));
if (destroy)
destroyStream(stream);
}
function listener() {
callback();
unregister(listener);
}
tracks.forEach((i) => i.addEventListener("ended", listener));
return unregister.bind(unregister, listener);
}
function getDevices(kind) {
return navigator.mediaDevices.enumerateDevices().then((devices) => {
if (kind)
return devices.filter((i) => i.kind === kind);
return devices;
});
}
function getCategorizedDevices() {
return getDevices().then((devices) => {
const list = {};
devices.forEach((i) => list[i.kind].push(i));
return list;
});
}
function getFilteredDevices(key, value) {
return getDevices().then((devices) => devices.filter((i) => (i == null ? void 0 : i[key]) === value));
}
async function getDevice(key, value, devices) {
const entries = devices || await getDevices();
return entries.filter((i) => (i == null ? void 0 : i[key]) === value);
}
class EventBus {
constructor() {
this.bus = new EventTarget();
}
on(type, listener) {
this.bus.addEventListener(type, listener);
}
once(type, listener) {
this.bus.addEventListener(type, listener, { once: true });
}
off(type, listener) {
this.bus.removeEventListener(type, listener);
}
emit(type, data) {
const evt = new CustomEvent(type, { detail: data });
this.bus.dispatchEvent(evt);
}
}
function onDeviceChange(listener, options) {
navigator.mediaDevices.addEventListener("devicechange", listener, options);
return () => navigator.mediaDevices.removeEventListener("devicechange", listener, options);
}
function watchDevice(key, value) {
const events = new EventBus();
let deviceExists = false;
const updateDevice = () => getDevice(key, value).then(([i]) => deviceExists = !!i);
(async () => {
await updateDevice();
onDeviceChange(async () => {
const device = await getDevice(key, value);
if (deviceExists && !device)
events.emit("removed");
else if (!deviceExists && device)
events.emit("added");
deviceExists = !!device;
});
})();
return events;
}
function watchDevicesList(onUpdate, immediate) {
const update = () => getDevices().then(onUpdate);
if (immediate)
update();
return onDeviceChange(update, { passive: true });
}
function requestMediaPermissions() {
return Promise.all([
navigator.mediaDevices.getUserMedia({ audio: true }),
navigator.mediaDevices.getUserMedia({ video: true }),
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
]).then((streams) => {
streams.forEach((i) => destroyStream(i));
return true;
}).catch(() => false);
}
function requestPermission(name) {
const key = { camera: "video", microphone: "audio" }[name];
return navigator.mediaDevices.getUserMedia({ [key]: true }).then(() => true).catch(() => false);
}
function watchPermission(name, onChange, immediate) {
const destroy = navigator.permissions.query({ name }).then((status) => {
if (immediate)
onChange(status);
status.addEventListener("change", () => onChange(status));
return () => status.removeEventListener("change", () => onChange(status));
});
return () => {
destroy.then((fn) => fn == null ? void 0 : fn());
};
}
function watchPermissions(names, onChange, immediate) {
const destroyFns = [];
names.forEach((name) => destroyFns.push(watchPermission(name, (i) => onChange(name, i), immediate)));
return () => destroyFns.forEach((fn) => fn == null ? void 0 : fn());
}
function hasPermission(name) {
return navigator.permissions.query({ name }).then((status) => {
return status.state === "granted";
});
}
function hasMediaPermissions(name) {
return Promise.all([
hasPermission("camera"),
hasPermission("microphone")
]).then((i) => i[0] && i[1]);
}
export { amplifyStream, destroyStream, destroyStreams, getCategorizedDevices, getDevice, getDevices, getFilteredDevices, getStream, hasMediaPermissions, hasPermission, muxStreams, onAnyTrackEnded, onDeviceChange, onTracksEnded, requestMediaPermissions, requestPermission, watchDevice, watchDevicesList, watchPermission, watchPermissions };