{"version":3,"sources":["../src/track.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n  CreateAudioTrackResponse,\n  CreateVideoTrackResponse,\n  OwnedTrack,\n  StreamState,\n  TrackInfo,\n  TrackKind,\n} from '@livekit/rtc-ffi-bindings';\nimport { CreateAudioTrackRequest, CreateVideoTrackRequest } from '@livekit/rtc-ffi-bindings';\nimport type { AudioSource } from './audio_source.js';\nimport type { AudioStreamSource } from './audio_stream.js';\nimport { FfiClient, FfiHandle } from './ffi_client.js';\nimport type { Room } from './room.js';\nimport type { VideoSource } from './video_source.js';\n\nexport abstract class Track {\n  /** @internal */\n  info?: TrackInfo;\n\n  /** @internal */\n  ffi_handle: FfiHandle;\n\n  private roomRef: WeakRef<Room> | null = null;\n  private audioStreams: Set<WeakRef<AudioStreamSource>> = new Set();\n  private streamFinalizationRegistry: FinalizationRegistry<WeakRef<AudioStreamSource>>;\n  private onRoomTokenRefreshed = () => {\n    const room = this.resolveRoom();\n    if (!room || !room.token || !room.serverUrl) {\n      return;\n    }\n    for (const stream of this.iterateStreams()) {\n      const processor = stream.processor;\n      if (!processor) {\n        continue;\n      }\n      processor.onCredentialsUpdated({ token: room.token, url: room.serverUrl });\n    }\n  };\n\n  constructor(owned: OwnedTrack) {\n    this.info = owned.info;\n    this.ffi_handle = new FfiHandle(owned.handle!.id!);\n    this.streamFinalizationRegistry = new FinalizationRegistry<WeakRef<AudioStreamSource>>(\n      (ref) => {\n        this.audioStreams.delete(ref);\n      },\n    );\n  }\n\n  /** @internal */\n  resolveRoom(): Room | null {\n    return this.roomRef?.deref() ?? null;\n  }\n\n  /** @internal */\n  setRoom(room: Room | null): void {\n    const oldRoom = this.resolveRoom();\n    if (!oldRoom && !room) {\n      // Already roomless — nothing to detach and nothing to re-clear. Without\n      // this guard a second setRoom(null) (e.g. the unpublishTrack /\n      // localTrackUnpublished race calling it from both paths) would re-fire\n      // onStreamInfoCleared / onCredentialsCleared on every registered processor.\n      return;\n    }\n    if (oldRoom && oldRoom !== room) {\n      oldRoom.off('tokenRefreshed', this.onRoomTokenRefreshed);\n    }\n    if (room) {\n      room.off('tokenRefreshed', this.onRoomTokenRefreshed);\n      room.on('tokenRefreshed', this.onRoomTokenRefreshed);\n    }\n    this.roomRef = room ? new WeakRef(room) : null;\n    for (const stream of this.iterateStreams()) {\n      this.pushProcessorMetadataToStream(stream, room);\n    }\n  }\n\n  /** @internal */\n  registerAudioStream(stream: AudioStreamSource): void {\n    const ref = new WeakRef(stream);\n    this.audioStreams.add(ref);\n    this.streamFinalizationRegistry.register(stream, ref);\n    const room = this.resolveRoom();\n    if (room) {\n      this.pushProcessorMetadataToStream(stream, room);\n    }\n  }\n\n  /** @internal */\n  unregisterAudioStream(stream: AudioStreamSource): void {\n    for (const ref of this.audioStreams) {\n      if (ref.deref() === stream) {\n        this.audioStreams.delete(ref);\n        return;\n      }\n    }\n  }\n\n  /**\n   * End every {@link AudioStream} attached to this track.\n   *\n   * @remarks\n   * The FFI keeps feeding an audio stream until the stream's own handle is\n   * dropped, so streams on a track that is no longer subscribed would otherwise\n   * go on delivering audio for the lifetime of the process.\n   *\n   * @internal\n   */\n  closeAudioStreams(): void {\n    for (const stream of [...this.iterateStreams()]) {\n      stream.teardown();\n    }\n    this.audioStreams.clear();\n  }\n\n  private *iterateStreams(): Generator<AudioStreamSource> {\n    const dead: Array<WeakRef<AudioStreamSource>> = [];\n    for (const ref of this.audioStreams) {\n      const stream = ref.deref();\n      if (stream) {\n        yield stream;\n      } else {\n        dead.push(ref);\n      }\n    }\n    for (const ref of dead) {\n      this.audioStreams.delete(ref);\n    }\n  }\n\n  private pushProcessorMetadataToStream(stream: AudioStreamSource, room: Room | null): void {\n    const processor = stream.processor;\n    if (!processor) {\n      return;\n    }\n\n    if (!room) {\n      // Guard with optional-call: plugins built against an older @livekit/rtc-node\n      // inherit a FrameProcessor base class that doesn't define these methods,\n      // so they could be undefined on the prototype chain.\n      processor.onStreamInfoCleared?.();\n      processor.onCredentialsCleared?.();\n      return;\n    }\n\n    let identity = '';\n    let publicationSid = '';\n    const trackSid = this.sid;\n    if (trackSid) {\n      let found = false;\n      for (const participant of room.remoteParticipants.values()) {\n        const publication = participant.trackPublications.get(trackSid);\n        if (publication) {\n          identity = participant.identity;\n          publicationSid = publication.sid ?? '';\n          found = true;\n          break;\n        }\n      }\n      if (!found) {\n        const local = room.localParticipant;\n        if (local) {\n          for (const publication of local.trackPublications.values()) {\n            if (publication.sid === trackSid) {\n              identity = local.identity;\n              publicationSid = publication.sid ?? '';\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    processor.onStreamInfoUpdated({\n      roomName: room.name ?? '',\n      participantIdentity: identity,\n      publicationSid,\n    });\n    if (room.token && room.serverUrl) {\n      processor.onCredentialsUpdated({ token: room.token, url: room.serverUrl });\n    }\n  }\n\n  get sid(): string | undefined {\n    return this.info?.sid;\n  }\n\n  get name(): string | undefined {\n    return this.info?.name;\n  }\n\n  get kind(): TrackKind | undefined {\n    return this.info?.kind;\n  }\n\n  get stream_state(): StreamState | undefined {\n    return this.info?.streamState;\n  }\n\n  get muted(): boolean | undefined {\n    return this.info?.muted;\n  }\n\n  async close() {\n    this.ffi_handle.dispose();\n  }\n}\n\nexport class LocalAudioTrack extends Track {\n  private source?: AudioSource;\n\n  constructor(owned: OwnedTrack, source?: AudioSource) {\n    super(owned);\n    this.source = source;\n  }\n\n  static createAudioTrack(name: string, source: AudioSource): LocalAudioTrack {\n    const req = new CreateAudioTrackRequest({\n      name: name,\n      sourceHandle: source.ffiHandle.handle,\n    });\n\n    const res = FfiClient.instance.request<CreateAudioTrackResponse>({\n      message: { case: 'createAudioTrack', value: req },\n    });\n\n    return new LocalAudioTrack(res.track!, source);\n  }\n\n  async close(closeSource = true) {\n    await super.close();\n    if (closeSource) {\n      await this.source?.close();\n    }\n  }\n}\n\nexport class LocalVideoTrack extends Track {\n  private source?: VideoSource;\n\n  constructor(owned: OwnedTrack, source?: VideoSource) {\n    super(owned);\n    this.source = source;\n  }\n\n  static createVideoTrack(name: string, source: VideoSource): LocalVideoTrack {\n    const req = new CreateVideoTrackRequest({\n      name: name,\n      sourceHandle: source.ffiHandle.handle,\n    });\n\n    const res = FfiClient.instance.request<CreateVideoTrackResponse>({\n      message: { case: 'createVideoTrack', value: req },\n    });\n\n    return new LocalVideoTrack(res.track!, source);\n  }\n\n  async close(closeSource = true) {\n    await super.close();\n    if (closeSource) {\n      await this.source?.close();\n    }\n  }\n}\n\nexport class RemoteVideoTrack extends Track {\n  constructor(owned: OwnedTrack) {\n    super(owned);\n  }\n}\n\nexport class RemoteAudioTrack extends Track {\n  constructor(owned: OwnedTrack) {\n    super(owned);\n  }\n}\n\nexport type LocalTrack = LocalVideoTrack | LocalAudioTrack;\nexport type RemoteTrack = RemoteVideoTrack | RemoteAudioTrack;\nexport type AudioTrack = LocalAudioTrack | RemoteAudioTrack;\nexport type VideoTrack = LocalVideoTrack | RemoteVideoTrack;\n"],"mappings":"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","names":[]}