{"mappings":";AAEA;IACE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;IACE,WAAW,IAAI,IAAI,CAAC;IACpB,OAAO,IAAI,iBAAiB,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AIND;IACE,CACE,IAAI,EAAE,iBAAiB,EACvB,UAAU,EAAE,GAAG,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAChC,gBAAgB,EAAE,gBAAgB,EAClC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC;CACZ;AAqDD,yCAAyC,MAAM,EAAE,CAEhD;ACnDD;IACE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAOD;IACE,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAID,yBAA0B,SAAQ,YAAY;gBAqBhC,IAAI,EAAE,mBAAmB;IAiC/B,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,OAAO,GAAE,MAAM,GAAG,SAAqB;IA8KnE,IAAI;IAIJ,MAAM;IAIA,MAAM;IAKZ,IAAI;IAOJ,OAAO;CAIR","sources":["src/src/adapters/Adapter.ts","src/src/adapters/WHPPAdapter.ts","src/src/adapters/EyevinnAdapter.ts","src/src/adapters/WHEPAdapter.ts","src/src/adapters/AdapterFactory.ts","src/src/index.ts","src/index.ts"],"sourcesContent":[null,null,null,null,null,null,"import { Adapter } from './adapters/Adapter';\nimport {\n  AdapterFactory,\n  AdapterFactoryFunction\n} from './adapters/AdapterFactory';\nimport { EventEmitter } from 'events';\nimport { CSAIManager } from '@eyevinn/csai-manager';\n\nexport { ListAvailableAdapters } from './adapters/AdapterFactory';\n\nenum Message {\n  NO_MEDIA = 'no-media',\n  MEDIA_RECOVERED = 'media-recovered',\n  PEER_CONNECTION_FAILED = 'peer-connection-failed',\n  INITIAL_CONNECTION_FAILED = 'initial-connection-failed',\n  CONNECT_ERROR = 'connect-error'\n}\n\nexport interface MediaConstraints {\n  audioOnly?: boolean;\n  videoOnly?: boolean;\n}\n\nconst MediaConstraintsDefaults: MediaConstraints = {\n  audioOnly: false,\n  videoOnly: false\n};\n\ninterface WebRTCPlayerOptions {\n  video: HTMLVideoElement;\n  type: string;\n  adapterFactory?: AdapterFactoryFunction;\n  iceServers?: RTCIceServer[];\n  debug?: boolean;\n  vmapUrl?: string;\n  statsTypeFilter?: string; // regexp\n  detectTimeout?: boolean;\n  timeoutThreshold?: number;\n  mediaConstraints?: MediaConstraints;\n}\n\nconst RECONNECT_ATTEMPTS = 2;\n\nexport class WebRTCPlayer extends EventEmitter {\n  private videoElement: HTMLVideoElement;\n  private peer: RTCPeerConnection = <RTCPeerConnection>{};\n  private adapterType: string;\n  private adapterFactory: AdapterFactoryFunction | undefined = undefined;\n  private iceServers: RTCIceServer[];\n  private debug: boolean;\n  private channelUrl: URL = <URL>{};\n  private authKey?: string = undefined;\n  private reconnectAttemptsLeft: number = RECONNECT_ATTEMPTS;\n  private csaiManager?: CSAIManager;\n  private adapter: Adapter = <Adapter>{};\n  private statsInterval: ReturnType<typeof setInterval> | undefined;\n  private statsTypeFilter: string | undefined = undefined;\n  private msStatsInterval = 5000;\n  private mediaTimeoutOccured = false;\n  private mediaTimeoutThreshold = 30000;\n  private timeoutThresholdCounter = 0;\n  private bytesReceived = 0;\n  private mediaConstraints: MediaConstraints;\n\n  constructor(opts: WebRTCPlayerOptions) {\n    super();\n    this.mediaConstraints = {\n      ...MediaConstraintsDefaults,\n      ...opts.mediaConstraints\n    };\n    this.videoElement = opts.video;\n    this.adapterType = opts.type;\n    this.adapterFactory = opts.adapterFactory;\n    this.statsTypeFilter = opts.statsTypeFilter;\n    this.mediaTimeoutThreshold =\n      opts.timeoutThreshold ?? this.mediaTimeoutThreshold;\n\n    this.iceServers = [{ urls: 'stun:stun.l.google.com:19302' }];\n    if (opts.iceServers) {\n      this.iceServers = opts.iceServers;\n    }\n    this.debug = !!opts.debug;\n    if (opts.vmapUrl) {\n      this.csaiManager = new CSAIManager({\n        contentVideoElement: this.videoElement,\n        vmapUrl: opts.vmapUrl,\n        isLive: true,\n        autoplay: true\n      });\n      this.videoElement.addEventListener('ended', () => {\n        if (this.csaiManager) {\n          this.csaiManager.destroy();\n        }\n      });\n    }\n  }\n\n  async load(channelUrl: URL, authKey: string | undefined = undefined) {\n    this.channelUrl = channelUrl;\n    this.authKey = authKey;\n    this.connect();\n  }\n\n  // eslint-disable-next-line  @typescript-eslint/no-explicit-any\n  private log(...args: any[]) {\n    if (this.debug) {\n      console.log('WebRTC-player', ...args);\n    }\n  }\n\n  // eslint-disable-next-line  @typescript-eslint/no-explicit-any\n  private error(...args: any[]) {\n    console.error('WebRTC-player', ...args);\n  }\n\n  private async onConnectionStateChange() {\n    if (this.peer.connectionState === 'failed') {\n      this.emit(Message.PEER_CONNECTION_FAILED);\n      this.peer && this.peer.close();\n\n      if (this.reconnectAttemptsLeft <= 0) {\n        this.error('Connection failed, reconnecting failed');\n        return;\n      }\n\n      this.log(\n        `Connection failed, recreating peer connection, attempts left ${this.reconnectAttemptsLeft}`\n      );\n      await this.connect();\n      this.reconnectAttemptsLeft--;\n    } else if (this.peer.connectionState === 'connected') {\n      this.log('Connected');\n      this.reconnectAttemptsLeft = RECONNECT_ATTEMPTS;\n    }\n  }\n\n  private onErrorHandler(error: string) {\n    this.log(`onError=${error}`);\n    switch (error) {\n      case 'reconnectneeded':\n        this.peer && this.peer.close();\n        this.videoElement.srcObject = null;\n        this.setupPeer();\n        this.adapter.resetPeer(this.peer);\n        this.adapter.connect();\n        break;\n      case 'connectionfailed':\n        this.peer && this.peer.close();\n        this.videoElement.srcObject = null;\n        this.emit(Message.INITIAL_CONNECTION_FAILED);\n        break;\n      case 'connecterror':\n        this.peer && this.peer.close();\n        this.adapter.resetPeer(this.peer);\n        this.emit(Message.CONNECT_ERROR);\n        break;\n    }\n  }\n\n  private async onConnectionStats() {\n    if (this.peer && this.statsTypeFilter) {\n      let bytesReceivedBlock = 0;\n      const stats = await this.peer.getStats(null);\n\n      stats.forEach((report) => {\n        if (report.type.match(this.statsTypeFilter)) {\n          this.emit(`stats:${report.type}`, report);\n        }\n\n        //inbound-rtp attribute bytesReceived from stats report will contain the total number of bytes received for this SSRC.\n        //In this case there are several SSRCs. They are all added together in each onConnectionStats iteration and compared to their value during the previous iteration.\n        if (report.type.match('inbound-rtp')) {\n          bytesReceivedBlock += report.bytesReceived;\n        }\n      });\n\n      if (bytesReceivedBlock <= this.bytesReceived) {\n        this.timeoutThresholdCounter += this.msStatsInterval;\n\n        if (\n          this.mediaTimeoutOccured === false &&\n          this.timeoutThresholdCounter >= this.mediaTimeoutThreshold\n        ) {\n          this.emit(Message.NO_MEDIA);\n          this.mediaTimeoutOccured = true;\n        }\n      } else {\n        this.bytesReceived = bytesReceivedBlock;\n        this.timeoutThresholdCounter = 0;\n\n        if (this.mediaTimeoutOccured == true) {\n          this.emit(Message.MEDIA_RECOVERED);\n          this.mediaTimeoutOccured = false;\n        }\n      }\n    }\n  }\n\n  private setupPeer() {\n    this.peer = new RTCPeerConnection({ iceServers: this.iceServers });\n    this.peer.onconnectionstatechange = this.onConnectionStateChange.bind(this);\n    this.peer.ontrack = this.onTrack.bind(this);\n  }\n\n  private onTrack(event: RTCTrackEvent) {\n    for (const stream of event.streams) {\n      if (stream.id === 'feedbackvideomslabel') {\n        continue;\n      }\n\n      console.log(\n        'Set video element remote stream to ' + stream.id,\n        ' audio ' +\n          stream.getAudioTracks().length +\n          ' video ' +\n          stream.getVideoTracks().length\n      );\n\n      // Create a new MediaStream if we don't have one\n      if (!this.videoElement.srcObject) {\n        this.videoElement.srcObject = new MediaStream();\n      }\n\n      // We might have one stream of both audio and video, or separate streams for audio and video\n      for (const track of stream.getTracks()) {\n        (this.videoElement.srcObject as MediaStream).addTrack(track);\n      }\n    }\n  }\n\n  private async connect() {\n    this.setupPeer();\n\n    if (this.adapterType !== 'custom') {\n      this.adapter = AdapterFactory(\n        this.adapterType,\n        this.peer,\n        this.channelUrl,\n        this.onErrorHandler.bind(this),\n        this.mediaConstraints,\n        this.authKey\n      );\n    } else if (this.adapterFactory) {\n      this.adapter = this.adapterFactory(\n        this.peer,\n        this.channelUrl,\n        this.onErrorHandler.bind(this),\n        this.mediaConstraints,\n        this.authKey\n      );\n    }\n    if (!this.adapter) {\n      throw new Error(`Failed to create adapter (${this.adapterType})`);\n    }\n\n    if (this.debug) {\n      this.adapter.enableDebug();\n    }\n\n    this.statsInterval = setInterval(\n      this.onConnectionStats.bind(this),\n      this.msStatsInterval\n    );\n    try {\n      await this.adapter.connect();\n    } catch (error) {\n      console.error(error);\n      this.stop();\n    }\n  }\n\n  mute() {\n    this.videoElement.muted = true;\n  }\n\n  unmute() {\n    this.videoElement.muted = false;\n  }\n\n  async unload() {\n    await this.adapter.disconnect();\n    this.stop();\n  }\n\n  stop() {\n    clearInterval(this.statsInterval);\n    this.peer.close();\n    this.videoElement.srcObject = null;\n    this.videoElement.load();\n  }\n\n  destroy() {\n    this.stop();\n    this.removeAllListeners();\n  }\n}\n"],"names":[],"version":3,"file":"types.d.ts.map","sourceRoot":"../"}