{"version":3,"sources":["../../src/broadcast.ts","../../src/media/controls/controller.ts","../../src/hls/hls.ts","../../src/media/utils.ts","../../src/webrtc/shared.ts","../../src/media/controls/pictureInPicture.ts","../../src/webrtc/whip.ts"],"sourcesContent":["import { PERMISSIONS_ERROR_MESSAGE } from \"@livepeer/core/errors\";\nimport type { MediaControllerStore } from \"@livepeer/core/media\";\nimport type { ClientStorage } from \"@livepeer/core/storage\";\nimport { warn } from \"@livepeer/core/utils\";\nimport {\n  createJSONStorage,\n  persist,\n  subscribeWithSelector,\n} from \"zustand/middleware\";\nimport { createStore, type StoreApi } from \"zustand/vanilla\";\nimport { isPictureInPictureSupported } from \"./media/controls\";\nimport { getRTCPeerConnectionConstructor } from \"./webrtc/shared\";\nimport {\n  attachMediaStreamToPeerConnection,\n  createMirroredVideoTrack,\n  createNewWHIP,\n  getDisplayMedia,\n  getDisplayMediaExists,\n  getMediaDevices,\n  getUserMedia,\n  // biome-ignore lint/correctness/noUnusedImports: ignored using `--suppress`\n  setMediaStreamTracksStatus,\n} from \"./webrtc/whip\";\n\nconst delay = (ms: number) => {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n};\n\nexport type BroadcastStatus = \"live\" | \"pending\" | \"idle\";\nexport type AudioDeviceId = \"default\" | `ID${string}`;\nexport type VideoDeviceId = \"default\" | \"screen\" | `ID${string}`;\n\nexport type MediaDeviceIds = {\n  audioinput: AudioDeviceId;\n  videoinput: VideoDeviceId;\n};\n\nexport type MediaDeviceInfoExtended = Omit<\n  MediaDeviceInfo,\n  \"label\" | \"toJSON\"\n> & {\n  /**\n   * This is a convenience field added to MediaDeviceInfo to help easily add a device picker.\n   *\n   * For security reasons, the label field will blank unless an active media stream exists\n   * or the user has granted persistent permission for media device access. The set of device labels\n   * could otherwise be used as part of a fingerprinting mechanism to identify a user.\n   *\n   * When the label field is not blank, these are the same value. Otherwise, the value is a friendly default.\n   */\n  friendlyName: string;\n  /**\n   * For security reasons, the label field is blank unless an active media stream exists\n   * or the user has granted persistent permission for media device access. The set of device labels\n   * could otherwise be used as part of a fingerprinting mechanism to identify a user.\n   *\n   * We override it here to be null when it is blank, for easier developer usage.\n   */\n  label: string | null;\n};\n\nexport const getBroadcastDeviceInfo = (\n  version: string,\n): BroadcastDeviceInformation => ({\n  version,\n\n  isMediaDevicesSupported: Boolean(getMediaDevices()),\n  isRTCPeerConnectionSupported: Boolean(getRTCPeerConnectionConstructor()),\n  isDisplayMediaSupported: Boolean(getDisplayMediaExists()),\n});\n\nexport type BroadcastDeviceInformation = {\n  version: string;\n\n  /** If the environment supports mediaDevices */\n  isMediaDevicesSupported: boolean;\n  /** If the environment supports RTCPeerConnection */\n  isRTCPeerConnectionSupported: boolean;\n  /** If the environment supports sharing display media */\n  isDisplayMediaSupported: boolean;\n};\n\nexport type BroadcastControlsState = {\n  /** The last time that a force renegotiation was requested (this is triggered on an error) */\n  requestedForceRenegotiateLastTime: number;\n  /** The last time that the device list was requested */\n  requestedUpdateDeviceListLastTime: number;\n\n  /** The requested audio input device ID */\n  requestedAudioInputDeviceId: AudioDeviceId;\n  /** The requested video input device ID */\n  requestedVideoInputDeviceId: VideoDeviceId | null;\n\n  /** The previous video input device ID, used when screenshare ends */\n  previousVideoInputDeviceId: VideoDeviceId | null;\n\n  /** The original microphone track, stored for swapping with silent track */\n  microphoneTrack: MediaStreamTrack | null;\n\n  /**\n   * The internal list of the current media devices from the browser.\n   */\n  mediaDevices: MediaDeviceInfo[] | null;\n};\n\nexport type InitialBroadcastProps = {\n  /**\n   * The aspect ratio for the container element\n   */\n  aspectRatio: number | null;\n\n  /**\n   * Whether audio is initially enabled for the broadcast.\n   *\n   * Set to false to initialize the broadcast to not request an audio track.\n   */\n  audio: boolean | Omit<MediaTrackConstraints, \"deviceId\">;\n\n  /**\n   * The creatorId for the current broadcast.\n   */\n  creatorId: string | null;\n\n  /**\n   * Whether hotkeys are enabled. Defaults to `true`. Allows users to use keyboard shortcuts for broadcast control.\n   *\n   * This is highly recommended to adhere to ARIA guidelines.\n   */\n  hotkeys: boolean;\n\n  /**\n   * Whether the WebRTC stream should attempt to initialize immediately after the user grants\n   * permission to their video/audio input.\n   *\n   * Defaults to `false`, where preview is shown and then once the stream is enabled, it sends\n   * media to the server.\n   */\n  forceEnabled?: boolean;\n\n  /**\n   * Whether video is initially enabled for the broadcast.\n   *\n   * Set to false to initialize the broadcast to not request a video track.\n   */\n  video: boolean | Omit<MediaTrackConstraints, \"deviceId\">;\n\n  /**\n   * @deprecated in favor of `iceServers`\n   *\n   * Whether to disable ICE gathering.\n   *\n   * Set to true to disable ICE gathering. This is useful for testing purposes.\n   */\n  noIceGathering?: boolean;\n\n  /**\n   * Whether to send a silent audio track if the audio is disabled.\n   *\n   * Set to true to send a silent audio track if the audio is disabled.\n   */\n  silentAudioTrack?: boolean;\n\n  /**\n   * The ICE servers to use.\n   *\n   * If not provided, the default ICE servers will be used.\n   */\n  iceServers?: RTCIceServer | RTCIceServer[];\n\n  /**\n   * Whether the video stream should be mirrored (horizontally flipped).\n   *\n   * Set to true to broadcast a mirrored view.\n   * Defaults to `false`.\n   */\n  mirrored?: boolean;\n};\n\nexport type BroadcastAriaText = {\n  audioTrigger: string;\n  start: string;\n  screenshareTrigger: string;\n  videoTrigger: string;\n};\n\nexport type BroadcastState = {\n  /** The ARIA text for the current state. */\n  aria: BroadcastAriaText;\n\n  /** If the broadcast audio track is turned on. */\n  audio: boolean;\n\n  /** Whether the broadcast is currently enabled, or in \"preview\" mode. */\n  enabled: boolean;\n\n  /** Whether the broadcast store is hydrated. */\n  hydrated: boolean;\n\n  /**\n   * A list of the current media devices. This will change when permissions change, or when\n   * a user starts sharing their display.\n   */\n  mediaDevices: MediaDeviceInfoExtended[] | null;\n\n  /** The MediaStream for the current broadcast. */\n  mediaStream: MediaStream | null;\n\n  /** Whether the broadcast component is mounted. */\n  mounted: boolean;\n\n  /** The RTCPeerConnection for the current broadcast. */\n  peerConnection: RTCPeerConnection | null;\n\n  /** The status of the current broadcast. */\n  status: BroadcastStatus;\n\n  /**\n   * The WHIP ingest URL to use for the broadcast.\n   */\n  ingestUrl: string | null;\n\n  /** If the broadcast video track is turned on. */\n  video: boolean;\n\n  /** The currently selected media devices. */\n  mediaDeviceIds: MediaDeviceIds;\n\n  /** The initial props passed into the component. */\n  __initialProps: InitialBroadcastProps;\n  /** The broadcast device information and support. */\n  __device: BroadcastDeviceInformation;\n  /** The controls state. */\n  __controls: BroadcastControlsState;\n\n  __controlsFunctions: {\n    requestDeviceListInfo: () => void;\n    requestForceRenegotiate: () => void;\n    requestMediaDeviceId: (\n      deviceId: AudioDeviceId,\n      type: keyof MediaDeviceIds,\n    ) => void;\n    rotateAudioSource: () => void;\n    rotateVideoSource: () => void;\n    setIngestUrl: (ingestUrl: string) => void;\n    setInitialState: (\n      ids: MediaDeviceIds,\n      audio: boolean,\n      video: boolean,\n    ) => void;\n    setPeerConnection: (peerConnection: RTCPeerConnection) => void;\n    setStatus: (status: BroadcastStatus) => void;\n    setMediaDeviceIds: (mediaDevices: Partial<MediaDeviceIds>) => void;\n    toggleAudio: () => void;\n    toggleDisplayMedia: () => void;\n    toggleEnabled: () => void;\n    toggleVideo: () => void;\n    updateDeviceList: (mediaDevices: MediaDeviceInfo[]) => void;\n    updateMediaStream: (mediaStream: MediaStream) => void;\n  };\n};\n\nexport type BroadcastStore = StoreApi<BroadcastState> & {\n  subscribe: {\n    (\n      listener: (\n        selectedState: BroadcastState,\n        previousSelectedState: BroadcastState,\n      ) => void,\n    ): () => void;\n    <U>(\n      selector: (state: BroadcastState) => U,\n      listener: (selectedState: U, previousSelectedState: U) => void,\n      options?: {\n        equalityFn?: (a: U, b: U) => boolean;\n        fireImmediately?: boolean;\n      },\n    ): () => void;\n  };\n  persist: {\n    onFinishHydration: (fn: (state: BroadcastState) => void) => () => void;\n  };\n};\n\nexport const createBroadcastStore = ({\n  ingestUrl,\n  device,\n  storage,\n  initialProps,\n}: {\n  ingestUrl: string | null | undefined;\n  device: BroadcastDeviceInformation;\n  storage: ClientStorage;\n  initialProps: Partial<InitialBroadcastProps>;\n}): { store: BroadcastStore; destroy: () => void } => {\n  const initialControls: BroadcastControlsState = {\n    requestedUpdateDeviceListLastTime: 0,\n    requestedForceRenegotiateLastTime: 0,\n    requestedAudioInputDeviceId: \"default\",\n    requestedVideoInputDeviceId: null,\n    previousVideoInputDeviceId: null,\n    mediaDevices: null,\n    microphoneTrack: null,\n  };\n\n  const store = createStore<\n    BroadcastState,\n    [\n      [\"zustand/subscribeWithSelector\", Partial<BroadcastState>],\n      [\"zustand/persist\", Partial<BroadcastState>],\n    ]\n  >(\n    subscribeWithSelector(\n      persist(\n        // biome-ignore lint/correctness/noUnusedFunctionParameters: ignored using `--suppress`\n        (set, get) => ({\n          audio: initialProps?.audio !== false,\n          video: initialProps?.video !== false,\n\n          hydrated: false,\n          mounted: false,\n\n          enabled: initialProps?.forceEnabled ?? false,\n\n          status: \"idle\",\n\n          mediaStream: null,\n          mediaDevices: null,\n          peerConnection: null,\n\n          ingestUrl: ingestUrl ?? null,\n\n          mediaDeviceIds: {\n            audioinput: \"default\",\n            videoinput: \"default\",\n          },\n\n          aria: {\n            audioTrigger:\n              initialProps?.audio === false\n                ? \"Turn audio on (space)\"\n                : \"Turn audio off (space)\",\n            start: \"Start broadcasting (b)\",\n            screenshareTrigger: \"Share screen (d)\",\n            videoTrigger:\n              initialProps?.video === false\n                ? \"Turn video on (v)\"\n                : \"Turn video off (v)\",\n          },\n\n          __initialProps: {\n            aspectRatio: initialProps?.aspectRatio ?? null,\n            audio: initialProps?.audio ?? true,\n            creatorId: initialProps.creatorId ?? null,\n            forceEnabled: initialProps?.forceEnabled ?? false,\n            hotkeys: initialProps.hotkeys ?? true,\n            ingestUrl: ingestUrl ?? null,\n            video: initialProps?.video ?? true,\n            noIceGathering: initialProps?.noIceGathering ?? false,\n            silentAudioTrack: initialProps?.silentAudioTrack ?? false,\n            iceServers: initialProps?.iceServers,\n            mirrored: initialProps?.mirrored ?? false,\n          },\n\n          __device: device,\n\n          __controls: initialControls,\n\n          __metadata: null,\n\n          __controlsFunctions: {\n            updateMediaStream: (mediaStream) =>\n              set(() => ({\n                mediaStream,\n              })),\n\n            setPeerConnection: (peerConnection) =>\n              set(() => ({\n                peerConnection,\n              })),\n\n            setIngestUrl: (ingestUrl) =>\n              set(() => ({\n                ingestUrl,\n              })),\n\n            requestForceRenegotiate: () =>\n              set(({ __controls }) => ({\n                __controls: {\n                  ...__controls,\n                  requestedForceRenegotiateLastTime: Date.now(),\n                },\n              })),\n\n            rotateAudioSource: () =>\n              set(({ mediaDeviceIds, mediaDevices, __controls }) => {\n                if (!mediaDevices) {\n                  warn(\n                    \"Could not rotate audio source, no audio media devices detected.\",\n                  );\n\n                  return {};\n                }\n\n                const audioDevices = mediaDevices.filter(\n                  (m) => m.kind === \"audioinput\",\n                );\n\n                const currentAudioInputIndex = audioDevices.findIndex(\n                  (s) => s.deviceId === mediaDeviceIds.audioinput,\n                );\n\n                // Get the next audio input device\n                const nextAudioInputDevice =\n                  audioDevices[\n                    (currentAudioInputIndex + 1) % audioDevices.length\n                  ] ?? null;\n\n                return {\n                  __controls: {\n                    ...__controls,\n                    requestedAudioInputDeviceId:\n                      nextAudioInputDevice.deviceId as AudioDeviceId,\n                  },\n                };\n              }),\n\n            rotateVideoSource: () =>\n              set(({ mediaDeviceIds, mediaDevices, __controls }) => {\n                if (!mediaDevices) {\n                  warn(\n                    \"Could not rotate video source, no video media devices detected.\",\n                  );\n\n                  return {};\n                }\n\n                const videoDevices = mediaDevices.filter(\n                  (m) => m.kind === \"videoinput\",\n                );\n\n                const currentVideoInputIndex = videoDevices.findIndex(\n                  (s) => s.deviceId === mediaDeviceIds.videoinput,\n                );\n\n                // Get the next video input device\n                const nextVideoInputDevice =\n                  videoDevices[\n                    (currentVideoInputIndex + 1) % videoDevices.length\n                  ] ?? null;\n\n                return {\n                  __controls: {\n                    ...__controls,\n                    requestedVideoInputDeviceId:\n                      nextVideoInputDevice.deviceId as VideoDeviceId,\n                  },\n                };\n              }),\n\n            toggleDisplayMedia: () =>\n              set(({ __controls, mediaDeviceIds, aria }) => {\n                if (mediaDeviceIds.videoinput === \"screen\") {\n                  return {\n                    aria: {\n                      ...aria,\n                      screenshareTrigger: \"Share screen (d)\",\n                    },\n                    __controls: {\n                      ...__controls,\n                      requestedVideoInputDeviceId:\n                        __controls.previousVideoInputDeviceId,\n                    },\n                  };\n                }\n\n                return {\n                  aria: {\n                    ...aria,\n                    screenshareTrigger: \"Stop sharing screen (d)\",\n                  },\n                  __controls: {\n                    ...__controls,\n                    previousVideoInputDeviceId: mediaDeviceIds.videoinput,\n                    requestedVideoInputDeviceId: \"screen\",\n                  },\n                };\n              }),\n\n            setInitialState: (deviceIds, audio, video) =>\n              set(({ __controls }) => ({\n                hydrated: true,\n                audio,\n                video,\n                __controls: {\n                  ...__controls,\n                  requestedAudioInputDeviceId:\n                    deviceIds?.audioinput ?? \"default\",\n                  requestedVideoInputDeviceId:\n                    deviceIds?.videoinput === \"screen\"\n                      ? \"default\"\n                      : (deviceIds?.videoinput ?? \"default\"),\n                },\n              })),\n\n            requestMediaDeviceId: (deviceId, type) =>\n              set(({ __controls }) => ({\n                __controls: {\n                  ...__controls,\n                  ...(type === \"videoinput\"\n                    ? {\n                        requestedVideoInputDeviceId: deviceId,\n                      }\n                    : type === \"audioinput\"\n                      ? {\n                          requestedAudioInputDeviceId: deviceId,\n                        }\n                      : {}),\n                },\n              })),\n\n            setStatus: (status) =>\n              set(() => ({\n                status,\n              })),\n\n            setMediaDeviceIds: (newMediaDeviceIds) =>\n              set(({ mediaDeviceIds }) => ({\n                mediaDeviceIds: {\n                  ...mediaDeviceIds,\n                  ...newMediaDeviceIds,\n                },\n              })),\n\n            updateDeviceList: (mediaDevices) =>\n              set(({ __controls }) => ({\n                __controls: {\n                  ...__controls,\n                  mediaDevices,\n                },\n              })),\n\n            requestDeviceListInfo: () =>\n              set(({ __controls }) => ({\n                __controls: {\n                  ...__controls,\n                  requestedUpdateDeviceListLastTime: Date.now(),\n                },\n              })),\n\n            toggleVideo: () =>\n              set(({ video, aria }) => ({\n                video: !video,\n                aria: {\n                  ...aria,\n                  videoTrigger: !video\n                    ? \"Turn video off (v)\"\n                    : \"Turn video on (v)\",\n                },\n              })),\n\n            toggleAudio: () =>\n              set(({ audio, aria }) => ({\n                audio: !audio,\n                aria: {\n                  ...aria,\n                  audioTrigger: !audio\n                    ? \"Turn audio off (space)\"\n                    : \"Turn audio on (space)\",\n                },\n              })),\n\n            toggleEnabled: () =>\n              set(({ enabled, aria }) => ({\n                enabled: !enabled,\n                aria: {\n                  ...aria,\n                  start: enabled\n                    ? \"Start broadcasting (b)\"\n                    : \"Stop broadcasting (b)\",\n                },\n              })),\n          },\n        }),\n        {\n          name: \"livepeer-broadcast-controller\",\n          version: 1,\n          // these values are persisted across broadcasts\n          partialize: ({ audio, video, mediaDeviceIds }) => ({\n            audio,\n            video,\n            mediaDeviceIds,\n          }),\n          storage: createJSONStorage(() => storage),\n        },\n      ),\n    ),\n  );\n\n  const destroy = store.persist.onFinishHydration(\n    ({ mediaDeviceIds, audio, video }) => {\n      store\n        .getState()\n        .__controlsFunctions.setInitialState(mediaDeviceIds, audio, video);\n    },\n  );\n\n  return { store, destroy };\n};\n\nconst MEDIA_BROADCAST_INITIALIZED_ATTRIBUTE =\n  \"data-livepeer-broadcast-initialized\";\n\nconst allKeyTriggers = [\n  \"KeyL\",\n  \"KeyV\",\n  \"KeyB\",\n  \"Space\",\n  \"KeyD\",\n  \"KeyC\",\n  \"KeyM\",\n] as const;\ntype KeyTrigger = (typeof allKeyTriggers)[number];\n\nexport const addBroadcastEventListeners = (\n  element: HTMLMediaElement,\n  store: BroadcastStore,\n  mediaStore: MediaControllerStore,\n) => {\n  const onKeyUp = (e: KeyboardEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n\n    const code = e.code as KeyTrigger;\n\n    if (allKeyTriggers.includes(code)) {\n      if (code === \"Space\" || code === \"KeyL\") {\n        store.getState().__controlsFunctions.toggleAudio();\n      } else if (code === \"KeyV\") {\n        store.getState().__controlsFunctions.toggleVideo();\n      } else if (code === \"KeyB\") {\n        store.getState().__controlsFunctions.toggleEnabled();\n      } else if (code === \"KeyD\") {\n        store.getState().__controlsFunctions.toggleDisplayMedia();\n      } else if (code === \"KeyC\") {\n        store.getState().__controlsFunctions.rotateVideoSource();\n      } else if (code === \"KeyM\") {\n        store.getState().__controlsFunctions.rotateAudioSource();\n      }\n    }\n  };\n\n  const onDeviceChange = () => {\n    store.getState().__controlsFunctions.requestDeviceListInfo();\n  };\n\n  const mediaDevices = getMediaDevices();\n\n  mediaDevices?.addEventListener?.(\"devicechange\", onDeviceChange);\n\n  const parentElementOrElement = element?.parentElement ?? element;\n\n  if (element) {\n    if (parentElementOrElement) {\n      if (store.getState().__initialProps.hotkeys) {\n        parentElementOrElement.addEventListener(\"keyup\", onKeyUp);\n        parentElementOrElement.setAttribute(\"tabindex\", \"0\");\n      }\n    }\n\n    element.setAttribute(MEDIA_BROADCAST_INITIALIZED_ATTRIBUTE, \"true\");\n  }\n\n  // add effects\n  const { destroy: destroyEffects } = addEffectsToStore(\n    element,\n    store,\n    mediaStore,\n  );\n\n  const removeHydrationListener = store.persist.onFinishHydration(\n    ({ mediaDeviceIds, audio, video }) => {\n      store\n        .getState()\n        .__controlsFunctions.setInitialState(mediaDeviceIds, audio, video);\n    },\n  );\n\n  return {\n    destroy: () => {\n      removeHydrationListener?.();\n\n      parentElementOrElement?.removeEventListener?.(\"keyup\", onKeyUp);\n\n      mediaDevices?.removeEventListener?.(\"devicechange\", onDeviceChange);\n\n      destroyEffects?.();\n\n      element?.removeAttribute?.(MEDIA_BROADCAST_INITIALIZED_ATTRIBUTE);\n    },\n  };\n};\n\ntype Cleanup = () => void | Promise<void>;\n\n// Cleanup function for whip\nlet cleanupWhip: Cleanup = () => {};\n// Cleanup function for media source\nlet cleanupMediaStream: Cleanup = () => {};\n\nconst addEffectsToStore = (\n  element: HTMLMediaElement,\n  store: BroadcastStore,\n  mediaStore: MediaControllerStore,\n) => {\n  /** MEDIA STORE SYNC LISTENERS - these one-way synchronize the playback state with the broadcast state */\n\n  // Subscribe to error count\n  const destroyErrorCount = mediaStore.subscribe(\n    ({ errorCount }) => errorCount,\n    async (errorCount) => {\n      if (errorCount > 0) {\n        const delayTime = 500 * 2 ** (errorCount - 1);\n        await delay(delayTime);\n\n        store.getState().__controlsFunctions.requestForceRenegotiate();\n      }\n    },\n  );\n\n  // Subscribe to sync the mounted states\n  const destroyMediaSyncMounted = mediaStore.subscribe(\n    ({ mounted }) => mounted,\n    async (mounted) => {\n      // we use setState here so it's clear this isn't an external function\n      store.setState({ mounted });\n    },\n  );\n\n  // Subscribe to sync the error states\n  const destroyMediaSyncError = mediaStore.subscribe(\n    ({ error }) => error,\n    async (error) => {\n      if (error?.type === \"permissions\") {\n        // we use setState here so it's clear this isn't an external function\n        store.setState((state) => ({\n          __controls: {\n            ...state.__controls,\n            requestedVideoInputDeviceId: state.mediaDeviceIds.videoinput,\n          },\n        }));\n      }\n    },\n  );\n\n  // Subscribe to media stream changes\n  const destroyPictureInPictureSupportedMonitor = store.subscribe(\n    (state) => state.mediaStream,\n    async () => {\n      const isPipSupported = isPictureInPictureSupported(element);\n\n      if (!isPipSupported) {\n        mediaStore.setState((state) => ({\n          __device: {\n            ...state.__device,\n            isPictureInPictureSupported: isPipSupported,\n          },\n        }));\n      }\n    },\n    {\n      equalityFn: (a, b) => a?.id === b?.id,\n    },\n  );\n\n  /** STORE LISTENERS - handle broadcast state */\n\n  // Subscribe to request user media\n  const destroyWhip = store.subscribe(\n    ({ enabled, ingestUrl, __controls, mounted, __initialProps }) => ({\n      enabled,\n      ingestUrl,\n      requestedForceRenegotiateLastTime:\n        __controls.requestedForceRenegotiateLastTime,\n      mounted,\n      noIceGathering: __initialProps.noIceGathering,\n      silentAudioTrack: __initialProps.silentAudioTrack,\n      iceServers: __initialProps.iceServers,\n    }),\n    async ({ enabled, ingestUrl, noIceGathering, iceServers }) => {\n      await cleanupWhip?.();\n\n      if (!enabled) {\n        return;\n      }\n\n      if (!ingestUrl) {\n        warn(\n          \"No ingest URL provided, cannot start stream. Please check the configuration passed to the Broadcast component.\",\n        );\n        return;\n      }\n\n      let unmounted = false;\n\n      const onErrorComposed = (err: Error) => {\n        if (!unmounted) {\n          mediaStore.getState().__controlsFunctions.setLive(false);\n          mediaStore.getState().__controlsFunctions?.onError?.(err);\n        }\n      };\n\n      store.getState().__controlsFunctions.setStatus(\"pending\");\n\n      const { destroy } = createNewWHIP({\n        ingestUrl,\n        element,\n        callbacks: {\n          onRTCPeerConnection: (peerConnection) => {\n            store\n              .getState()\n              .__controlsFunctions.setPeerConnection(peerConnection);\n          },\n          onConnected: () => {\n            store.getState().__controlsFunctions.setStatus(\"live\");\n            mediaStore.getState().__controlsFunctions.onError(null);\n          },\n          onError: onErrorComposed,\n        },\n        sdpTimeout: null,\n        noIceGathering,\n        iceServers,\n      });\n\n      cleanupWhip = () => {\n        unmounted = true;\n        destroy?.();\n        store.getState().__controlsFunctions.setStatus(\"idle\");\n      };\n    },\n    {\n      equalityFn: (a, b) =>\n        a.requestedForceRenegotiateLastTime ===\n          b.requestedForceRenegotiateLastTime &&\n        a.ingestUrl === b.ingestUrl &&\n        a.enabled === b.enabled &&\n        a.mounted === b.mounted,\n    },\n  );\n\n  // Subscribe to request user media\n  const destroyRequestUserMedia = store.subscribe(\n    (state) => ({\n      hydrated: state.hydrated,\n      mounted: state.mounted,\n      video: state.video,\n      audio: state.audio,\n      requestedAudioDeviceId: state.__controls.requestedAudioInputDeviceId,\n      requestedVideoDeviceId: state.__controls.requestedVideoInputDeviceId,\n      initialAudioConfig: state.__initialProps.audio,\n      initialVideoConfig: state.__initialProps.video,\n      mirrored: state.__initialProps.mirrored,\n      previousMediaStream: state.mediaStream,\n      silentAudioTrack: state.__initialProps.silentAudioTrack,\n    }),\n    async ({\n      hydrated,\n      mounted,\n      audio,\n      video,\n      requestedAudioDeviceId,\n      requestedVideoDeviceId,\n      previousMediaStream,\n      initialAudioConfig,\n      initialVideoConfig,\n      silentAudioTrack,\n      mirrored,\n    }) => {\n      try {\n        if (!mounted || !hydrated) {\n          return;\n        }\n\n        // Force audio to true if silentAudioTrack is enabled so we get a microphone track\n        const shouldRequestAudio = audio || silentAudioTrack;\n\n        if (!shouldRequestAudio && !video) {\n          console.log(\n            \"|||| FORCING VIDEO ENABLED to request getUserMedia ||||\",\n          );\n          warn(\n            \"At least one of audio and video must be requested. Overriding video to be enabled so that `getUserMedia` can be requested.\",\n          );\n\n          store.setState({ video: true });\n          video = true;\n        }\n\n        const audioConstraints =\n          typeof initialAudioConfig !== \"boolean\" ? initialAudioConfig : null;\n        const videoConstraints =\n          typeof initialVideoConfig !== \"boolean\" ? initialVideoConfig : null;\n\n        console.log(\n          \"|||| Requesting media with audio:\",\n          shouldRequestAudio,\n          \"and video:\",\n          video,\n          \"||||\",\n        );\n        const stream = await (requestedVideoDeviceId === \"screen\"\n          ? getDisplayMedia({\n              // for now, only the microphone audio track is supported - we don't support multiple\n              // discrete audio tracks\n              audio: false,\n\n              // we assume that if the user is requested to share screen, they want to enable video,\n              // and we don't listen to the `video` enabled state\n              //\n              // we apply the video constraints to the video track\n              video: videoConstraints ?? true,\n            })\n          : getUserMedia({\n              // Always request audio if silentAudioTrack is enabled\n              audio:\n                shouldRequestAudio &&\n                requestedAudioDeviceId &&\n                requestedAudioDeviceId !== \"default\"\n                  ? {\n                      ...(audioConstraints ? audioConstraints : {}),\n                      deviceId: {\n                        ideal: requestedAudioDeviceId,\n                      },\n                    }\n                  : shouldRequestAudio\n                    ? {\n                        ...(audioConstraints ? audioConstraints : {}),\n                      }\n                    : false,\n              video:\n                video &&\n                requestedVideoDeviceId &&\n                requestedVideoDeviceId !== \"default\"\n                  ? {\n                      ...(videoConstraints ? videoConstraints : {}),\n                      deviceId: {\n                        ideal: requestedVideoDeviceId,\n                      },\n                      ...(mirrored ? { facingMode: \"user\" } : {}),\n                    }\n                  : video\n                    ? {\n                        ...(videoConstraints ? videoConstraints : {}),\n                        ...(mirrored ? { facingMode: \"user\" } : {}),\n                      }\n                    : false,\n            }));\n\n        if (stream) {\n          const microphoneTrack = stream?.getAudioTracks()?.[0] ?? null;\n          if (microphoneTrack) {\n            store.setState((state) => ({\n              __controls: {\n                ...state.__controls,\n                microphoneTrack: microphoneTrack,\n              },\n            }));\n          }\n\n          // we get the device ID from the MediaStream and update those\n          const allAudioTracks = stream?.getAudioTracks() ?? [];\n          const allVideoTracks = stream?.getVideoTracks() ?? [];\n\n          const allAudioDeviceIds = allAudioTracks.map(\n            (track) => track?.getSettings()?.deviceId,\n          );\n          const allVideoDeviceIds = allVideoTracks.map(\n            (track) => track?.getSettings()?.deviceId,\n          );\n\n          const firstAudioDeviceId = (allAudioDeviceIds?.[0] ??\n            null) as AudioDeviceId | null;\n          const firstVideoDeviceId = (allVideoDeviceIds?.[0] ??\n            null) as VideoDeviceId | null;\n\n          store.getState().__controlsFunctions.setMediaDeviceIds({\n            ...(firstAudioDeviceId ? { audioinput: firstAudioDeviceId } : {}),\n            ...(firstVideoDeviceId\n              ? {\n                  videoinput:\n                    requestedVideoDeviceId === \"screen\"\n                      ? \"screen\"\n                      : firstVideoDeviceId,\n                }\n              : {}),\n          });\n\n          // merge the new audio and/or video and the old media stream\n          const mergedMediaStream = new MediaStream();\n\n          const mergedAudioTrack =\n            allAudioTracks?.[0] ??\n            previousMediaStream?.getAudioTracks?.()?.[0] ??\n            null;\n\n          let mergedVideoTrack =\n            allVideoTracks?.[0] ??\n            previousMediaStream?.getVideoTracks?.()?.[0] ??\n            null;\n\n          if (\n            mergedVideoTrack &&\n            mirrored &&\n            requestedVideoDeviceId !== \"screen\"\n          ) {\n            try {\n              const videoSettings = mergedVideoTrack.getSettings();\n              const isFrontFacing =\n                videoSettings.facingMode === \"user\" ||\n                !videoSettings.facingMode;\n\n              if (isFrontFacing) {\n                element.classList.add(\"livepeer-mirrored-video\");\n                mergedVideoTrack = createMirroredVideoTrack(mergedVideoTrack);\n              } else {\n                element.classList.remove(\"livepeer-mirrored-video\");\n              }\n            } catch (err) {\n              warn(\n                `Failed to apply video mirroring: ${(err as Error).message}`,\n              );\n            }\n          } else {\n            element.classList.remove(\"livepeer-mirrored-video\");\n          }\n\n          if (mergedAudioTrack) mergedMediaStream.addTrack(mergedAudioTrack);\n          if (mergedVideoTrack) mergedMediaStream.addTrack(mergedVideoTrack);\n\n          store\n            .getState()\n            .__controlsFunctions.updateMediaStream(mergedMediaStream);\n        }\n      } catch (e) {\n        if ((e as Error)?.name === \"NotAllowedError\") {\n          mediaStore\n            .getState()\n            .__controlsFunctions.onError(new Error(PERMISSIONS_ERROR_MESSAGE));\n        } else {\n          warn((e as Error)?.message);\n        }\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a.hydrated === b.hydrated &&\n        a.mounted === b.mounted &&\n        a.requestedAudioDeviceId === b.requestedAudioDeviceId &&\n        a.requestedVideoDeviceId === b.requestedVideoDeviceId,\n    },\n  );\n\n  // Subscribe to audio & video enabled, and media stream\n  const destroyAudioVideoEnabled = store.subscribe(\n    (state) => ({\n      audio: state.audio,\n      video: state.video,\n      mediaStream: state.mediaStream,\n      silentAudioTrack: state.__initialProps.silentAudioTrack,\n      peerConnection: state.peerConnection,\n      microphoneTrack: state.__controls.microphoneTrack,\n    }),\n    async ({\n      audio,\n      video,\n      mediaStream,\n      silentAudioTrack,\n      peerConnection,\n      microphoneTrack,\n    }) => {\n      if (!mediaStream) return;\n\n      for (const videoTrack of mediaStream.getVideoTracks()) {\n        videoTrack.enabled = video;\n      }\n\n      if (silentAudioTrack) {\n        if (peerConnection) {\n          const currentAudioTrack = mediaStream.getAudioTracks()[0];\n\n          if (!audio && microphoneTrack) {\n            // use silent track\n            if (currentAudioTrack && currentAudioTrack !== microphoneTrack) {\n              currentAudioTrack.enabled = true;\n            } else {\n              // swap in a silent track\n              const silentTrack = createSilentAudioTrack();\n\n              if (currentAudioTrack) {\n                mediaStream.removeTrack(currentAudioTrack);\n              }\n              mediaStream.addTrack(silentTrack);\n\n              // Replace in peer connection\n              const audioSender = peerConnection\n                .getSenders()\n                .find((s) => s.track && s.track.kind === \"audio\");\n              if (audioSender) {\n                await audioSender.replaceTrack(silentTrack);\n              }\n            }\n          } else if (audio && microphoneTrack) {\n            if (currentAudioTrack === microphoneTrack) {\n              microphoneTrack.enabled = true;\n            } else {\n              // swap back to microphone track\n              if (currentAudioTrack) {\n                mediaStream.removeTrack(currentAudioTrack);\n              }\n              mediaStream.addTrack(microphoneTrack);\n\n              const audioSender = peerConnection\n                .getSenders()\n                .find((s) => s.track && s.track.kind === \"audio\");\n              if (audioSender) {\n                await audioSender.replaceTrack(microphoneTrack);\n                microphoneTrack.enabled = true;\n              }\n            }\n          }\n        } else {\n          for (const audioTrack of mediaStream.getAudioTracks()) {\n            audioTrack.enabled = audio;\n          }\n        }\n      } else {\n        for (const audioTrack of mediaStream.getAudioTracks()) {\n          audioTrack.enabled = audio;\n        }\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a.audio === b.audio &&\n        a.video === b.video &&\n        a.mediaStream?.id === b.mediaStream?.id,\n    },\n  );\n\n  // Subscribe to media stream\n  const destroyPeerConnectionAndMediaStream = store.subscribe(\n    ({ mediaStream, peerConnection }) => ({ mediaStream, peerConnection }),\n    async ({ mediaStream, peerConnection }) => {\n      if (!mediaStream || !peerConnection) {\n        return;\n      }\n\n      await attachMediaStreamToPeerConnection({\n        mediaStream,\n        peerConnection,\n      });\n    },\n    {\n      equalityFn: (a, b) =>\n        a.mediaStream?.id === b.mediaStream?.id &&\n        a.peerConnection === b.peerConnection,\n    },\n  );\n\n  // Subscribe to mediastream changes\n  const destroyMediaStream = store.subscribe(\n    (state) => state.mediaStream,\n    async (mediaStream) => {\n      await cleanupMediaStream?.();\n\n      if (mediaStream) {\n        element.srcObject = mediaStream;\n\n        const togglePlay = () => {\n          mediaStore.getState().__controlsFunctions.togglePlay(true);\n        };\n\n        element.addEventListener(\"loadedmetadata\", togglePlay);\n\n        cleanupMediaStream = () => {\n          element?.removeEventListener?.(\"loadedmetadata\", togglePlay);\n\n          element.srcObject = null;\n        };\n      } else {\n        element.srcObject = null;\n      }\n    },\n    {\n      equalityFn: (a, b) => a?.id === b?.id,\n    },\n  );\n\n  // Subscribe to media devices\n  const destroyUpdateDeviceList = store.subscribe(\n    (state) => ({\n      mounted: state.mounted,\n      requestedUpdateDeviceListLastTime:\n        state.__controls.requestedUpdateDeviceListLastTime,\n    }),\n    async ({ mounted }) => {\n      if (!mounted) {\n        return;\n      }\n\n      const mediaDevices = getMediaDevices();\n      const devices = await mediaDevices?.enumerateDevices();\n\n      if (devices) {\n        store\n          .getState()\n          .__controlsFunctions.updateDeviceList(\n            devices.filter((d) => d.deviceId),\n          );\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a.mounted === b.mounted &&\n        a.requestedUpdateDeviceListLastTime ===\n          b.requestedUpdateDeviceListLastTime,\n    },\n  );\n\n  // Subscribe to media devices and map to friendly names\n  const destroyMapDeviceListToFriendly = store.subscribe(\n    (state) => ({\n      mediaDeviceIds: state.mediaDeviceIds,\n      mediaDevices: state.__controls.mediaDevices,\n    }),\n    async ({ mediaDeviceIds, mediaDevices }) => {\n      if (mediaDevices) {\n        const extendedDevices: MediaDeviceInfoExtended[] = mediaDevices\n          .filter((d) => d.deviceId)\n          .map((device, i) => ({\n            deviceId: device.deviceId,\n            kind: device.kind,\n            groupId: device.groupId,\n            label: device.label || null,\n            friendlyName:\n              device.label ??\n              `${\n                device.kind === \"audioinput\"\n                  ? \"Audio Source\"\n                  : device.kind === \"audiooutput\"\n                    ? \"Audio Output\"\n                    : \"Video Source\"\n              } ${i + 1} (${\n                device.deviceId === \"default\"\n                  ? \"default\"\n                  : device.deviceId.slice(0, 6)\n              })`,\n          }));\n\n        const isScreenshare = mediaDeviceIds.videoinput === \"screen\";\n\n        if (isScreenshare) {\n          extendedDevices.push({\n            deviceId: mediaDeviceIds.videoinput,\n            label: \"Screen share\",\n            groupId: \"none\",\n            kind: \"videoinput\",\n            friendlyName: \"Screen share\",\n          });\n        }\n\n        store.setState({\n          mediaDevices: extendedDevices,\n        });\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a.mediaDeviceIds === b.mediaDeviceIds &&\n        a.mediaDevices === b.mediaDevices,\n    },\n  );\n\n  const destroyPeerConnectionAudioHandler = store.subscribe(\n    (state) => ({\n      peerConnection: state.peerConnection,\n      audio: state.audio,\n      mediaStream: state.mediaStream,\n      silentAudioTrack: state.__initialProps.silentAudioTrack,\n      microphoneTrack: state.__controls.microphoneTrack,\n    }),\n    async ({\n      peerConnection,\n      audio,\n      mediaStream,\n      silentAudioTrack,\n      microphoneTrack,\n    }) => {\n      // Only run when the peer connection becomes available\n      if (!peerConnection || !mediaStream || !silentAudioTrack) return;\n\n      // swap in the silent track\n      if (!audio && microphoneTrack) {\n        const currentAudioTracks = mediaStream.getAudioTracks();\n        const currentAudioTrack = currentAudioTracks[0];\n\n        if (currentAudioTrack && currentAudioTrack !== microphoneTrack) {\n          return;\n        }\n\n        const silentTrack = createSilentAudioTrack();\n\n        for (const track of currentAudioTracks) {\n          mediaStream.removeTrack(track);\n        }\n\n        mediaStream.addTrack(silentTrack);\n\n        const audioSender = peerConnection\n          .getSenders()\n          .find((s) => s.track && s.track.kind === \"audio\");\n\n        if (audioSender) {\n          await audioSender.replaceTrack(silentTrack);\n        }\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a.peerConnection === b.peerConnection && a.audio === b.audio,\n    },\n  );\n\n  return {\n    destroy: () => {\n      destroyAudioVideoEnabled?.();\n      destroyErrorCount?.();\n      destroyMapDeviceListToFriendly?.();\n      destroyMediaStream?.();\n      destroyMediaSyncError?.();\n      destroyMediaSyncMounted?.();\n      destroyPeerConnectionAndMediaStream?.();\n      destroyPeerConnectionAudioHandler?.();\n      destroyPictureInPictureSupportedMonitor?.();\n      destroyRequestUserMedia?.();\n      destroyUpdateDeviceList?.();\n      destroyWhip?.();\n    },\n  };\n};\n\n/**\n * Creates a silent audio track to use when audio is muted but we still want\n * to send an audio track. This helps maintain connection stability while muted.\n * @returns MediaStreamTrack A silent audio track\n */\nexport const createSilentAudioTrack = (): MediaStreamTrack => {\n  // biome-ignore lint/suspicious/noExplicitAny: ignored using `--suppress`\n  const ctx = new (window.AudioContext || (window as any).webkitAudioContext)();\n  const oscillator = ctx.createOscillator();\n  const dst = ctx.createMediaStreamDestination();\n\n  const gainNode = ctx.createGain();\n  gainNode.gain.value = 0;\n\n  oscillator.type = \"sine\";\n  oscillator.frequency.value = 440;\n\n  oscillator.connect(gainNode);\n  gainNode.connect(dst);\n\n  oscillator.start();\n  const track = dst.stream.getAudioTracks()[0];\n  track.enabled = true;\n  return track;\n};\n","import {\n  ACCESS_CONTROL_ERROR_MESSAGE,\n  BFRAMES_ERROR_MESSAGE,\n  STREAM_OFFLINE_ERROR_MESSAGE,\n} from \"@livepeer/core/errors\";\nimport type { MediaControllerStore } from \"@livepeer/core/media\";\nimport { warn } from \"@livepeer/core/utils\";\nimport type { HlsConfig as HlsJsConfig } from \"hls.js\";\nimport { createNewHls, type HlsError } from \"../../hls/hls\";\nimport { createNewWHEP } from \"../../webrtc/whep\";\nimport {\n  addFullscreenEventListener,\n  enterFullscreen,\n  exitFullscreen,\n  isCurrentlyFullscreen,\n} from \"./fullscreen\";\nimport {\n  addEnterPictureInPictureEventListener,\n  addExitPictureInPictureEventListener,\n  enterPictureInPicture,\n  exitPictureInPicture,\n  isCurrentlyPictureInPicture,\n} from \"./pictureInPicture\";\nimport { isVolumeChangeSupported } from \"./volume\";\n\nexport type HlsConfig = Partial<HlsJsConfig>;\n\nconst MEDIA_CONTROLLER_INITIALIZED_ATTRIBUTE =\n  \"data-livepeer-controller-initialized\";\n\nconst allKeyTriggers = [\n  \"KeyF\",\n  \"KeyK\",\n  \"KeyM\",\n  \"KeyI\",\n  \"KeyV\",\n  \"KeyX\",\n  \"Space\",\n  \"ArrowRight\",\n  \"ArrowLeft\",\n] as const;\ntype KeyTrigger = (typeof allKeyTriggers)[number];\n\nconst delay = (ms: number) => {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n};\n\nexport const addEventListeners = (\n  element: HTMLMediaElement,\n  store: MediaControllerStore,\n) => {\n  const initializedState = store.getState();\n\n  try {\n    isVolumeChangeSupported(\n      initializedState.currentSource?.type === \"audio\" ? \"audio\" : \"video\",\n    ).then((result) => {\n      store.setState(({ __device }) => ({\n        __device: {\n          ...__device,\n          isVolumeChangeSupported: result,\n        },\n      }));\n    });\n  } catch (e) {\n    console.error(e);\n  }\n\n  const onLoadedMetadata = () => {\n    store.getState().__controlsFunctions.onCanPlay();\n    store.getState().__controlsFunctions.requestMeasure();\n  };\n\n  const onLoadedData = () => {\n    store.getState().__controlsFunctions.requestMeasure();\n  };\n\n  const onPlay = () => {\n    store.getState().__controlsFunctions.onPlay();\n  };\n  const onPause = () => {\n    store.getState().__controlsFunctions.onPause();\n  };\n\n  const onDurationChange = () =>\n    store\n      .getState()\n      .__controlsFunctions.onDurationChange(element?.duration ?? 0);\n\n  const onKeyUp = (e: KeyboardEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n\n    const code = e.code as KeyTrigger;\n\n    store.getState().__controlsFunctions.updateLastInteraction();\n\n    const isNotBroadcast =\n      store.getState().__initialProps.hotkeys !== \"broadcast\";\n\n    if (allKeyTriggers.includes(code)) {\n      if ((code === \"Space\" || code === \"KeyK\") && isNotBroadcast) {\n        store.getState().__controlsFunctions.togglePlay();\n      } else if (code === \"ArrowRight\" && isNotBroadcast) {\n        store.getState().__controlsFunctions.requestSeekForward();\n      } else if (code === \"ArrowLeft\" && isNotBroadcast) {\n        store.getState().__controlsFunctions.requestSeekBack();\n      } else if (code === \"KeyM\" && isNotBroadcast) {\n        store.getState().__controlsFunctions.requestToggleMute();\n      } else if (code === \"KeyX\" && isNotBroadcast) {\n        store.getState().__controlsFunctions.requestClip();\n      } else if (code === \"KeyF\") {\n        store.getState().__controlsFunctions.requestToggleFullscreen();\n      } else if (code === \"KeyI\") {\n        store.getState().__controlsFunctions.requestTogglePictureInPicture();\n      }\n    }\n  };\n\n  const onMouseUpdate = () => {\n    store.getState().__controlsFunctions.updateLastInteraction();\n  };\n  const onTouchUpdate = async () => {\n    store.getState().__controlsFunctions.updateLastInteraction();\n  };\n\n  const onVolumeChange = () => {\n    store\n      .getState()\n      .__controlsFunctions.setVolume(element.muted ? 0 : (element.volume ?? 0));\n  };\n\n  const onRateChange = () => {\n    store.getState().__controlsFunctions.setPlaybackRate(element.playbackRate);\n  };\n\n  const onTimeUpdate = () => {\n    store.getState().__controlsFunctions.onProgress(element?.currentTime ?? 0);\n\n    if (element && (element?.duration ?? 0) > 0) {\n      const currentTime = element.currentTime;\n\n      const buffered = [...Array(element.buffered.length)].reduce(\n        (prev, _curr, i) => {\n          const start = element.buffered.start(element.buffered.length - 1 - i);\n          const end = element.buffered.end(element.buffered.length - 1 - i);\n\n          // if the TimeRange covers the current time, then use this value\n          if (start <= currentTime && end >= currentTime) {\n            return end;\n          }\n\n          return prev;\n        },\n        // default to no buffering\n        0,\n      );\n\n      store.getState().__controlsFunctions.updateBuffered(buffered);\n    }\n  };\n\n  const onError = async (e: ErrorEvent) => {\n    const source = store.getState().currentSource;\n\n    if (source?.type === \"video\") {\n      const sourceElement = e.target;\n      const parentElement = (sourceElement as HTMLSourceElement)?.parentElement;\n      const videoUrl =\n        (parentElement as HTMLVideoElement)?.currentSrc ??\n        (sourceElement as HTMLVideoElement)?.currentSrc;\n\n      if (videoUrl) {\n        try {\n          const response = await fetch(videoUrl);\n          if (response.status === 404) {\n            console.warn(\"Video not found\");\n            return store\n              .getState()\n              .__controlsFunctions?.onError?.(\n                new Error(STREAM_OFFLINE_ERROR_MESSAGE),\n              );\n          }\n          if (response.status === 401) {\n            console.warn(\"Unauthorized to view video\");\n            return store\n              .getState()\n              .__controlsFunctions?.onError?.(\n                new Error(ACCESS_CONTROL_ERROR_MESSAGE),\n              );\n          }\n        } catch (err) {\n          console.warn(err);\n          return store\n            .getState()\n            .__controlsFunctions?.onError?.(\n              new Error(\"Error fetching video URL\"),\n            );\n        }\n      }\n\n      console.warn(\"Unknown error loading video\");\n      return store\n        .getState()\n        .__controlsFunctions?.onError?.(\n          new Error(\"Unknown error loading video\"),\n        );\n    }\n\n    store.getState().__controlsFunctions.onError(new Error(e?.message));\n  };\n\n  const onWaiting = async () => {\n    store.getState().__controlsFunctions.onWaiting();\n  };\n\n  const onStalled = async () => {\n    store.getState().__controlsFunctions.onStalled();\n  };\n\n  const onLoadStart = async () => {\n    store.getState().__controlsFunctions.onLoading();\n  };\n\n  const onEnded = async () => {\n    store.getState().__controlsFunctions.onEnded();\n  };\n\n  const onResize = async () => {\n    store.getState().__controlsFunctions.requestMeasure();\n  };\n\n  const parentElementOrElement = element?.parentElement ?? element;\n\n  if (element) {\n    element.addEventListener(\"volumechange\", onVolumeChange);\n    element.addEventListener(\"ratechange\", onRateChange);\n\n    element.addEventListener(\"loadedmetadata\", onLoadedMetadata);\n    element.addEventListener(\"loadeddata\", onLoadedData);\n    element.addEventListener(\"play\", onPlay);\n    element.addEventListener(\"playing\", onPlay);\n    element.addEventListener(\"pause\", onPause);\n    element.addEventListener(\"durationchange\", onDurationChange);\n    element.addEventListener(\"timeupdate\", onTimeUpdate);\n    element.addEventListener(\"error\", onError);\n    element.addEventListener(\"waiting\", onWaiting);\n    element.addEventListener(\"stalled\", onStalled);\n    element.addEventListener(\"loadstart\", onLoadStart);\n    element.addEventListener(\"ended\", onEnded);\n\n    parentElementOrElement?.addEventListener(\"mouseout\", onMouseUpdate);\n    parentElementOrElement?.addEventListener(\"mousemove\", onMouseUpdate);\n\n    parentElementOrElement?.addEventListener(\"touchstart\", onTouchUpdate);\n    parentElementOrElement?.addEventListener(\"touchend\", onTouchUpdate);\n    parentElementOrElement?.addEventListener(\"touchmove\", onTouchUpdate);\n\n    if (typeof window !== \"undefined\") {\n      window?.addEventListener?.(\"resize\", onResize);\n    }\n\n    parentElementOrElement?.addEventListener(\"keyup\", onKeyUp);\n    parentElementOrElement?.setAttribute(\"tabindex\", \"0\");\n\n    element.setAttribute(MEDIA_CONTROLLER_INITIALIZED_ATTRIBUTE, \"true\");\n  }\n\n  const onFullscreenChange = () => {\n    store\n      .getState()\n      .__controlsFunctions.setFullscreen(isCurrentlyFullscreen(element));\n  };\n\n  const onEnterPictureInPicture = () => {\n    store.getState().__controlsFunctions.setPictureInPicture(true);\n  };\n  const onExitPictureInPicture = () => {\n    store.getState().__controlsFunctions.setPictureInPicture(false);\n  };\n\n  // add effects\n  const removeEffectsFromStore = addEffectsToStore(element, store);\n\n  // add fullscreen listener\n  const removeFullscreenListener = addFullscreenEventListener(\n    element,\n    onFullscreenChange,\n  );\n\n  // add picture in picture listeners\n  const removeEnterPictureInPictureListener =\n    addEnterPictureInPictureEventListener(element, onEnterPictureInPicture);\n  const removeExitPictureInPictureListener =\n    addExitPictureInPictureEventListener(element, onExitPictureInPicture);\n\n  return {\n    destroy: () => {\n      removeFullscreenListener?.();\n\n      removeEnterPictureInPictureListener?.();\n      removeExitPictureInPictureListener?.();\n\n      element?.removeEventListener?.(\"ratechange\", onRateChange);\n      element?.removeEventListener?.(\"volumechange\", onVolumeChange);\n      element?.removeEventListener?.(\"loadedmetadata\", onLoadedMetadata);\n      element?.removeEventListener?.(\"loadeddata\", onLoadedData);\n      element?.removeEventListener?.(\"play\", onPlay);\n      element?.removeEventListener?.(\"playing\", onPlay);\n      element?.removeEventListener?.(\"pause\", onPause);\n      element?.removeEventListener?.(\"durationchange\", onDurationChange);\n      element?.removeEventListener?.(\"timeupdate\", onTimeUpdate);\n      element?.removeEventListener?.(\"error\", onError);\n      element?.removeEventListener?.(\"waiting\", onWaiting);\n      element?.removeEventListener?.(\"stalled\", onStalled);\n      element?.removeEventListener?.(\"loadstart\", onLoadStart);\n      element?.removeEventListener?.(\"ended\", onEnded);\n\n      if (typeof window !== \"undefined\") {\n        window?.removeEventListener?.(\"resize\", onResize);\n      }\n\n      parentElementOrElement?.removeEventListener?.(\"mouseout\", onMouseUpdate);\n      parentElementOrElement?.removeEventListener?.(\"mousemove\", onMouseUpdate);\n\n      parentElementOrElement?.removeEventListener?.(\n        \"touchstart\",\n        onTouchUpdate,\n      );\n      parentElementOrElement?.removeEventListener?.(\"touchend\", onTouchUpdate);\n      parentElementOrElement?.removeEventListener?.(\"touchmove\", onTouchUpdate);\n\n      parentElementOrElement?.removeEventListener?.(\"keyup\", onKeyUp);\n\n      removeEffectsFromStore?.();\n\n      element?.removeAttribute?.(MEDIA_CONTROLLER_INITIALIZED_ATTRIBUTE);\n    },\n  };\n};\n\ntype Cleanup = () => void | Promise<void>;\n\n// Cleanup function for src side effects\nlet cleanupSource: Cleanup = () => {};\n// Cleanup function for poster image side effects\nlet cleanupPosterImage: Cleanup = () => {};\n\nconst addEffectsToStore = (\n  element: HTMLMediaElement,\n  store: MediaControllerStore,\n) => {\n  // Subscribe to source changes (and trigger playback based on these)\n  const destroySource = store.subscribe(\n    ({\n      __initialProps,\n      __controls,\n      currentSource,\n      errorCount,\n      progress,\n      mounted,\n      videoQuality,\n    }) => ({\n      aspectRatio: __initialProps.aspectRatio,\n      autoPlay: __initialProps.autoPlay,\n      backoff: __initialProps.backoff,\n      backoffMax: __initialProps.backoffMax,\n      calculateDelay: __initialProps.calculateDelay,\n      errorCount,\n      lastError: __controls.lastError,\n      hlsConfig: __controls.hlsConfig,\n      mounted,\n      progress,\n      source: currentSource,\n      timeout: __initialProps.timeout,\n      videoQuality,\n    }),\n    async ({\n      aspectRatio,\n      autoPlay,\n      // biome-ignore lint/correctness/noUnusedFunctionParameters: ignored using `--suppress`\n      backoff,\n      // biome-ignore lint/correctness/noUnusedFunctionParameters: ignored using `--suppress`\n      backoffMax,\n      calculateDelay,\n      errorCount,\n      // biome-ignore lint/correctness/noUnusedFunctionParameters: ignored using `--suppress`\n      lastError,\n      hlsConfig,\n      mounted,\n      progress,\n      source,\n      timeout,\n      videoQuality,\n    }) => {\n      if (!mounted) {\n        return;\n      }\n\n      await cleanupSource?.();\n\n      await delay(\n        Math.max(calculateDelay(errorCount), errorCount === 0 ? 0 : 100),\n      );\n\n      let unmounted = false;\n\n      if (!source) {\n        return;\n      }\n\n      let jumped = false;\n\n      const jumpToPreviousPosition = () => {\n        const live = store.getState().live;\n\n        if (!live && progress && !jumped) {\n          element.currentTime = progress;\n\n          jumped = true;\n        }\n      };\n\n      const onErrorComposed = (err: Error) => {\n        if (!unmounted) {\n          cleanupSource?.();\n\n          store.getState().__controlsFunctions?.onError?.(err);\n        }\n      };\n\n      if (source.type === \"webrtc\") {\n        const unsubscribeBframes = store.subscribe(\n          (state) => state?.__metadata,\n          (metadata) => {\n            let webrtcIsPossibleForOneTrack = false;\n\n            // Check if metadata and meta tracks are available\n            if (metadata?.meta?.tracks) {\n              // Iterate over each track in the metadata\n              for (const trackId of Object.keys(metadata.meta.tracks)) {\n                // Check if the track does not have bframes equal to 1\n                if (metadata?.meta?.tracks[trackId]?.bframes !== 1) {\n                  webrtcIsPossibleForOneTrack = true;\n                }\n              }\n            }\n\n            // Determine if fallback to HLS is necessary\n            const shouldNotFallBackToHLS =\n              webrtcIsPossibleForOneTrack || metadata?.meta?.bframes === 0;\n\n            // If fallback to HLS is needed and component is not unmounted, handle the error\n            if (!shouldNotFallBackToHLS && !unmounted) {\n              onErrorComposed(new Error(BFRAMES_ERROR_MESSAGE));\n            }\n          },\n        );\n\n        const { destroy } = createNewWHEP({\n          source: source.src,\n          element,\n          callbacks: {\n            onConnected: () => {\n              store.getState().__controlsFunctions.setLive(true);\n              jumpToPreviousPosition();\n            },\n            onError: onErrorComposed,\n            onPlaybackOffsetUpdated:\n              store.getState().__controlsFunctions.updatePlaybackOffsetMs,\n            onRedirect: store.getState().__controlsFunctions.onFinalUrl,\n          },\n          accessControl: {\n            jwt: store.getState().__initialProps.jwt,\n            accessKey: store.getState().__initialProps.accessKey,\n          },\n          sdpTimeout: timeout,\n          iceServers: store.getState().__initialProps.iceServers,\n        });\n\n        const id = setTimeout(() => {\n          if (!store.getState().canPlay && !unmounted) {\n            store.getState().__controlsFunctions.onWebRTCTimeout?.();\n\n            onErrorComposed(\n              new Error(\n                \"Timeout reached for canPlay - triggering playback error.\",\n              ),\n            );\n          }\n        }, timeout);\n\n        cleanupSource = () => {\n          unmounted = true;\n\n          clearTimeout(id);\n          destroy?.();\n          unsubscribeBframes?.();\n        };\n\n        return;\n      }\n\n      if (source.type === \"hls\") {\n        const indexUrl = /\\/hls\\/[^/\\s]+\\/index\\.m3u8/;\n\n        const onErrorCleaned = (error: HlsError) => {\n          const cleanError = new Error(\n            error?.response?.data?.toString?.() ??\n              (error?.response?.code === 401\n                ? ACCESS_CONTROL_ERROR_MESSAGE\n                : \"Error with HLS.js\"),\n          );\n\n          onErrorComposed?.(cleanError);\n        };\n\n        const hlsConfigResolved = hlsConfig as Partial<HlsConfig> | null;\n\n        const { destroy, setQuality } = createNewHls({\n          source: source?.src,\n          element,\n          initialQuality: videoQuality,\n          aspectRatio: aspectRatio ?? 16 / 9,\n          callbacks: {\n            onLive: store.getState().__controlsFunctions.setLive,\n            onDuration: store.getState().__controlsFunctions.onDurationChange,\n            onCanPlay: () => {\n              store.getState().__controlsFunctions.onCanPlay();\n              jumpToPreviousPosition();\n              store.getState().__controlsFunctions.onError(null);\n            },\n            onError: onErrorCleaned,\n            onPlaybackOffsetUpdated:\n              store.getState().__controlsFunctions.updatePlaybackOffsetMs,\n            onRedirect: store.getState().__controlsFunctions.onFinalUrl,\n          },\n          config: {\n            ...(hlsConfigResolved ?? {}),\n            async xhrSetup(xhr, url) {\n              if (hlsConfigResolved?.xhrSetup) {\n                await hlsConfigResolved?.xhrSetup?.(xhr, url);\n              } else {\n                const live = store.getState().live;\n\n                if (!live || url.match(indexUrl)) {\n                  const jwt = store.getState().__initialProps.jwt;\n                  const accessKey = store.getState().__initialProps.accessKey;\n\n                  if (accessKey)\n                    xhr.setRequestHeader(\"Livepeer-Access-Key\", accessKey);\n                  else if (jwt) xhr.setRequestHeader(\"Livepeer-Jwt\", jwt);\n                }\n              }\n            },\n            autoPlay,\n          },\n        });\n\n        const unsubscribeQualityUpdate = store.subscribe(\n          (state) => state.videoQuality,\n          (newQuality) => {\n            setQuality(newQuality);\n          },\n        );\n\n        cleanupSource = () => {\n          unmounted = true;\n          destroy?.();\n          unsubscribeQualityUpdate?.();\n        };\n\n        return;\n      }\n\n      if (source?.type === \"video\") {\n        store.getState().__controlsFunctions.onFinalUrl(source.src);\n\n        element.addEventListener(\"canplay\", jumpToPreviousPosition);\n\n        element.src = source.src;\n        element.load();\n\n        cleanupSource = () => {\n          unmounted = true;\n\n          element?.removeEventListener?.(\"canplay\", jumpToPreviousPosition);\n        };\n\n        return;\n      }\n    },\n    {\n      equalityFn: (a, b) => {\n        const errorCountChanged =\n          a.errorCount !== b.errorCount && b.errorCount !== 0;\n        const lastErrorChanged = a.lastError !== b.lastError;\n\n        const sourceChanged = a.source?.src !== b.source?.src;\n        const mountedChanged = a.mounted !== b.mounted;\n\n        const shouldReRender =\n          errorCountChanged ||\n          lastErrorChanged ||\n          sourceChanged ||\n          mountedChanged;\n\n        return !shouldReRender;\n      },\n    },\n  );\n\n  // Subscribe to poster image changes\n  const destroyPosterImage = store.subscribe(\n    ({ __controls, live, __controlsFunctions, __initialProps }) => ({\n      thumbnail: __controls.thumbnail?.src,\n      live,\n      setPoster: __controlsFunctions.setPoster,\n      posterLiveUpdate: __initialProps.posterLiveUpdate,\n    }),\n    async ({ thumbnail, live, setPoster, posterLiveUpdate }) => {\n      cleanupPosterImage?.();\n\n      if (thumbnail && live && posterLiveUpdate > 0) {\n        const interval = setInterval(() => {\n          const thumbnailUrl = new URL(thumbnail);\n\n          thumbnailUrl.searchParams.set(\"v\", Date.now().toFixed(0));\n\n          setPoster(thumbnailUrl.toString());\n        }, posterLiveUpdate);\n\n        cleanupPosterImage = () => clearInterval(interval);\n      }\n    },\n    {\n      equalityFn: (a, b) => a.thumbnail === b.thumbnail && a.live === b.live,\n    },\n  );\n\n  // Subscribe to play/pause changes\n  const destroyPlayPause = store.subscribe(\n    (state) => state.__controls.requestedPlayPauseLastTime,\n    async () => {\n      if (element.paused) {\n        await element.play();\n      } else {\n        await element.pause();\n      }\n    },\n  );\n\n  // Subscribe to playback rate changes\n  const destroyPlaybackRate = store.subscribe(\n    (state) => state.playbackRate,\n    (current) => {\n      element.playbackRate = current === \"constant\" ? 1 : current;\n    },\n  );\n\n  // Subscribe to volume changes\n  const destroyVolume = store.subscribe(\n    (state) => ({\n      playing: state.playing,\n      volume: state.volume,\n      isVolumeChangeSupported: state.__device.isVolumeChangeSupported,\n    }),\n    (current) => {\n      if (current.isVolumeChangeSupported) {\n        element.volume = current.volume;\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a.volume === b.volume &&\n        a.playing === b.playing &&\n        a.isVolumeChangeSupported === b.isVolumeChangeSupported,\n    },\n  );\n\n  // Subscribe to mute changes\n  const destroyMute = store.subscribe(\n    (state) => state.__controls.muted,\n    (current, prev) => {\n      if (current !== prev) {\n        element.muted = current;\n      }\n    },\n  );\n\n  // Subscribe to seeking changes\n  const destroySeeking = store.subscribe(\n    (state) => state.__controls.requestedRangeToSeekTo,\n    (current) => {\n      if (typeof element.readyState === \"undefined\" || element.readyState > 0) {\n        element.currentTime = current;\n      }\n    },\n  );\n\n  // Subscribe to fullscreen changes\n  const destroyFullscreen = store.subscribe(\n    (state) => state.__controls.requestedFullscreenLastTime,\n    async () => {\n      const isFullscreen = isCurrentlyFullscreen(element);\n      if (isFullscreen) exitFullscreen(element);\n      else enterFullscreen(element);\n    },\n  );\n\n  // Subscribe to picture-in-picture changes\n  const destroyPictureInPicture = store.subscribe(\n    (state) => state.__controls.requestedPictureInPictureLastTime,\n    async () => {\n      try {\n        const isPictureInPicture = await isCurrentlyPictureInPicture(element);\n        if (isPictureInPicture) await exitPictureInPicture(element);\n        else await enterPictureInPicture(element);\n      } catch (e) {\n        warn((e as Error)?.message ?? \"Picture in picture is not supported\");\n\n        store.setState((state) => ({\n          __device: {\n            ...state.__device,\n            isPictureInPictureSupported: false,\n          },\n        }));\n      }\n    },\n  );\n\n  // Subscribe to autohide interactions\n  const destroyAutohide = store.subscribe(\n    (state) => ({\n      lastInteraction: state.__controls.lastInteraction,\n      autohide: state.__controls.autohide,\n    }),\n    async ({ lastInteraction, autohide }) => {\n      if (autohide && lastInteraction) {\n        store.getState().__controlsFunctions.setHidden(false);\n\n        await delay(autohide);\n\n        const parentElementOrElement = element?.parentElement ?? element;\n\n        // we check if any children of the parent element are in an \"open\" state, which is the radix\n        // data attribute for popovers and other elements\n        // this is the only way to reliably hide the controls while a popover is shown, and possibly\n        // is missing some data attributes for other primitives\n        const openElement = parentElementOrElement?.querySelector?.(\n          '[data-state=\"open\"]',\n        );\n\n        if (\n          !openElement &&\n          !store.getState().hidden &&\n          lastInteraction === store.getState().__controls.lastInteraction\n        ) {\n          store.getState().__controlsFunctions.setHidden(true);\n        }\n      }\n    },\n    {\n      equalityFn: (a, b) =>\n        a?.lastInteraction === b?.lastInteraction &&\n        a?.autohide === b?.autohide,\n    },\n  );\n\n  // Subscribe to sizing requests\n  const destroyRequestSizing = store.subscribe(\n    (state) => ({\n      lastTime: state.__controls.requestedMeasureLastTime,\n      fullscreen: state.fullscreen,\n    }),\n    async () => {\n      store.getState().__controlsFunctions.setSize({\n        ...((element as unknown as HTMLVideoElement)?.videoHeight &&\n        (element as unknown as HTMLVideoElement)?.videoWidth\n          ? {\n              media: {\n                height: (element as unknown as HTMLVideoElement).videoHeight,\n                width: (element as unknown as HTMLVideoElement).videoWidth,\n              },\n            }\n          : {}),\n        ...(element?.clientHeight && element?.clientWidth\n          ? {\n              container: {\n                height: element.clientHeight,\n                width: element.clientWidth,\n              },\n            }\n          : {}),\n        ...(typeof window !== \"undefined\" &&\n        window?.innerHeight &&\n        window?.innerWidth\n          ? {\n              window: {\n                height: window.innerHeight,\n                width: window.innerWidth,\n              },\n            }\n          : {}),\n      });\n    },\n    {\n      equalityFn: (a, b) =>\n        a?.fullscreen === b?.fullscreen && a?.lastTime === b?.lastTime,\n    },\n  );\n\n  // Subscribe to media sizing changes\n  const destroyMediaSizing = store.subscribe(\n    (state) => state.__controls.size?.media,\n    async (media) => {\n      const parentElementOrElement = element?.parentElement ?? element;\n\n      if (parentElementOrElement) {\n        if (media?.height && media?.width) {\n          const elementStyle = parentElementOrElement.style;\n          elementStyle.setProperty(\n            \"--livepeer-media-height\",\n            `${media.height}px`,\n          );\n          elementStyle.setProperty(\n            \"--livepeer-media-width\",\n            `${media.width}px`,\n          );\n        }\n      }\n    },\n    {\n      equalityFn: (a, b) => a?.height === b?.height && a?.width === b?.width,\n    },\n  );\n\n  // Subscribe to container sizing changes\n  const destroyContainerSizing = store.subscribe(\n    (state) => state.__controls.size?.container,\n    async (container) => {\n      const parentElementOrElement = element?.parentElement ?? element;\n\n      if (parentElementOrElement) {\n        if (container?.height && container?.width) {\n          const elementStyle = parentElementOrElement.style;\n          elementStyle.setProperty(\n            \"--livepeer-container-height\",\n            `${container.height}px`,\n          );\n          elementStyle.setProperty(\n            \"--livepeer-container-width\",\n            `${container.width}px`,\n          );\n        }\n      }\n    },\n    {\n      equalityFn: (a, b) => a?.height === b?.height && a?.width === b?.width,\n    },\n  );\n\n  return () => {\n    destroyAutohide?.();\n    destroyContainerSizing?.();\n    destroyFullscreen?.();\n    destroyMediaSizing?.();\n    destroyMute?.();\n    destroyPictureInPicture?.();\n    destroyPlaybackRate?.();\n    destroyPlayPause?.();\n    destroyPosterImage?.();\n    destroyRequestSizing?.();\n    destroySeeking?.();\n    destroyVolume?.();\n    destroySource?.();\n\n    cleanupPosterImage?.();\n    cleanupSource?.();\n  };\n};\n","import {\n  calculateVideoQualityDimensions,\n  type VideoQuality,\n} from \"@livepeer/core/media\";\nimport Hls, { type ErrorData, type HlsConfig } from \"hls.js\";\nimport { isClient } from \"../media/utils\";\n\nexport const VIDEO_HLS_INITIALIZED_ATTRIBUTE =\n  \"data-livepeer-video-hls-initialized\";\n\nexport type HlsError = ErrorData;\n\nexport type VideoConfig = { autoplay?: boolean };\nexport type HlsVideoConfig = Partial<HlsConfig> & {\n  autoPlay?: boolean;\n};\n\n/**\n * Checks if hls.js can play in the browser.\n */\nexport const isHlsSupported = () => (isClient() ? Hls.isSupported() : true);\n\n/**\n * Create an hls.js instance and attach to the provided media element.\n */\nexport const createNewHls = <TElement extends HTMLMediaElement>({\n  source,\n  element,\n  callbacks,\n  aspectRatio,\n  config,\n  initialQuality,\n}: {\n  source: string;\n  element: TElement;\n  initialQuality: VideoQuality;\n  aspectRatio: number;\n  callbacks: {\n    onLive?: (v: boolean) => void;\n    onPlaybackOffsetUpdated?: (d: number) => void;\n    onDuration?: (v: number) => void;\n    onCanPlay?: () => void;\n    onError?: (data: HlsError) => void;\n    onRedirect?: (url: string | null) => void;\n  };\n  config: HlsVideoConfig;\n}): {\n  setQuality: (quality: VideoQuality) => void;\n  destroy: () => void;\n} => {\n  // do not attach twice\n  if (element.getAttribute(VIDEO_HLS_INITIALIZED_ATTRIBUTE) === \"true\") {\n    return {\n      setQuality: () => {\n        //\n      },\n      destroy: () => {\n        //\n      },\n    };\n  }\n\n  element.setAttribute(VIDEO_HLS_INITIALIZED_ATTRIBUTE, \"true\");\n\n  const hls = new Hls({\n    backBufferLength: 60 * 1.5,\n    manifestLoadingMaxRetry: 0,\n    fragLoadingMaxRetry: 0,\n    levelLoadingMaxRetry: 0,\n    appendErrorMaxRetry: 0,\n    ...config,\n    ...(config?.liveSyncDurationCount\n      ? {\n          liveSyncDurationCount: config.liveSyncDurationCount,\n        }\n      : {\n          liveMaxLatencyDurationCount: 7,\n          liveSyncDurationCount: 3,\n        }),\n  });\n\n  const onDestroy = () => {\n    hls?.destroy?.();\n    element?.removeAttribute?.(VIDEO_HLS_INITIALIZED_ATTRIBUTE);\n  };\n\n  if (element) {\n    hls.attachMedia(element);\n  }\n\n  let redirected = false;\n\n  hls.on(Hls.Events.LEVEL_LOADED, async (_e, data) => {\n    const { live, totalduration: duration, url } = data.details;\n\n    if (!redirected) {\n      callbacks?.onRedirect?.(url ?? null);\n      redirected = true;\n    }\n\n    callbacks?.onLive?.(Boolean(live));\n    callbacks?.onDuration?.(duration ?? 0);\n  });\n\n  hls.on(Hls.Events.MEDIA_ATTACHED, async () => {\n    hls.loadSource(source);\n\n    hls.on(Hls.Events.MANIFEST_PARSED, (_event, _data) => {\n      setQuality({\n        hls: hls ?? null,\n        quality: initialQuality,\n        aspectRatio,\n      });\n\n      callbacks?.onCanPlay?.();\n      if (config.autoPlay) element?.play?.();\n    });\n  });\n\n  hls.on(Hls.Events.ERROR, async (_event, data) => {\n    const { details, fatal } = data;\n\n    const isManifestParsingError = details === \"manifestParsingError\";\n\n    if (!fatal && !isManifestParsingError) return;\n    callbacks?.onError?.(data);\n\n    if (fatal) {\n      console.error(`Fatal error : ${data.details}`);\n      switch (data.type) {\n        case Hls.ErrorTypes.MEDIA_ERROR:\n          hls.recoverMediaError();\n          break;\n        case Hls.ErrorTypes.NETWORK_ERROR:\n          console.error(`A network error occurred: ${data.details}`);\n          break;\n        default:\n          console.error(`An unrecoverable error occurred: ${data.details}`);\n          hls.destroy();\n          break;\n      }\n    }\n  });\n\n  function updateOffset() {\n    const currentDate = Date.now();\n    const newDate = hls.playingDate;\n\n    if (newDate && currentDate) {\n      callbacks?.onPlaybackOffsetUpdated?.(currentDate - newDate.getTime());\n    }\n  }\n\n  const updateOffsetInterval = setInterval(updateOffset, 2000);\n\n  return {\n    destroy: () => {\n      onDestroy?.();\n      clearInterval?.(updateOffsetInterval);\n      element?.removeAttribute?.(VIDEO_HLS_INITIALIZED_ATTRIBUTE);\n    },\n    setQuality: (videoQuality) => {\n      setQuality({\n        hls: hls ?? null,\n        quality: videoQuality,\n        aspectRatio,\n      });\n    },\n  };\n};\n\nconst setQuality = ({\n  hls,\n  quality,\n  aspectRatio,\n}: {\n  hls: Hls | null;\n  quality: VideoQuality;\n  aspectRatio: number;\n}) => {\n  if (hls) {\n    const { width } = calculateVideoQualityDimensions(quality, aspectRatio);\n\n    if (!width || quality === \"auto\") {\n      hls.currentLevel = -1; // Auto level\n      return;\n    }\n\n    if (hls.levels && hls.levels.length > 0) {\n      // Sort levels by the absolute difference between their width and the desired width\n      const sortedLevels = hls.levels\n        .map((level, index) => ({ ...level, index }))\n        .sort(\n          (a, b) =>\n            Math.abs((width ?? 0) - a.width) - Math.abs((width ?? 0) - b.width),\n        );\n\n      // Choose the level with the smallest difference in width\n      const bestMatchLevel = sortedLevels?.[0];\n\n      if ((bestMatchLevel?.index ?? -1) >= 0) {\n        hls.currentLevel = bestMatchLevel.index;\n      } else {\n        hls.currentLevel = -1;\n      }\n    }\n  }\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 { 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 { isClient } from \"../utils\";\n\nexport const isPictureInPictureSupported = (\n  element?: HTMLMediaElement | null,\n) => {\n  if (typeof document === \"undefined\") {\n    return true;\n  }\n\n  const videoElement = element ?? document.createElement(\"video\");\n\n  const isPiPDisabled = Boolean(\n    (videoElement as HTMLVideoElement).disablePictureInPicture,\n  );\n\n  const { apiType } = getPictureInPictureMode(videoElement);\n\n  return Boolean(apiType) && !isPiPDisabled;\n};\n\nexport const isCurrentlyPictureInPicture = (\n  inputElement?: HTMLMediaElement | null,\n) => {\n  const { apiType, element } = getPictureInPictureMode(inputElement);\n\n  if (apiType === \"w3c\") {\n    return Boolean(document?.pictureInPictureElement);\n  }\n  if (apiType === \"webkit\") {\n    return element?.webkitPresentationMode === \"picture-in-picture\";\n  }\n\n  return false;\n};\n\nexport const enterPictureInPicture = async (\n  inputElement?: HTMLMediaElement | null,\n) => {\n  const { apiType, element } = getPictureInPictureMode(inputElement);\n\n  if (apiType === \"w3c\") {\n    await element?.requestPictureInPicture?.();\n  } else if (apiType === \"webkit\") {\n    await element?.webkitSetPresentationMode?.(\"picture-in-picture\");\n  }\n\n  return null;\n};\n\nexport const exitPictureInPicture = (\n  inputElement?: HTMLMediaElement | null,\n) => {\n  const { apiType, element } = getPictureInPictureMode(inputElement);\n\n  if (apiType === \"w3c\") {\n    return document?.exitPictureInPicture?.() ?? null;\n  }\n  if (apiType === \"webkit\") {\n    return element?.webkitSetPresentationMode?.(\"inline\") ?? null;\n  }\n\n  return null;\n};\n\nexport const addEnterPictureInPictureEventListener = (\n  inputElement: HTMLMediaElement | null,\n  callback: EventListener,\n) => {\n  const { apiType, element } = getPictureInPictureMode(inputElement);\n\n  if (apiType === \"w3c\" && element) {\n    element.addEventListener(\"enterpictureinpicture\", callback, false);\n\n    return () => {\n      element.removeEventListener(\"enterpictureinpicture\", callback, false);\n    };\n  }\n\n  if (apiType === \"webkit\" && element) {\n    const callbackComposed = (e: Event) => {\n      if (element?.webkitPresentationMode === \"picture-in-picture\") {\n        callback?.(e);\n      }\n    };\n\n    document.addEventListener(\n      \"webkitpresentationmodechanged\",\n      callbackComposed,\n      false,\n    );\n\n    return () => {\n      document.removeEventListener(\n        \"webkitpresentationmodechanged\",\n        callbackComposed,\n        false,\n      );\n    };\n  }\n\n  return null;\n};\n\nexport const addExitPictureInPictureEventListener = (\n  inputElement: HTMLMediaElement | null,\n  callback: EventListener,\n) => {\n  const { apiType, element } = getPictureInPictureMode(inputElement);\n\n  if (apiType === \"w3c\" && element) {\n    element.addEventListener(\"leavepictureinpicture\", callback, false);\n\n    return () => {\n      element.removeEventListener(\"leavepictureinpicture\", callback, false);\n    };\n  }\n\n  if (apiType === \"webkit\" && element) {\n    const callbackComposed = (e: Event) => {\n      if (element?.webkitPresentationMode === \"inline\") {\n        callback?.(e);\n      }\n    };\n\n    document.addEventListener(\n      \"webkitpresentationmodechanged\",\n      callbackComposed,\n      false,\n    );\n\n    return () => {\n      document.removeEventListener(\n        \"webkitpresentationmodechanged\",\n        callbackComposed,\n        false,\n      );\n    };\n  }\n\n  return null;\n};\n\nconst getPictureInPictureMode = (element?: HTMLMediaElement | null) => {\n  if (isClient() && element instanceof HTMLVideoElement) {\n    // we disable the next line since we handle missing Safari versions in the next statement\n    if (document?.pictureInPictureEnabled) {\n      return { apiType: \"w3c\", element } as const;\n    }\n\n    // fallback to trying webkit\n    if (element?.webkitSupportsPresentationMode?.(\"picture-in-picture\")) {\n      return { apiType: \"webkit\", element } as const;\n    }\n  }\n\n  return { apiType: null };\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":["import_errors","import_utils","import_errors","import_utils","import_utils","ingestUrl"]}