@craftedxp/voice-js 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +25 -20
  2. package/dist/assistant.d.mts +3 -3
  3. package/dist/assistant.d.ts +3 -3
  4. package/dist/assistant.js +210 -22
  5. package/dist/assistant.js.map +1 -1
  6. package/dist/assistant.mjs +2 -1
  7. package/dist/browser.d.mts +3 -3
  8. package/dist/browser.d.ts +3 -3
  9. package/dist/browser.js +201 -34
  10. package/dist/browser.js.map +1 -1
  11. package/dist/browser.mjs +3 -2
  12. package/dist/browser.mjs.map +1 -1
  13. package/dist/{chunk-ZW22Y67M.mjs → chunk-C2U6V5NI.mjs} +191 -23
  14. package/dist/chunk-C2U6V5NI.mjs.map +1 -0
  15. package/dist/chunk-OS2GZAR7.mjs +18 -0
  16. package/dist/chunk-OS2GZAR7.mjs.map +1 -0
  17. package/dist/{chunk-LV7JGPYW.mjs → chunk-VSY43NGD.mjs} +6 -13
  18. package/dist/chunk-VSY43NGD.mjs.map +1 -0
  19. package/dist/{config-D2TbvIqT.d.mts → config-DfbPB1Tq.d.mts} +22 -3
  20. package/dist/{config-D2TbvIqT.d.ts → config-DfbPB1Tq.d.ts} +22 -3
  21. package/dist/embed.iife.js +42 -5
  22. package/dist/{incomingCall-CfRRzj2P.d.ts → incomingCall-2B3XB8pM.d.mts} +3 -1
  23. package/dist/{incomingCall-CfRRzj2P.d.mts → incomingCall-CYmeQkIw.d.ts} +3 -1
  24. package/dist/node.d.mts +37 -4
  25. package/dist/node.d.ts +37 -4
  26. package/dist/node.js +31 -2
  27. package/dist/node.js.map +1 -1
  28. package/dist/node.mjs +31 -2
  29. package/dist/node.mjs.map +1 -1
  30. package/dist/room.js +15 -12
  31. package/dist/room.js.map +1 -1
  32. package/dist/room.mjs +2 -1
  33. package/dist/transcribe.d.mts +2 -2
  34. package/dist/transcribe.d.ts +2 -2
  35. package/dist/transcribe.js +209 -21
  36. package/dist/transcribe.js.map +1 -1
  37. package/dist/transcribe.mjs +2 -1
  38. package/dist/transcribe.mjs.map +1 -1
  39. package/package.json +1 -1
  40. package/dist/chunk-LV7JGPYW.mjs.map +0 -1
  41. package/dist/chunk-ZW22Y67M.mjs.map +0 -1
