@fishjam-cloud/react-client
Version:
React client library for Fishjam
96 lines (95 loc) • 4.98 kB
JavaScript
import { NOT_FOUND_ERROR, OVERCONSTRAINED_ERROR, PERMISSION_DENIED, UNHANDLED_ERROR } from "../utils/errors";
import { prepareConstraints } from "./constraints";
const defaultErrors = { audio: null, video: null };
const errorMap = {
NotFoundError: NOT_FOUND_ERROR,
OverconstrainedError: OVERCONSTRAINED_ERROR,
NotAllowedError: PERMISSION_DENIED,
};
const getSingleMedia = async (type, constraints) => {
const baseConstraints = { audio: undefined, video: undefined };
try {
const stream = await navigator.mediaDevices.getUserMedia({ ...baseConstraints, [type]: constraints });
return [stream, null];
}
catch (err) {
if (!(err instanceof DOMException))
return [null, UNHANDLED_ERROR];
return [null, errorMap[err.name] ?? UNHANDLED_ERROR];
}
};
const tryToGetAudioOnlyThenVideoOnly = async (constraints, initialError) => {
const [audioStream, audioErr] = await getSingleMedia("audio", constraints.audio);
if (audioStream)
return { stream: audioStream, errors: { video: initialError, audio: null } };
const [videoStream, videoErr] = await getSingleMedia("video", constraints.video);
return { stream: videoStream, errors: { audio: audioErr, video: videoErr } };
};
export const getAvailableMedia = async (constraints, errors = defaultErrors) => {
try {
return { stream: await navigator.mediaDevices.getUserMedia(constraints), errors };
}
catch (err) {
if (err instanceof DOMException) {
switch (err.name) {
case errors.audio?.name:
case errors.video?.name:
return { stream: null, errors };
case "NotFoundError":
return tryToGetAudioOnlyThenVideoOnly(constraints, PERMISSION_DENIED);
case "OverconstrainedError":
return getAvailableMedia({ audio: unspecifyDevice(constraints.audio), video: unspecifyDevice(constraints.video) }, { audio: OVERCONSTRAINED_ERROR, video: OVERCONSTRAINED_ERROR });
case "NotAllowedError":
return tryToGetAudioOnlyThenVideoOnly(constraints, PERMISSION_DENIED);
}
}
return { stream: null, errors: { audio: UNHANDLED_ERROR, video: UNHANDLED_ERROR } };
}
};
// Safari changes deviceId between sessions, therefore we cannot rely on deviceId for identification purposes.
// We can switch a random device that comes from safari to one that has the same label as the one used in the previous session.
export const correctDevicesOnSafari = async (stream, errors, devices, constraints, previousDevices) => {
const shouldCorrectDevices = isAnyDeviceDifferentFromLastSession(previousDevices.video, previousDevices.audio, getCurrentDevicesSettings(stream, devices));
if (!shouldCorrectDevices)
return { stream, errors };
const videoIdToStart = devices.find((info) => info.label === previousDevices.video?.label)?.deviceId;
const audioIdToStart = devices.find((info) => info.label === previousDevices.audio?.label)?.deviceId;
if (!videoIdToStart && !audioIdToStart)
return { stream, errors };
stopTracks(stream);
const exactConstraints = {
video: !errors.video && prepareConstraints(videoIdToStart, constraints.video),
audio: !errors.audio && prepareConstraints(audioIdToStart, constraints.audio),
};
return await getAvailableMedia(exactConstraints, errors);
};
const getCurrentDevicesSettings = (requestedDevices, mediaDeviceInfos) => {
const currentDevices = { videoinput: null, audioinput: null };
for (const track of requestedDevices.getTracks()) {
const settings = track.getSettings();
if (settings.deviceId) {
const currentDevice = mediaDeviceInfos.find((device) => device.deviceId == settings.deviceId);
const kind = currentDevice?.kind ?? null;
if ((currentDevice && kind === "videoinput") || kind === "audioinput") {
currentDevices[kind] = currentDevice ?? null;
}
}
}
return currentDevices;
};
const isDeviceDifferentFromLastSession = (lastDevice, currentDevice) => lastDevice && (currentDevice?.deviceId !== lastDevice.deviceId || currentDevice?.label !== lastDevice?.label);
const isAnyDeviceDifferentFromLastSession = (lastVideoDevice, lastAudioDevice, currentDevices) => !!((currentDevices?.videoinput &&
isDeviceDifferentFromLastSession(lastVideoDevice, currentDevices?.videoinput || null)) ||
(currentDevices?.audioinput &&
isDeviceDifferentFromLastSession(lastAudioDevice, currentDevices?.audioinput || null)));
const stopTracks = (requestedDevices) => {
for (const track of requestedDevices.getTracks()) {
track.stop();
}
};
const unspecifyDevice = (trackConstraints) => {
if (typeof trackConstraints === "object") {
return { ...trackConstraints, deviceId: undefined };
}
return trackConstraints;
};