UNPKG

@fishjam-cloud/react-client

Version:
96 lines (95 loc) 4.98 kB
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; };