{"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"]}