@@ -8,7 +8,8 @@ import {
8
8
  handleServerMessage,
9
9
  parseIncomingCall,
10
10
  startTextSession
11
- } from "./chunk-ZW22Y67M.mjs";
11
+ } from "./chunk-C2U6V5NI.mjs";
12
+ import "./chunk-OS2GZAR7.mjs";
12
13
  export {
13
14
  buildWsUrl,
14
15
  configureVoiceClient,
@@ -1,8 +1,8 @@
1
- import { V as VoiceClientConfig, a as VoiceClientFactory } from './config-D2TbvIqT.mjs';
2
- export { C as Call, b as CallEndEvent, c as CallEndReason, d as CallError, e as CallErrorCode, f as CallState, g as ChatEvent, h as ClientTool, i as ClientToolMap, F as FetchToken, j as FetchTokenArgs, k as FetchTokenResult, P as ProtocolCallbacks, l as ProtocolState, S as ServerMessage, m as StartCallOptions, n as StartTextSessionOpts, T as TextSession, o as TranscriptEntry, p as VolumeEvent, q as buildWsUrl, r as createProtocolState, s as handleServerMessage, t as startTextSession } from './config-D2TbvIqT.mjs';
1
+ import { V as VoiceClientConfig, a as VoiceClientFactory } from './config-DfbPB1Tq.mjs';
2
+ export { C as Call, b as CallEndEvent, c as CallEndReason, d as CallError, e as CallErrorCode, f as CallState, g as CallTransport, h as ChatEvent, i as ClientTool, j as ClientToolMap, F as FetchToken, k as FetchTokenArgs, l as FetchTokenResult, P as ProtocolCallbacks, m as ProtocolState, S as ServerMessage, n as StartCallOptions, o as StartTextSessionOpts, T as TextSession, p as TranscriptEntry, q as TransportChangeEvent, r as VolumeEvent, s as buildWsUrl, t as createProtocolState, u as handleServerMessage, v as startTextSession } from './config-DfbPB1Tq.mjs';
3
3
  import { JoinRoomOptions, RoomSession } from './room.mjs';
4
4
  export { AnalysisMessage, RoomEventName, RoomEventPayloads, RoomParticipantInfo, SystemMessage, TranscriptMessage, joinRoom } from './room.mjs';
5
- export { C as CaptureController, a as CaptureOptions, I as IncomingCallPayload, O as OnAgentSpeakingChange, b as OnChunk, c as OnError, d as OnVolume, P as PlaybackController, e as PlaybackOptions, R as RWSEvent, f as RWSOptions, g as ReconnectingWebSocket, W as WebSocketFactory, h as WebSocketLike, i as createAudioCapture, j as createAudioPlayback, k as createReconnectingWebSocket, p as parseIncomingCall } from './incomingCall-CfRRzj2P.mjs';
5
+ export { C as CaptureController, a as CaptureOptions, I as IncomingCallPayload, O as OnAgentSpeakingChange, b as OnChunk, c as OnError, d as OnVolume, P as PlaybackController, e as PlaybackOptions, R as RWSEvent, f as RWSOptions, g as ReconnectingWebSocket, W as WebSocketFactory, h as WebSocketLike, i as createAudioCapture, j as createAudioPlayback, k as createReconnectingWebSocket, p as parseIncomingCall } from './incomingCall-2B3XB8pM.mjs';
6
6
  import 'livekit-client';
7
7
 
8
8
  /**
package/dist/browser.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { V as VoiceClientConfig, a as VoiceClientFactory } from './config-D2TbvIqT.js';
2
- export { C as Call, b as CallEndEvent, c as CallEndReason, d as CallError, e as CallErrorCode, f as CallState, g as ChatEvent, h as ClientTool, i as ClientToolMap, F as FetchToken, j as FetchTokenArgs, k as FetchTokenResult, P as ProtocolCallbacks, l as ProtocolState, S as ServerMessage, m as StartCallOptions, n as StartTextSessionOpts, T as TextSession, o as TranscriptEntry, p as VolumeEvent, q as buildWsUrl, r as createProtocolState, s as handleServerMessage, t as startTextSession } from './config-D2TbvIqT.js';
1
+ import { V as VoiceClientConfig, a as VoiceClientFactory } from './config-DfbPB1Tq.js';
2
+ export { C as Call, b as CallEndEvent, c as CallEndReason, d as CallError, e as CallErrorCode, f as CallState, g as CallTransport, h as ChatEvent, i as ClientTool, j as ClientToolMap, F as FetchToken, k as FetchTokenArgs, l as FetchTokenResult, P as ProtocolCallbacks, m as ProtocolState, S as ServerMessage, n as StartCallOptions, o as StartTextSessionOpts, T as TextSession, p as TranscriptEntry, q as TransportChangeEvent, r as VolumeEvent, s as buildWsUrl, t as createProtocolState, u as handleServerMessage, v as startTextSession } from './config-DfbPB1Tq.js';
3
3
  import { JoinRoomOptions, RoomSession } from './room.js';
4
4
  export { AnalysisMessage, RoomEventName, RoomEventPayloads, RoomParticipantInfo, SystemMessage, TranscriptMessage, joinRoom } from './room.js';
5
- export { C as CaptureController, a as CaptureOptions, I as IncomingCallPayload, O as OnAgentSpeakingChange, b as OnChunk, c as OnError, d as OnVolume, P as PlaybackController, e as PlaybackOptions, R as RWSEvent, f as RWSOptions, g as ReconnectingWebSocket, W as WebSocketFactory, h as WebSocketLike, i as createAudioCapture, j as createAudioPlayback, k as createReconnectingWebSocket, p as parseIncomingCall } from './incomingCall-CfRRzj2P.js';
5
+ export { C as CaptureController, a as CaptureOptions, I as IncomingCallPayload, O as OnAgentSpeakingChange, b as OnChunk, c as OnError, d as OnVolume, P as PlaybackController, e as PlaybackOptions, R as RWSEvent, f as RWSOptions, g as ReconnectingWebSocket, W as WebSocketFactory, h as WebSocketLike, i as createAudioCapture, j as createAudioPlayback, k as createReconnectingWebSocket, p as parseIncomingCall } from './incomingCall-CYmeQkIw.js';
6
6
  import 'livekit-client';
7
7
 
8
8
  /**
package/dist/browser.js CHANGED
@@ -523,7 +523,8 @@ function buildWsUrl(args) {
523
523
  const base = new URL(args.apiBase);
524
524
  const proto = base.protocol === "https:" ? "wss:" : "ws:";
525
525
  const bargeQS = args.bargeIn === false ? "&barge=off" : "";
526
- return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}`;
526
+ const mediaQS = args.media === "livekit" ? "&media=livekit" : "";
527
+ return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}${mediaQS}`;
527
528
  }
528
529
 
529
530
  // src/clientTools.ts
@@ -677,7 +678,75 @@ var createClientMarksBuffer = (args) => {
677
678
  };
678
679
  };
679
680
 
681
+ // src/livekit/createLiveKitMedia.ts
682
+ var toError = (e) => e instanceof Error ? e : new Error(String(e));
683
+ function createLiveKitMedia(lk, opts = {}) {
684
+ let room = null;
685
+ let audioEl = null;
686
+ let muted = false;
687
+ const attach = (track) => {
688
+ if (track.kind !== lk.Track.Kind.Audio) return;
689
+ if (typeof document === "undefined") return;
690
+ if (!audioEl) {
691
+ audioEl = document.createElement("audio");
692
+ audioEl.autoplay = true;
693
+ audioEl.style.display = "none";
694
+ document.body.appendChild(audioEl);
695
+ }
696
+ track.attach(audioEl);
697
+ };
698
+ return {
699
+ connect: async (url, token) => {
700
+ const r = new lk.Room({ adaptiveStream: false, dynacast: false });
701
+ room = r;
702
+ r.on(lk.RoomEvent.TrackSubscribed, (track) => attach(track));
703
+ await r.connect(url, token);
704
+ if (room !== r) {
705
+ return;
706
+ }
707
+ await r.localParticipant.setMicrophoneEnabled(!muted);
708
+ if (room !== r) {
709
+ return;
710
+ }
711
+ try {
712
+ await r.startAudio();
713
+ } catch {
714
+ }
715
+ },
716
+ disconnect: () => {
717
+ const r = room;
718
+ room = null;
719
+ if (r) void r.disconnect().catch(() => void 0);
720
+ audioEl?.remove();
721
+ audioEl = null;
722
+ },
723
+ setMuted: (m) => {
724
+ muted = m;
725
+ const r = room;
726
+ if (!r) return;
727
+ void r.localParticipant.setMicrophoneEnabled(!m).catch((e) => opts.onError?.(toError(e)));
728
+ }
729
+ };
730
+ }
731
+
680
732
  // src/VoiceClient.ts
733
+ var parseMediaFrame = (raw) => {
734
+ if (!raw.includes('"livekit_')) return null;
735
+ try {
736
+ const m = JSON.parse(raw);
737
+ if (m.type === "livekit_join" && typeof m.url === "string" && typeof m.token === "string") {
738
+ return { type: "livekit_join", url: m.url, token: m.token };
739
+ }
740
+ if (m.type === "livekit_fallback") {
741
+ return {
742
+ type: "livekit_fallback",
743
+ reason: typeof m.reason === "string" ? m.reason : void 0
744
+ };
745
+ }
746
+ } catch {
747
+ }
748
+ return null;
749
+ };
681
750
  var BrowserVoiceClient = class {
682
751
  constructor(args) {
683
752
  this.rws = null;
@@ -689,6 +758,9 @@ var BrowserVoiceClient = class {
689
758
  this.startedAt = null;
690
759
  this.endedFired = false;
691
760
  this.lastError = null;
761
+ this.mediaMode = "ws";
762
+ this.lkMedia = null;
763
+ this.lkJoinSeen = false;
692
764
  this.end = () => {
693
765
  this.teardown("user_hangup");
694
766
  };
@@ -696,11 +768,73 @@ var BrowserVoiceClient = class {
696
768
  if (this.muted) return;
697
769
  this.muted = true;
698
770
  this.capture?.mute(true);
771
+ this.lkMedia?.setMuted(true);
699
772
  };
700
773
  this.unmute = () => {
701
774
  if (!this.muted) return;
702
775
  this.muted = false;
703
776
  this.capture?.mute(false);
777
+ this.lkMedia?.setMuted(false);
778
+ };
779
+ this.ensureWsPlayback = async () => {
780
+ if (this.playback) return;
781
+ const createPlayback = this.args.audio?.createPlayback ?? createAudioPlayback;
782
+ this.playback = createPlayback({
783
+ onVolume: (v) => {
784
+ this.outputVolume = v;
785
+ this.args.options.onVolume?.({ input: this.inputVolume, output: v });
786
+ }
787
+ });
788
+ try {
789
+ await this.playback.resume();
790
+ } catch {
791
+ }
792
+ };
793
+ // ---------------------------------------------------------------
794
+ // LiveKit media lifecycle (phase 35)
795
+ // ---------------------------------------------------------------
796
+ this.joinLiveKit = async (url, token) => {
797
+ this.lkJoinSeen = true;
798
+ const lk = this.args.media?.lk;
799
+ if (!lk) {
800
+ this.fallbackToWs("livekit-client unavailable");
801
+ return;
802
+ }
803
+ const media = createLiveKitMedia(lk, {
804
+ onError: (e) => this.emitError({ code: "mic_start_failed", message: e.message })
805
+ });
806
+ this.lkMedia = media;
807
+ if (this.muted) media.setMuted(true);
808
+ try {
809
+ await media.connect(url, token);
810
+ if (this.lkMedia !== media) return;
811
+ this.mediaMode = "livekit";
812
+ this.rws?.send(JSON.stringify({ type: "livekit_ready" }));
813
+ } catch (err) {
814
+ const reason = err instanceof Error ? err.message : String(err);
815
+ media.disconnect();
816
+ if (this.lkMedia === media) this.lkMedia = null;
817
+ this.rws?.send(JSON.stringify({ type: "livekit_failed", reason }));
818
+ this.fallbackToWs(reason);
819
+ }
820
+ };
821
+ // Idempotent: whichever side gives up first (client join error, server
822
+ // livekit_fallback, `connected` without a join) lands here once.
823
+ this.fallbackToWs = (reason) => {
824
+ if (this.mediaMode === "ws") return;
825
+ this.mediaMode = "ws";
826
+ this.lkMedia?.disconnect();
827
+ this.lkMedia = null;
828
+ void this.ensureWsPlayback();
829
+ void this.startCapture();
830
+ this.args.options.onTransportChange?.({ requested: "livekit", actual: "ws", reason });
831
+ };
832
+ this.resetLiveKitForReconnect = () => {
833
+ if (this.args.media?.kind !== "livekit") return;
834
+ this.lkMedia?.disconnect();
835
+ this.lkMedia = null;
836
+ this.lkJoinSeen = false;
837
+ this.mediaMode = "livekit-pending";
704
838
  };
705
839
  // ---------------------------------------------------------------
706
840
  // Internal
@@ -721,17 +855,27 @@ var BrowserVoiceClient = class {
721
855
  this.handleSocketEvent = (ev) => {
722
856
  switch (ev.type) {
723
857
  case "open":
724
- void this.startCapture();
858
+ if (this.mediaMode === "ws") void this.startCapture();
725
859
  break;
726
860
  case "reconnected":
727
861
  this.proto.transcript = [];
728
862
  this.proto.agentBubbleId = null;
729
863
  this.args.options.onTranscript?.(this.proto.transcript);
730
- void this.startCapture();
864
+ this.resetLiveKitForReconnect();
865
+ if (this.mediaMode === "ws") void this.startCapture();
731
866
  this.setState("listening");
732
867
  break;
733
868
  case "message":
734
869
  if (typeof ev.data === "string") {
870
+ const media = parseMediaFrame(ev.data);
871
+ if (media?.type === "livekit_join") {
872
+ if (this.mediaMode === "livekit-pending") void this.joinLiveKit(media.url, media.token);
873
+ break;
874
+ }
875
+ if (media?.type === "livekit_fallback") {
876
+ this.fallbackToWs(media.reason ?? "server_fallback");
877
+ break;
878
+ }
735
879
  handleServerMessage(ev.data, this.proto, {
736
880
  onState: this.setState,
737
881
  onTranscript: (entries) => this.args.options.onTranscript?.(entries),
@@ -748,7 +892,12 @@ var BrowserVoiceClient = class {
748
892
  if (typeof seq === "number") this.marks.onAgentTurnEnd(seq);
749
893
  },
750
894
  onCallEnd: (reason) => this.teardown(reason),
751
- onConnected: () => this.sendClientToolsRegister(),
895
+ onConnected: () => {
896
+ if (this.mediaMode === "livekit-pending" && !this.lkJoinSeen) {
897
+ this.fallbackToWs("server_declined");
898
+ }
899
+ this.sendClientToolsRegister();
900
+ },
752
901
  onClientToolCall: (frame) => dispatchClientToolCall(
753
902
  (f) => this.rws?.send(JSON.stringify(f)),
754
903
  this.args.options.clientTools ?? {},
@@ -756,6 +905,8 @@ var BrowserVoiceClient = class {
756
905
  )
757
906
  });
758
907
  } else {
908
+ if (this.mediaMode === "livekit") break;
909
+ if (!this.playback) void this.ensureWsPlayback();
759
910
  this.marks.markFirstAudibleOutput();
760
911
  this.playback?.enqueue(ev.data);
761
912
  }
@@ -773,7 +924,8 @@ var BrowserVoiceClient = class {
773
924
  };
774
925
  this.startCapture = async () => {
775
926
  if (this.capture?.isCapturing()) return;
776
- this.capture = createAudioCapture({
927
+ const createCapture = this.args.audio?.createCapture ?? createAudioCapture;
928
+ this.capture = createCapture({
777
929
  onChunk: (pcm) => {
778
930
  this.marks.markFirstOutboundAudio();
779
931
  this.rws?.send(pcm);
@@ -802,6 +954,8 @@ var BrowserVoiceClient = class {
802
954
  }
803
955
  this.capture?.stop();
804
956
  this.capture = null;
957
+ this.lkMedia?.disconnect();
958
+ this.lkMedia = null;
805
959
  this.playback?.close();
806
960
  this.playback = null;
807
961
  try {
@@ -854,28 +1008,17 @@ var BrowserVoiceClient = class {
854
1008
  async start() {
855
1009
  this.setState("connecting");
856
1010
  this.startedAt = Date.now();
1011
+ this.mediaMode = this.args.media?.kind === "livekit" ? "livekit-pending" : "ws";
857
1012
  const url = buildWsUrl({
858
1013
  apiBase: this.args.config.apiBase,
859
1014
  agentId: this.args.options.agentId,
860
1015
  token: this.args.token,
861
- bargeIn: this.args.options.bargeIn
862
- });
863
- this.playback = createAudioPlayback({
864
- onVolume: (v) => {
865
- this.outputVolume = v;
866
- this.args.options.onVolume?.({ input: this.inputVolume, output: v });
867
- }
1016
+ bargeIn: this.args.options.bargeIn,
1017
+ media: this.mediaMode === "ws" ? void 0 : "livekit"
868
1018
  });
869
- try {
870
- await this.playback.resume();
871
- } catch {
872
- }
1019
+ if (this.mediaMode === "ws") await this.ensureWsPlayback();
873
1020
  this.rws = createReconnectingWebSocket(
874
- {
875
- url,
876
- wsFactory: this.args.wsFactory,
877
- maxRetries: 3
878
- },
1021
+ { url, wsFactory: this.args.wsFactory, maxRetries: 3 },
879
1022
  (ev) => this.handleSocketEvent(ev)
880
1023
  );
881
1024
  }
@@ -1060,6 +1203,20 @@ async function createWebRtcCall(opts) {
1060
1203
  };
1061
1204
  }
1062
1205
 
1206
+ // src/livekit/loadLiveKit.ts
1207
+ var lkPromise;
1208
+ var loadLiveKit = (requiredBy = "this feature") => {
1209
+ lkPromise ?? (lkPromise = import("livekit-client").catch((e) => {
1210
+ lkPromise = void 0;
1211
+ const wrapped = new Error(
1212
+ `${requiredBy} requires the optional peer dependency 'livekit-client' \u2014 npm install livekit-client`
1213
+ );
1214
+ wrapped.cause = e;
1215
+ throw wrapped;
1216
+ }));
1217
+ return lkPromise;
1218
+ };
1219
+
1063
1220
  // src/textSession.ts
1064
1221
  async function startTextSession(opts) {
1065
1222
  const f = opts.fetch ?? fetch;
@@ -1156,7 +1313,7 @@ var parseIncomingCall = (raw) => {
1156
1313
  if (typeof p.agentId !== "string" || p.agentId.length === 0) {
1157
1314
  throw new Error("parseIncomingCall: missing `agentId`");
1158
1315
  }
1159
- const transport = p.transport === "webrtc" ? "webrtc" : "ws";
1316
+ const transport = p.transport === "webrtc" ? "webrtc" : p.transport === "livekit" ? "livekit" : "ws";
1160
1317
  const out = { token: p.token, agentId: p.agentId, transport };
1161
1318
  if (transport === "webrtc" && typeof p.webrtcGatewayBase === "string") {
1162
1319
  out.webrtcGatewayBase = p.webrtcGatewayBase;
@@ -1213,6 +1370,27 @@ var AssistantVoiceFactory = class {
1213
1370
  clientTools: options.clientTools
1214
1371
  });
1215
1372
  }
1373
+ if (resolved.transport === "livekit") {
1374
+ let lk = null;
1375
+ let loadError = "";
1376
+ try {
1377
+ lk = await loadLiveKit("transport 'livekit'");
1378
+ } catch (e) {
1379
+ loadError = e instanceof Error ? e.message : String(e);
1380
+ }
1381
+ if (!lk) {
1382
+ options.onTransportChange?.({ requested: "livekit", actual: "ws", reason: loadError });
1383
+ }
1384
+ const client2 = new BrowserVoiceClient({
1385
+ config: this.config,
1386
+ options: { ...options, context, metadata },
1387
+ token: resolved.token,
1388
+ wsFactory: browserWsFactory,
1389
+ media: lk ? { kind: "livekit", lk } : void 0
1390
+ });
1391
+ await client2.start();
1392
+ return client2;
1393
+ }
1216
1394
  const client = new BrowserVoiceClient({
1217
1395
  config: this.config,
1218
1396
  // Carry merged context/metadata through to startCall so server can
@@ -1273,17 +1451,6 @@ var decodeAnalysis = (bytes) => {
1273
1451
  };
1274
1452
 
1275
1453
  // src/room.ts
1276
- var lkPromise;
1277
- var loadLiveKit = () => {
1278
- lkPromise ?? (lkPromise = import("livekit-client").catch((e) => {
1279
- lkPromise = void 0;
1280
- const msg = "joinRoom requires the optional peer dependency 'livekit-client' \u2014 npm install livekit-client";
1281
- const wrapped = new Error(msg);
1282
- wrapped.cause = e;
1283
- throw wrapped;
1284
- }));
1285
- return lkPromise;
1286
- };
1287
1454
  var identityToPid = (identity) => identity.startsWith("guest:") ? identity.slice("guest:".length) : identity;
1288
1455
  var joinRoom = async (opts) => {
1289
1456
  const exchangeUrl = `${opts.apiBase.replace(/\/+$/, "")}/v1/rooms/${encodeURIComponent(
@@ -1308,7 +1475,7 @@ var joinRoom = async (opts) => {
1308
1475
  }
1309
1476
  });
1310
1477
  };
1311
- const lk = await loadLiveKit();
1478
+ const lk = await loadLiveKit("joinRoom");
1312
1479
  const trackKind = (t) => t.kind === lk.Track.Kind.Video ? "video" : "audio";
1313
1480
  const trackSource = (s) => {
1314
1481
  switch (s) {