@livepeer/core-web
Version:
Livepeer UI Kit's core web library, for adding reactive stores to video elements.
1 lines • 41.1 kB
Source Map (JSON)
{"version":3,"sources":["../../src/webrtc.ts","../../src/webrtc/shared.ts","../../src/media/utils.ts","../../src/webrtc/whep.ts","../../src/webrtc/whip.ts"],"sourcesContent":["export { createNewWHEP } from \"./webrtc/whep\";\nexport {\n attachMediaStreamToPeerConnection,\n createNewWHIP,\n getDisplayMedia,\n getMediaDevices,\n getUserMedia,\n type WebRTCConnectedPayload,\n} from \"./webrtc/whip\";\n","import { NOT_ACCEPTABLE_ERROR_MESSAGE } from \"@livepeer/core/errors\";\nimport type { AccessControlParams } from \"@livepeer/core/media\";\nimport { isClient } from \"../media/utils\";\n\n/**\n * Checks if WebRTC is supported and returns the appropriate RTCPeerConnection constructor.\n */\nexport const getRTCPeerConnectionConstructor = () => {\n // Check if the current environment is a client (browser)\n if (!isClient()) {\n return null; // If not a client, WebRTC is not supported\n }\n\n // Return the constructor for RTCPeerConnection with any vendor prefixes\n return (\n window.RTCPeerConnection ||\n window.webkitRTCPeerConnection ||\n window.mozRTCPeerConnection ||\n null // Return null if none of the constructors are available\n );\n};\n\n/**\n * Creates a new RTCPeerConnection instance with the given STUN and TURN servers.\n */\nexport function createPeerConnection(\n host: string | null,\n iceServers?: RTCIceServer | RTCIceServer[],\n): RTCPeerConnection | null {\n const RTCPeerConnectionConstructor = getRTCPeerConnectionConstructor();\n\n if (!RTCPeerConnectionConstructor) {\n throw new Error(\"No RTCPeerConnection constructor found in this browser.\");\n }\n\n // Defaults to Mist behavior\n const hostNoPort = host?.split(\":\")[0];\n const defaultIceServers = host\n ? [\n {\n urls: `stun:${hostNoPort}`,\n },\n {\n urls: `turn:${hostNoPort}`,\n username: \"livepeer\",\n credential: \"livepeer\",\n },\n ]\n : [];\n\n return new RTCPeerConnectionConstructor({\n iceServers: iceServers\n ? Array.isArray(iceServers)\n ? iceServers\n : [iceServers]\n : defaultIceServers,\n });\n}\n\nconst DEFAULT_TIMEOUT = 10000;\n\n/**\n * Performs the actual SDP exchange.\n *\n * 1. Sends the SDP offer to the server,\n * 2. Awaits the server's offer.\n *\n * SDP describes what kind of media we can send and how the server and client communicate.\n *\n * https://developer.mozilla.org/en-US/docs/Glossary/SDP\n * https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#name-protocol-operation\n */\nexport async function negotiateConnectionWithClientOffer(\n peerConnection: RTCPeerConnection | null | undefined,\n endpoint: string | null | undefined,\n ofr: RTCSessionDescription | null,\n controller: AbortController,\n accessControl: AccessControlParams,\n sdpTimeout: number | null,\n): Promise<Date> {\n if (peerConnection && endpoint && ofr) {\n /**\n * This response contains the server's SDP offer.\n * This specifies how the client should communicate,\n * and what kind of media client and server have negotiated to exchange.\n */\n const response = await postSDPOffer(\n endpoint,\n ofr.sdp,\n controller,\n accessControl,\n sdpTimeout,\n );\n if (response.ok) {\n const answerSDP = await response.text();\n await peerConnection.setRemoteDescription(\n new RTCSessionDescription({ type: \"answer\", sdp: answerSDP }),\n );\n\n const playheadUtc = response.headers.get(\"Playhead-Utc\");\n\n return new Date(playheadUtc ?? new Date());\n }\n if (response.status === 406) {\n throw new Error(NOT_ACCEPTABLE_ERROR_MESSAGE);\n }\n\n const errorMessage = await response.text();\n throw new Error(errorMessage);\n }\n\n throw new Error(\"Peer connection not defined.\");\n}\n\n/**\n * Helper function to prefer H264 codec in SDP\n */\nfunction preferCodec(sdp: string, codec: string): string {\n const lines = sdp.split(\"\\r\\n\");\n const mLineIndex = lines.findIndex((line) => line.startsWith(\"m=video\"));\n\n if (mLineIndex === -1) return sdp;\n\n const codecRegex = new RegExp(`a=rtpmap:(\\\\d+) ${codec}(/\\\\d+)+`);\n const codecLine = lines.find((line) => codecRegex.test(line));\n\n if (!codecLine) return sdp;\n\n // biome-ignore lint/style/noNonNullAssertion: todo: fix this\n const codecPayload = codecRegex.exec(codecLine)![1];\n const mLineElements = lines[mLineIndex].split(\" \");\n\n const reorderedMLine = [\n ...mLineElements.slice(0, 3),\n codecPayload,\n ...mLineElements.slice(3).filter((payload) => payload !== codecPayload),\n ];\n\n lines[mLineIndex] = reorderedMLine.join(\" \");\n return lines.join(\"\\r\\n\");\n}\n\n/**\n * Constructs the client's SDP offer with H264 codec preference\n *\n * SDP describes what kind of media we can send and how the server and client communicate.\n *\n * https://developer.mozilla.org/en-US/docs/Glossary/SDP\n * https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#name-protocol-operation\n */\nexport async function constructClientOffer(\n peerConnection: RTCPeerConnection | null | undefined,\n endpoint: string | null | undefined,\n noIceGathering?: boolean,\n) {\n if (peerConnection && endpoint) {\n // Override createOffer to include H264 codec preference\n const originalCreateOffer = peerConnection.createOffer.bind(peerConnection);\n // @ts-ignore (TODO: fix this)\n peerConnection.createOffer = async function (...args) {\n // @ts-ignore (TODO: fix this)\n const originalOffer = await originalCreateOffer.apply(this, args);\n return new RTCSessionDescription({\n // @ts-ignore (TODO: fix this)\n type: originalOffer.type,\n // @ts-ignore (TODO: fix this)\n sdp: preferCodec(originalOffer.sdp, \"H264\"),\n });\n };\n\n /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer */\n const offer = await peerConnection.createOffer();\n\n /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription */\n await peerConnection.setLocalDescription(offer);\n\n /** Wait for ICE gathering to complete */\n if (noIceGathering) {\n return peerConnection.localDescription;\n }\n const ofr = await waitToCompleteICEGathering(peerConnection);\n if (!ofr) {\n throw Error(\"failed to gather ICE candidates for offer\");\n }\n\n return ofr;\n }\n\n return null;\n}\n\n// Regular expression to match the playback ID at the end of the URL\n// It looks for a string that follows the last \"+\" or \"/\" and continues to the end of the pathname\nconst playbackIdPattern = /([/+])([^/+?]+)$/;\nconst REPLACE_PLACEHOLDER = \"PLAYBACK_ID\";\n\nconst MAX_REDIRECT_CACHE_SIZE = 10;\nconst redirectUrlCache = new Map<string, URL>();\n\nfunction getCachedTemplate(key: string): URL | undefined {\n const cachedItem = redirectUrlCache.get(key);\n\n if (cachedItem) {\n redirectUrlCache.delete(key);\n redirectUrlCache.set(key, cachedItem);\n }\n\n return cachedItem;\n}\n\nfunction setCachedTemplate(key: string, value: URL): void {\n if (redirectUrlCache.has(key)) {\n redirectUrlCache.delete(key);\n } else if (redirectUrlCache.size >= MAX_REDIRECT_CACHE_SIZE) {\n const oldestKey = redirectUrlCache.keys().next().value;\n if (oldestKey) {\n redirectUrlCache.delete(oldestKey);\n }\n }\n\n redirectUrlCache.set(key, value);\n}\n\nasync function postSDPOffer(\n endpoint: string,\n data: string,\n controller: AbortController,\n accessControl: AccessControlParams,\n sdpTimeout: number | null,\n) {\n const id = setTimeout(\n () => controller.abort(),\n sdpTimeout ?? DEFAULT_TIMEOUT,\n );\n\n const urlForPost = new URL(endpoint);\n const parsedMatches = urlForPost.pathname.match(playbackIdPattern);\n const currentPlaybackId = parsedMatches?.[2];\n\n const cachedTemplateUrl = getCachedTemplate(endpoint);\n\n // if we both have a cached redirect URL and a match for the playback ID,\n // use these to shortcut the typical webrtc redirect flow\n if (cachedTemplateUrl && currentPlaybackId) {\n urlForPost.host = cachedTemplateUrl.host;\n urlForPost.pathname = cachedTemplateUrl.pathname.replace(\n REPLACE_PLACEHOLDER,\n currentPlaybackId,\n );\n urlForPost.search = cachedTemplateUrl.search;\n }\n\n const response = await fetch(urlForPost.toString(), {\n method: \"POST\",\n mode: \"cors\",\n headers: {\n \"content-type\": \"application/sdp\",\n ...(accessControl?.accessKey\n ? {\n \"Livepeer-Access-Key\": accessControl.accessKey,\n }\n : {}),\n ...(accessControl?.jwt\n ? {\n \"Livepeer-Jwt\": accessControl.jwt,\n }\n : {}),\n },\n body: data,\n signal: controller.signal,\n });\n\n clearTimeout(id);\n\n return response;\n}\n\nexport async function getRedirectUrl(\n endpoint: string,\n abortController: AbortController,\n timeout: number | null,\n) {\n try {\n const cachedTemplateUrl = getCachedTemplate(endpoint);\n\n if (cachedTemplateUrl) {\n const currentIngestUrl = new URL(endpoint);\n const matches = currentIngestUrl.pathname.match(playbackIdPattern);\n const currentPlaybackId = matches?.[2];\n\n if (currentPlaybackId) {\n const finalRedirectUrl = new URL(cachedTemplateUrl);\n finalRedirectUrl.pathname = cachedTemplateUrl.pathname.replace(\n REPLACE_PLACEHOLDER,\n currentPlaybackId,\n );\n return finalRedirectUrl;\n }\n }\n\n const id = setTimeout(\n () => abortController.abort(),\n timeout ?? DEFAULT_TIMEOUT,\n );\n\n const response = await fetch(endpoint, {\n method: \"HEAD\",\n signal: abortController.signal,\n });\n\n // consume response body\n await response.text();\n\n clearTimeout(id);\n\n const actualRedirectedUrl = new URL(response.url);\n\n if (actualRedirectedUrl) {\n const templateForCache = new URL(actualRedirectedUrl);\n templateForCache.pathname = templateForCache.pathname.replace(\n playbackIdPattern,\n `$1${REPLACE_PLACEHOLDER}`,\n );\n\n if (\n !templateForCache.searchParams.has(\"ingestpb\") ||\n templateForCache.searchParams.get(\"ingestpb\") !== \"true\"\n ) {\n setCachedTemplate(endpoint, templateForCache);\n }\n }\n return actualRedirectedUrl;\n // biome-ignore lint/correctness/noUnusedVariables: ignored using `--suppress`\n } catch (e) {\n return null;\n }\n}\n\n/**\n * Receives an RTCPeerConnection and waits until\n * the connection is initialized or a timeout passes.\n *\n * https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#section-4.1\n * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceGatheringState\n * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/icegatheringstatechange_event\n */\nasync function waitToCompleteICEGathering(peerConnection: RTCPeerConnection) {\n return new Promise<RTCSessionDescription | null>((resolve) => {\n /** Wait at most five seconds for ICE gathering. */\n setTimeout(() => {\n resolve(peerConnection.localDescription);\n }, 5000);\n peerConnection.onicegatheringstatechange = (_ev) => {\n if (peerConnection.iceGatheringState === \"complete\") {\n resolve(peerConnection.localDescription);\n }\n };\n });\n}\n\n/**\n * Parses the ICE servers from the `Link` headers returned during SDP negotiation.\n */\n// function parseIceServersFromLinkHeader(\n// iceString: string | null,\n// ): NonNullable<RTCConfiguration['iceServers']> | null {\n// try {\n// const servers = iceString\n// ?.split(', ')\n// .map((serverStr) => {\n// const parts = serverStr.split('; ');\n// const server: NonNullable<RTCConfiguration['iceServers']>[number] = {\n// urls: '',\n// };\n\n// for (const part of parts) {\n// if (part.startsWith('stun:') || part.startsWith('turn:')) {\n// server.urls = part;\n// } else if (part.startsWith('username=')) {\n// server.username = part.slice('username=\"'.length, -1);\n// } else if (part.startsWith('credential=')) {\n// server.credential = part.slice('credential=\"'.length, -1);\n// }\n// }\n\n// return server;\n// })\n// .filter((server) => server.urls);\n\n// return servers && (servers?.length ?? 0) > 0 ? servers : null;\n// } catch (e) {\n// console.error(e);\n// }\n\n// return null;\n// }\n","import type { Src } from \"@livepeer/core/media\";\nimport { noop } from \"@livepeer/core/utils\";\n\nexport const isClient = () => typeof window !== \"undefined\";\nexport const ua = () =>\n isClient() ? window?.navigator?.userAgent?.toLowerCase() : \"\";\nexport const isIos = () => /iphone|ipad|ipod|ios|CriOS|FxiOS/.test(ua());\nexport const isAndroid = () => /android/.test(ua());\nexport const isMobile = () => isClient() && (isIos() || isAndroid());\nexport const isIphone = () =>\n isClient() && /(iPhone|iPod)/gi.test(window?.navigator?.platform);\nexport const isFirefox = () => /firefox/.test(ua());\nexport const isChrome = () => isClient() && !!window?.chrome;\nexport const isSafari = () =>\n Boolean(\n isClient() &&\n !isChrome() &&\n (window?.safari || isIos() || /(apple|safari)/.test(ua())),\n );\n\n/**\n * To detect autoplay, we create a video element and call play on it, if it is `paused` after\n * a `play()` call, autoplay is supported. Although this unintuitive, it works across browsers\n * and is currently the lightest way to detect autoplay without using a data source.\n *\n * @see {@link https://github.com/ampproject/amphtml/blob/9bc8756536956780e249d895f3e1001acdee0bc0/src/utils/video.js#L25}\n */\nexport const canAutoplay = (\n muted = true,\n playsinline = true,\n): Promise<boolean> => {\n if (!isClient()) return Promise.resolve(false);\n\n const video = document.createElement(\"video\");\n\n if (muted) {\n video.setAttribute(\"muted\", \"\");\n video.muted = true;\n }\n\n if (playsinline) {\n video.setAttribute(\"playsinline\", \"\");\n video.setAttribute(\"webkit-playsinline\", \"\");\n }\n\n video.setAttribute(\"height\", \"0\");\n video.setAttribute(\"width\", \"0\");\n\n video.style.position = \"fixed\";\n video.style.top = \"0\";\n video.style.width = \"0\";\n video.style.height = \"0\";\n video.style.opacity = \"0\";\n\n // Promise wrapped this way to catch both sync throws and async rejections.\n // More info: https://github.com/tc39/proposal-promise-try\n new Promise((resolve) => resolve(video.play())).catch(noop);\n\n return Promise.resolve(!video.paused);\n};\n\n/**\n * Checks if the native HTML5 video player can play the mime type.\n */\nexport const canPlayMediaNatively = (src: Src): boolean => {\n if (isClient() && src?.mime) {\n if (src?.type?.includes(\"audio\")) {\n const audio = document.createElement(\"audio\");\n return audio.canPlayType(src.mime).length > 0;\n }\n\n const video = document.createElement(\"video\");\n return video.canPlayType(src.mime).length > 0;\n }\n\n return true;\n};\n","import type { AccessControlParams } from \"@livepeer/core/media\";\n\nimport {\n constructClientOffer,\n createPeerConnection,\n getRedirectUrl,\n negotiateConnectionWithClientOffer,\n} from \"./shared\";\n\nexport const VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE =\n \"data-livepeer-video-whep-initialized\";\n\n/**\n * Client that uses WHEP to play back video over WebRTC.\n *\n * https://www.ietf.org/id/draft-murillo-whep-00.html\n */\nexport const createNewWHEP = <TElement extends HTMLMediaElement>({\n source,\n element,\n callbacks,\n accessControl,\n sdpTimeout,\n iceServers,\n}: {\n source: string;\n element: TElement;\n callbacks: {\n onConnected?: () => void;\n onPlaybackOffsetUpdated?: (d: number) => void;\n onError?: (data: Error) => void;\n onRedirect?: (url: string | null) => void;\n };\n accessControl: AccessControlParams;\n sdpTimeout: number | null;\n iceServers?: RTCIceServer | RTCIceServer[];\n}): {\n destroy: () => void;\n} => {\n // do not attach twice\n if (element.getAttribute(VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE) === \"true\") {\n return {\n destroy: () => {\n //\n },\n };\n }\n\n element.setAttribute(VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE, \"true\");\n\n let destroyed = false;\n\n const abortController = new AbortController();\n\n let peerConnection: RTCPeerConnection | null = null;\n const stream = new MediaStream();\n\n const errorComposed = (e: Error) => {\n callbacks?.onError?.(e as Error);\n\n if (element) {\n element.srcObject = null;\n }\n };\n\n getRedirectUrl(source, abortController, sdpTimeout)\n .then((redirectUrl) => {\n if (destroyed || !redirectUrl) {\n return;\n }\n\n const redirectUrlString = redirectUrl.toString();\n\n callbacks?.onRedirect?.(redirectUrlString ?? null);\n\n /**\n * Create a new WebRTC connection, using public STUN servers with ICE,\n * allowing the client to discover its own IP address.\n * https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols#ice\n */\n peerConnection = createPeerConnection(redirectUrl.host, iceServers);\n\n if (peerConnection) {\n /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver */\n peerConnection.addTransceiver(\"video\", {\n direction: \"recvonly\",\n });\n peerConnection.addTransceiver(\"audio\", {\n direction: \"recvonly\",\n });\n\n /**\n * When new tracks are received in the connection, store local references,\n * so that they can be added to a MediaStream, and to the <video> element.\n *\n * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/track_event\n */\n peerConnection.ontrack = (event) => {\n if (destroyed) {\n return;\n }\n\n try {\n if (stream) {\n const track = event.track;\n const currentTracks = stream.getTracks();\n const streamAlreadyHasVideoTrack = currentTracks.some(\n (track) => track.kind === \"video\",\n );\n const streamAlreadyHasAudioTrack = currentTracks.some(\n (track) => track.kind === \"audio\",\n );\n switch (track.kind) {\n case \"video\":\n if (streamAlreadyHasVideoTrack) {\n break;\n }\n stream.addTrack(track);\n break;\n case \"audio\":\n if (streamAlreadyHasAudioTrack) {\n break;\n }\n stream.addTrack(track);\n break;\n default:\n console.log(`received unknown track ${track}`);\n }\n }\n } catch (e) {\n errorComposed(e as Error);\n }\n };\n\n peerConnection.addEventListener(\"connectionstatechange\", (_ev) => {\n if (destroyed) {\n return;\n }\n\n try {\n if (peerConnection?.connectionState === \"failed\") {\n callbacks?.onError?.(new Error(\"Failed to connect to peer.\"));\n }\n\n if (\n peerConnection?.connectionState === \"connected\" &&\n !element.srcObject\n ) {\n element.srcObject = stream;\n callbacks?.onConnected?.();\n }\n } catch (e) {\n errorComposed(e as Error);\n }\n });\n\n peerConnection.addEventListener(\"negotiationneeded\", async (_ev) => {\n if (destroyed) {\n return;\n }\n\n try {\n const ofr = await constructClientOffer(\n peerConnection,\n redirectUrlString,\n );\n\n if (destroyed) {\n return;\n }\n\n const response = await negotiateConnectionWithClientOffer(\n peerConnection,\n source,\n ofr,\n abortController,\n accessControl,\n sdpTimeout,\n );\n\n if (destroyed) {\n return;\n }\n\n const currentDate = Date.now();\n\n if (response && currentDate) {\n callbacks?.onPlaybackOffsetUpdated?.(\n currentDate - response.getTime(),\n );\n }\n } catch (e) {\n errorComposed(e as Error);\n }\n });\n }\n })\n .catch((e) => errorComposed(e as Error));\n\n return {\n destroy: () => {\n destroyed = true;\n abortController?.abort?.();\n\n peerConnection?.close?.();\n\n // Remove the WebRTC source\n if (element) {\n element.srcObject = null;\n }\n\n element?.removeAttribute?.(VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE);\n },\n };\n};\n","import { warn } from \"@livepeer/core/utils\";\nimport {\n constructClientOffer,\n createPeerConnection,\n getRedirectUrl,\n negotiateConnectionWithClientOffer,\n} from \"./shared\";\n\nconst STANDARD_FPS = 30;\n\nexport const VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE =\n \"data-livepeer-video-whip-initialized\";\n\nexport type WebRTCConnectedPayload = {\n stream: MediaStream;\n videoTransceiver: RTCRtpTransceiver;\n audioTransceiver: RTCRtpTransceiver;\n};\n\n/**\n * Client that uses WHIP to broadcast video over WebRTC.\n *\n * https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html\n */\nexport const createNewWHIP = <TElement extends HTMLMediaElement>({\n ingestUrl,\n element,\n callbacks,\n sdpTimeout,\n noIceGathering,\n iceServers,\n}: {\n ingestUrl: string;\n element: TElement;\n callbacks: {\n onRTCPeerConnection?: (payload: RTCPeerConnection) => void;\n onConnected?: () => void;\n onError?: (data: Error) => void;\n };\n sdpTimeout: number | null;\n noIceGathering?: boolean;\n iceServers?: RTCIceServer | RTCIceServer[];\n}): {\n destroy: () => void;\n} => {\n // do not attach twice\n if (element.getAttribute(VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE) === \"true\") {\n return {\n destroy: () => {\n //\n },\n };\n }\n\n element.setAttribute(VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE, \"true\");\n\n let destroyed = false;\n const abortController = new AbortController();\n\n let peerConnection: RTCPeerConnection | null = null;\n\n getRedirectUrl(ingestUrl, abortController, sdpTimeout)\n .then((redirectUrl) => {\n if (destroyed || !redirectUrl) {\n return;\n }\n\n const redirectUrlString = redirectUrl.toString().replace(\"video+\", \"\");\n\n /**\n * Create a new WebRTC connection, using public STUN servers with ICE,\n * allowing the client to discover its own IP address.\n * https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols#ice\n */\n peerConnection = createPeerConnection(redirectUrl.host, iceServers);\n\n if (peerConnection) {\n peerConnection.addEventListener(\"negotiationneeded\", async (_ev) => {\n try {\n const ofr = await constructClientOffer(\n peerConnection,\n redirectUrlString,\n noIceGathering,\n );\n\n await negotiateConnectionWithClientOffer(\n peerConnection,\n ingestUrl,\n ofr,\n abortController,\n {},\n sdpTimeout,\n );\n } catch (e) {\n callbacks?.onError?.(e as Error);\n }\n });\n\n peerConnection.addEventListener(\n \"connectionstatechange\",\n async (_ev) => {\n try {\n if (peerConnection?.connectionState === \"failed\") {\n callbacks?.onError?.(new Error(\"Failed to connect to peer.\"));\n }\n\n if (peerConnection?.connectionState === \"connected\") {\n callbacks?.onConnected?.();\n }\n } catch (e) {\n callbacks?.onError?.(e as Error);\n }\n },\n );\n\n callbacks?.onRTCPeerConnection?.(peerConnection);\n } else {\n warn(\"Could not create peer connection.\");\n }\n })\n .catch((e) => callbacks?.onError?.(e as Error));\n\n return {\n destroy: () => {\n destroyed = true;\n\n abortController?.abort?.();\n\n peerConnection?.close?.();\n\n element?.removeAttribute?.(VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE);\n },\n };\n};\n\nexport const attachMediaStreamToPeerConnection = async ({\n mediaStream,\n peerConnection,\n}: {\n mediaStream: MediaStream;\n peerConnection: RTCPeerConnection;\n}) => {\n const newVideoTrack = mediaStream?.getVideoTracks?.()?.[0] ?? null;\n const newAudioTrack = mediaStream?.getAudioTracks?.()?.[0] ?? null;\n\n const transceivers = peerConnection.getTransceivers();\n\n let videoTransceiver = transceivers.find(\n (t) => t.receiver.track.kind === \"video\",\n );\n let audioTransceiver = transceivers.find(\n (t) => t.receiver.track.kind === \"audio\",\n );\n\n if (newVideoTrack) {\n if (videoTransceiver) {\n // Replace existing video track\n await videoTransceiver.sender.replaceTrack(newVideoTrack);\n } else {\n // Add new video transceiver\n videoTransceiver = await peerConnection.addTransceiver(newVideoTrack, {\n direction: \"sendonly\",\n });\n }\n }\n\n if (newAudioTrack) {\n if (audioTransceiver) {\n // Replace existing audio track\n await audioTransceiver.sender.replaceTrack(newAudioTrack);\n } else {\n // Add new audio transceiver\n audioTransceiver = await peerConnection.addTransceiver(newAudioTrack, {\n direction: \"sendonly\",\n });\n }\n }\n};\n\nexport const setMediaStreamTracksStatus = async ({\n enableVideo,\n enableAudio,\n mediaStream,\n}: {\n enableVideo: boolean;\n enableAudio: boolean;\n mediaStream: MediaStream;\n}) => {\n for (const videoTrack of mediaStream.getVideoTracks()) {\n videoTrack.enabled = enableVideo;\n }\n for (const audioTrack of mediaStream.getAudioTracks()) {\n audioTrack.enabled = enableAudio;\n }\n};\n\nexport const getUserMedia = (constraints?: MediaStreamConstraints) => {\n if (typeof navigator === \"undefined\") {\n return null;\n }\n\n if (navigator?.mediaDevices?.getUserMedia) {\n // Modern browsers\n return navigator.mediaDevices.getUserMedia(constraints);\n }\n if (navigator?.getUserMedia) {\n // Older standard\n return navigator.getUserMedia(constraints);\n }\n if (navigator?.webkitGetUserMedia) {\n // Webkit browsers\n return navigator.webkitGetUserMedia(constraints);\n }\n if (navigator?.mozGetUserMedia) {\n // Mozilla browsers\n return navigator.mozGetUserMedia(constraints);\n }\n if (navigator?.msGetUserMedia) {\n // IE browsers\n return navigator.msGetUserMedia(constraints);\n }\n\n warn(\n \"getUserMedia is not supported in this environment. Check if you are in a secure (HTTPS) context - https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\",\n );\n\n return null;\n};\n\nexport const getMediaDevices = () => {\n if (typeof navigator === \"undefined\") {\n return null;\n }\n\n if (!navigator.mediaDevices) {\n warn(\n \"mediaDevices was not found in this environment. Check if you are in a secure (HTTPS) context - https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\",\n );\n return null;\n }\n\n return navigator.mediaDevices;\n};\n\nexport const getDisplayMediaExists = () => {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n\n if (!navigator?.mediaDevices?.getDisplayMedia) {\n return false;\n }\n\n return true;\n};\n\nexport const getDisplayMedia = (options?: DisplayMediaStreamOptions) => {\n if (typeof navigator === \"undefined\") {\n warn(\"getDisplayMedia does not exist in this environment.\");\n\n return null;\n }\n\n if (!navigator?.mediaDevices?.getDisplayMedia) {\n warn(\"getDisplayMedia does not exist in this environment.\");\n\n return null;\n }\n\n return navigator.mediaDevices.getDisplayMedia(options);\n};\n\n/**\n * Creates a mirrored version of a video track using a canvas element.\n * This function ensures the stream sent to the server is mirrored horizontally.\n */\nexport const createMirroredVideoTrack = (\n originalTrack: MediaStreamTrack,\n): MediaStreamTrack => {\n if (originalTrack.kind !== \"video\") {\n warn(\"Cannot mirror non-video track\");\n return originalTrack;\n }\n\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.style.position = \"absolute\";\n canvas.style.top = \"-9999px\";\n document.body.appendChild(canvas);\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n warn(\"Could not get canvas context for mirroring video\");\n return originalTrack;\n }\n\n const video = document.createElement(\"video\");\n video.srcObject = new MediaStream([originalTrack]);\n video.autoplay = true;\n video.muted = true;\n video.playsInline = true;\n\n const settings = originalTrack.getSettings();\n if (settings.width && settings.height) {\n canvas.width = settings.width;\n canvas.height = settings.height;\n }\n\n const mirroredStream = canvas.captureStream(STANDARD_FPS);\n const mirroredTrack = mirroredStream.getVideoTracks()[0];\n\n let animationFrameId: number;\n\n const drawFrame = () => {\n if (video.readyState >= 2) {\n if (\n canvas.width !== video.videoWidth ||\n canvas.height !== video.videoHeight\n ) {\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n }\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n ctx.save();\n ctx.scale(-1, 1);\n ctx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);\n ctx.restore();\n }\n\n animationFrameId = requestAnimationFrame(drawFrame);\n };\n\n video.onloadedmetadata = () => {\n if (!canvas.width || !canvas.height) {\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n }\n\n video.play().catch((e) => {\n warn(`Failed to play video in mirroring process: ${e.message}`);\n });\n\n drawFrame();\n };\n\n originalTrack.addEventListener(\"ended\", () => {\n cancelAnimationFrame(animationFrameId);\n mirroredTrack.stop();\n video.pause();\n video.srcObject = null;\n if (canvas.parentNode) {\n canvas.parentNode.removeChild(canvas);\n }\n });\n\n return mirroredTrack;\n } catch (err) {\n warn(`Error creating mirrored track: ${(err as Error).message}`);\n return originalTrack;\n }\n};\n"],"mappings":"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","names":["track","import_utils","VIDEO_WEBRTC_INITIALIZED_ATTRIBUTE"]}