{"version":3,"sources":["../src/audio_source.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n  AudioSourceInfo,\n  CaptureAudioFrameCallback,\n  CaptureAudioFrameResponse,\n  ClearAudioBufferResponse,\n  NewAudioSourceResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport {\n  AudioSourceType,\n  CaptureAudioFrameRequest,\n  ClearAudioBufferRequest,\n  FfiHandle,\n  NewAudioSourceRequest,\n} from '@livekit/rtc-ffi-bindings';\nimport type { AudioFrame } from './audio_frame.js';\nimport { FfiClient } from './ffi_client.js';\n\nexport class AudioSource {\n  /** @internal */\n  info: AudioSourceInfo;\n  /** @internal */\n  ffiHandle: FfiHandle;\n  /** @internal */\n  lastCapture: number;\n  /** @internal */\n  currentQueueSize: number;\n  /** @internal */\n  release = () => {};\n  promise = this.newPromise();\n  /** @internal */\n  timeout?: ReturnType<typeof setTimeout> = undefined;\n  /** @internal */\n  closed = false;\n\n  sampleRate: number;\n  numChannels: number;\n  queueSize: number;\n\n  constructor(sampleRate: number, numChannels: number, queueSize = 1000) {\n    this.sampleRate = sampleRate;\n    this.numChannels = numChannels;\n    this.queueSize = queueSize;\n\n    this.lastCapture = 0;\n    this.currentQueueSize = 0;\n\n    const req = new NewAudioSourceRequest({\n      type: AudioSourceType.AUDIO_SOURCE_NATIVE,\n      sampleRate: sampleRate,\n      numChannels: numChannels,\n      queueSizeMs: queueSize,\n    });\n\n    const res = FfiClient.instance.request<NewAudioSourceResponse>({\n      message: {\n        case: 'newAudioSource',\n        value: req,\n      },\n    });\n\n    this.info = res.source!.info!;\n    this.ffiHandle = new FfiHandle(res.source!.handle!.id!);\n  }\n\n  get queuedDuration(): number {\n    return Math.max(\n      this.currentQueueSize - Number(process.hrtime.bigint() / BigInt(1000000)) + this.lastCapture,\n      0,\n    );\n  }\n\n  clearQueue() {\n    const req = new ClearAudioBufferRequest({\n      sourceHandle: this.ffiHandle.handle,\n    });\n\n    FfiClient.instance.request<ClearAudioBufferResponse>({\n      message: {\n        case: 'clearAudioBuffer',\n        value: req,\n      },\n    });\n\n    this.currentQueueSize = 0;\n    this.release();\n  }\n\n  /** @internal */\n  async newPromise() {\n    return new Promise<void>((resolve) => {\n      this.release = resolve;\n    });\n  }\n\n  async waitForPlayout() {\n    return this.promise.then(() => {\n      this.lastCapture = 0;\n      this.currentQueueSize = 0;\n      this.promise = this.newPromise();\n      this.timeout = undefined;\n    });\n  }\n\n  async captureFrame(frame: AudioFrame) {\n    if (this.closed) {\n      throw new Error('AudioSource is closed');\n    }\n\n    if (frame.samplesPerChannel === 0) {\n      return;\n    }\n\n    const now = Number(process.hrtime.bigint() / BigInt(1000000));\n    const elapsed = this.lastCapture === 0 ? 0 : now - this.lastCapture;\n    const frameDurationMs = (frame.samplesPerChannel / frame.sampleRate) * 1000;\n    this.currentQueueSize = Math.max(this.currentQueueSize - elapsed, 0) + frameDurationMs;\n\n    this.lastCapture = now;\n\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n    }\n\n    this.timeout = setTimeout(this.release, this.currentQueueSize);\n\n    const req = new CaptureAudioFrameRequest({\n      sourceHandle: this.ffiHandle.handle,\n      buffer: frame.protoInfo(),\n    });\n\n    const res = FfiClient.instance.request<CaptureAudioFrameResponse>({\n      message: { case: 'captureAudioFrame', value: req },\n    });\n\n    const cb = await FfiClient.instance.waitFor<CaptureAudioFrameCallback>((ev) => {\n      return ev.message.case == 'captureAudioFrame' && ev.message.value.asyncId == res.asyncId;\n    });\n\n    if (cb.error) {\n      throw new Error(cb.error);\n    }\n  }\n\n  async close() {\n    // Clear any pending playout timeout so its callback doesn't fire after\n    // the handle is disposed, which would reference freed native state.\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.timeout = undefined;\n    }\n    // Resolve any pending waitForPlayout() promise so callers don't hang.\n    this.release();\n    this.ffiHandle.dispose();\n    this.closed = true;\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,8BAMO;AAEP,wBAA0B;AAEnB,MAAM,YAAY;AAAA,EAqBvB,YAAY,YAAoB,aAAqB,YAAY,KAAM;AAXvE;AAAA,mBAAU,MAAM;AAAA,IAAC;AACjB,mBAAU,KAAK,WAAW;AAE1B;AAAA,mBAA0C;AAE1C;AAAA,kBAAS;AAOP,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,YAAY;AAEjB,SAAK,cAAc;AACnB,SAAK,mBAAmB;AAExB,UAAM,MAAM,IAAI,8CAAsB;AAAA,MACpC,MAAM,wCAAgB;AAAA,MACtB;AAAA,MACA;AAAA,MACA,aAAa;AAAA,IACf,CAAC;AAED,UAAM,MAAM,4BAAU,SAAS,QAAgC;AAAA,MAC7D,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,SAAK,OAAO,IAAI,OAAQ;AACxB,SAAK,YAAY,IAAI,kCAAU,IAAI,OAAQ,OAAQ,EAAG;AAAA,EACxD;AAAA,EAEA,IAAI,iBAAyB;AAC3B,WAAO,KAAK;AAAA,MACV,KAAK,mBAAmB,OAAO,QAAQ,OAAO,OAAO,IAAI,OAAO,GAAO,CAAC,IAAI,KAAK;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAa;AACX,UAAM,MAAM,IAAI,gDAAwB;AAAA,MACtC,cAAc,KAAK,UAAU;AAAA,IAC/B,CAAC;AAED,gCAAU,SAAS,QAAkC;AAAA,MACnD,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,SAAK,mBAAmB;AACxB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,MAAM,aAAa;AACjB,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,WAAK,UAAU;AAAA,IACjB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,iBAAiB;AACrB,WAAO,KAAK,QAAQ,KAAK,MAAM;AAC7B,WAAK,cAAc;AACnB,WAAK,mBAAmB;AACxB,WAAK,UAAU,KAAK,WAAW;AAC/B,WAAK,UAAU;AAAA,IACjB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,OAAmB;AACpC,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AAEA,QAAI,MAAM,sBAAsB,GAAG;AACjC;AAAA,IACF;AAEA,UAAM,MAAM,OAAO,QAAQ,OAAO,OAAO,IAAI,OAAO,GAAO,CAAC;AAC5D,UAAM,UAAU,KAAK,gBAAgB,IAAI,IAAI,MAAM,KAAK;AACxD,UAAM,kBAAmB,MAAM,oBAAoB,MAAM,aAAc;AACvE,SAAK,mBAAmB,KAAK,IAAI,KAAK,mBAAmB,SAAS,CAAC,IAAI;AAEvE,SAAK,cAAc;AAEnB,QAAI,KAAK,SAAS;AAChB,mBAAa,KAAK,OAAO;AAAA,IAC3B;AAEA,SAAK,UAAU,WAAW,KAAK,SAAS,KAAK,gBAAgB;AAE7D,UAAM,MAAM,IAAI,iDAAyB;AAAA,MACvC,cAAc,KAAK,UAAU;AAAA,MAC7B,QAAQ,MAAM,UAAU;AAAA,IAC1B,CAAC;AAED,UAAM,MAAM,4BAAU,SAAS,QAAmC;AAAA,MAChE,SAAS,EAAE,MAAM,qBAAqB,OAAO,IAAI;AAAA,IACnD,CAAC;AAED,UAAM,KAAK,MAAM,4BAAU,SAAS,QAAmC,CAAC,OAAO;AAC7E,aAAO,GAAG,QAAQ,QAAQ,uBAAuB,GAAG,QAAQ,MAAM,WAAW,IAAI;AAAA,IACnF,CAAC;AAED,QAAI,GAAG,OAAO;AACZ,YAAM,IAAI,MAAM,GAAG,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AAGZ,QAAI,KAAK,SAAS;AAChB,mBAAa,KAAK,OAAO;AACzB,WAAK,UAAU;AAAA,IACjB;AAEA,SAAK,QAAQ;AACb,SAAK,UAAU,QAAQ;AACvB,SAAK,SAAS;AAAA,EAChB;AACF;","names":[]}