@memberjunction/ai-realtime-client 6.2.0-edge.2 → 6.2.0-edge.4

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 (206) hide show
  1. package/README.md +156 -29
  2. package/dist/audio/audioMeter.d.ts +71 -0
  3. package/dist/audio/audioMeter.d.ts.map +1 -1
  4. package/dist/audio/audioMeter.js +83 -0
  5. package/dist/audio/audioMeter.js.map +1 -1
  6. package/dist/audio/mediaElementAudioRouter.d.ts +41 -0
  7. package/dist/audio/mediaElementAudioRouter.d.ts.map +1 -0
  8. package/dist/audio/mediaElementAudioRouter.js +82 -0
  9. package/dist/audio/mediaElementAudioRouter.js.map +1 -0
  10. package/dist/audio/micCapture.d.ts +12 -3
  11. package/dist/audio/micCapture.d.ts.map +1 -1
  12. package/dist/audio/micCapture.js +15 -1
  13. package/dist/audio/micCapture.js.map +1 -1
  14. package/dist/audio/pcmPlayback.d.ts +72 -4
  15. package/dist/audio/pcmPlayback.d.ts.map +1 -1
  16. package/dist/audio/pcmPlayback.js +101 -1
  17. package/dist/audio/pcmPlayback.js.map +1 -1
  18. package/dist/drivers/assemblyAIRealtimeClient.d.ts +2 -0
  19. package/dist/drivers/assemblyAIRealtimeClient.d.ts.map +1 -1
  20. package/dist/drivers/assemblyAIRealtimeClient.js +14 -0
  21. package/dist/drivers/assemblyAIRealtimeClient.js.map +1 -1
  22. package/dist/drivers/elevenLabsRealtimeClient.d.ts +2 -0
  23. package/dist/drivers/elevenLabsRealtimeClient.d.ts.map +1 -1
  24. package/dist/drivers/elevenLabsRealtimeClient.js +14 -0
  25. package/dist/drivers/elevenLabsRealtimeClient.js.map +1 -1
  26. package/dist/drivers/geminiAvatarOutput.d.ts +132 -0
  27. package/dist/drivers/geminiAvatarOutput.d.ts.map +1 -0
  28. package/dist/drivers/geminiAvatarOutput.js +310 -0
  29. package/dist/drivers/geminiAvatarOutput.js.map +1 -0
  30. package/dist/drivers/geminiEnterpriseRealtimeClient.d.ts +38 -0
  31. package/dist/drivers/geminiEnterpriseRealtimeClient.d.ts.map +1 -0
  32. package/dist/drivers/geminiEnterpriseRealtimeClient.js +107 -0
  33. package/dist/drivers/geminiEnterpriseRealtimeClient.js.map +1 -0
  34. package/dist/drivers/geminiRealtimeClient.d.ts +314 -48
  35. package/dist/drivers/geminiRealtimeClient.d.ts.map +1 -1
  36. package/dist/drivers/geminiRealtimeClient.js +759 -176
  37. package/dist/drivers/geminiRealtimeClient.js.map +1 -1
  38. package/dist/drivers/openAILiveClient.d.ts +5 -8
  39. package/dist/drivers/openAILiveClient.d.ts.map +1 -1
  40. package/dist/drivers/openAILiveClient.js +19 -32
  41. package/dist/drivers/openAILiveClient.js.map +1 -1
  42. package/dist/drivers/openAIRealtimeClient.d.ts +23 -26
  43. package/dist/drivers/openAIRealtimeClient.d.ts.map +1 -1
  44. package/dist/drivers/openAIRealtimeClient.js +42 -57
  45. package/dist/drivers/openAIRealtimeClient.js.map +1 -1
  46. package/dist/generic/baseRealtimeClient.d.ts +188 -30
  47. package/dist/generic/baseRealtimeClient.d.ts.map +1 -1
  48. package/dist/generic/baseRealtimeClient.js +250 -14
  49. package/dist/generic/baseRealtimeClient.js.map +1 -1
  50. package/dist/generic/openAIProtocolClient.d.ts +57 -1
  51. package/dist/generic/openAIProtocolClient.d.ts.map +1 -1
  52. package/dist/generic/openAIProtocolClient.js +89 -4
  53. package/dist/generic/openAIProtocolClient.js.map +1 -1
  54. package/dist/index.d.ts +2 -3
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +3 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/media/attachVideoSource.d.ts +23 -0
  59. package/dist/media/attachVideoSource.d.ts.map +1 -0
  60. package/dist/media/attachVideoSource.js +49 -0
  61. package/dist/media/attachVideoSource.js.map +1 -0
  62. package/dist/media/channelVideoSource.d.ts +110 -14
  63. package/dist/media/channelVideoSource.d.ts.map +1 -1
  64. package/dist/media/channelVideoSource.js +183 -18
  65. package/dist/media/channelVideoSource.js.map +1 -1
  66. package/dist/media/decoders/frameScheduler.d.ts +119 -0
  67. package/dist/media/decoders/frameScheduler.d.ts.map +1 -0
  68. package/dist/media/decoders/frameScheduler.js +238 -0
  69. package/dist/media/decoders/frameScheduler.js.map +1 -0
  70. package/dist/media/decoders/imageFrameDecoder.d.ts +58 -0
  71. package/dist/media/decoders/imageFrameDecoder.d.ts.map +1 -0
  72. package/dist/media/decoders/imageFrameDecoder.js +110 -0
  73. package/dist/media/decoders/imageFrameDecoder.js.map +1 -0
  74. package/dist/media/decoders/mseFmp4Decoder.d.ts +130 -0
  75. package/dist/media/decoders/mseFmp4Decoder.d.ts.map +1 -0
  76. package/dist/media/decoders/mseFmp4Decoder.js +402 -0
  77. package/dist/media/decoders/mseFmp4Decoder.js.map +1 -0
  78. package/dist/media/decoders/streamCanvas.d.ts +46 -0
  79. package/dist/media/decoders/streamCanvas.d.ts.map +1 -0
  80. package/dist/media/decoders/streamCanvas.js +163 -0
  81. package/dist/media/decoders/streamCanvas.js.map +1 -0
  82. package/dist/media/decoders/videoMimeTypes.d.ts +29 -0
  83. package/dist/media/decoders/videoMimeTypes.d.ts.map +1 -0
  84. package/dist/media/decoders/videoMimeTypes.js +50 -0
  85. package/dist/media/decoders/videoMimeTypes.js.map +1 -0
  86. package/dist/media/decoders/webCodecsChunkDecoder.d.ts +99 -0
  87. package/dist/media/decoders/webCodecsChunkDecoder.d.ts.map +1 -0
  88. package/dist/media/decoders/webCodecsChunkDecoder.js +265 -0
  89. package/dist/media/decoders/webCodecsChunkDecoder.js.map +1 -0
  90. package/dist/media/displayCapture.d.ts +128 -0
  91. package/dist/media/displayCapture.d.ts.map +1 -0
  92. package/dist/media/displayCapture.js +245 -0
  93. package/dist/media/displayCapture.js.map +1 -0
  94. package/dist/media/frameCapture.d.ts +25 -8
  95. package/dist/media/frameCapture.d.ts.map +1 -1
  96. package/dist/media/frameCapture.js +42 -72
  97. package/dist/media/frameCapture.js.map +1 -1
  98. package/dist/media/frameSampler.d.ts +94 -0
  99. package/dist/media/frameSampler.d.ts.map +1 -0
  100. package/dist/media/frameSampler.js +134 -0
  101. package/dist/media/frameSampler.js.map +1 -0
  102. package/dist/media/index.d.ts +32 -0
  103. package/dist/media/index.d.ts.map +1 -0
  104. package/dist/media/index.js +32 -0
  105. package/dist/media/index.js.map +1 -0
  106. package/dist/media/localMediaController.d.ts +116 -0
  107. package/dist/media/localMediaController.d.ts.map +1 -0
  108. package/dist/media/localMediaController.js +281 -0
  109. package/dist/media/localMediaController.js.map +1 -0
  110. package/dist/media/mediaLayout.d.ts +93 -0
  111. package/dist/media/mediaLayout.d.ts.map +1 -0
  112. package/dist/media/mediaLayout.js +187 -0
  113. package/dist/media/mediaLayout.js.map +1 -0
  114. package/dist/media/mediaPreview.d.ts +94 -0
  115. package/dist/media/mediaPreview.d.ts.map +1 -0
  116. package/dist/media/mediaPreview.js +154 -0
  117. package/dist/media/mediaPreview.js.map +1 -0
  118. package/dist/media/mediaStage.d.ts +99 -0
  119. package/dist/media/mediaStage.d.ts.map +1 -0
  120. package/dist/media/mediaStage.js +171 -0
  121. package/dist/media/mediaStage.js.map +1 -0
  122. package/dist/media/model.d.ts +154 -0
  123. package/dist/media/model.d.ts.map +1 -0
  124. package/dist/media/model.js +9 -0
  125. package/dist/media/model.js.map +1 -0
  126. package/dist/media/playbackClock.d.ts +20 -0
  127. package/dist/media/playbackClock.d.ts.map +1 -0
  128. package/dist/media/playbackClock.js +13 -0
  129. package/dist/media/playbackClock.js.map +1 -0
  130. package/dist/media/videoFrameDecoder.d.ts +86 -0
  131. package/dist/media/videoFrameDecoder.d.ts.map +1 -0
  132. package/dist/media/videoFrameDecoder.js +2 -0
  133. package/dist/media/videoFrameDecoder.js.map +1 -0
  134. package/dist/media/videoFrameDecoderRegistry.d.ts +40 -0
  135. package/dist/media/videoFrameDecoderRegistry.d.ts.map +1 -0
  136. package/dist/media/videoFrameDecoderRegistry.js +69 -0
  137. package/dist/media/videoFrameDecoderRegistry.js.map +1 -0
  138. package/dist/media/videoPacing.d.ts +32 -0
  139. package/dist/media/videoPacing.d.ts.map +1 -0
  140. package/dist/media/videoPacing.js +37 -0
  141. package/dist/media/videoPacing.js.map +1 -0
  142. package/dist/media/videoPlayout.d.ts +168 -0
  143. package/dist/media/videoPlayout.d.ts.map +1 -0
  144. package/dist/media/videoPlayout.js +217 -0
  145. package/dist/media/videoPlayout.js.map +1 -0
  146. package/dist/media/videoSourceArbiter.d.ts +250 -0
  147. package/dist/media/videoSourceArbiter.d.ts.map +1 -0
  148. package/dist/media/videoSourceArbiter.js +423 -0
  149. package/dist/media/videoSourceArbiter.js.map +1 -0
  150. package/dist/testing/conformanceAssertions.d.ts +25 -0
  151. package/dist/testing/conformanceAssertions.d.ts.map +1 -0
  152. package/dist/testing/conformanceAssertions.js +64 -0
  153. package/dist/testing/conformanceAssertions.js.map +1 -0
  154. package/dist/testing/conformanceFixtures.d.ts +76 -0
  155. package/dist/testing/conformanceFixtures.d.ts.map +1 -0
  156. package/dist/testing/conformanceFixtures.js +169 -0
  157. package/dist/testing/conformanceFixtures.js.map +1 -0
  158. package/dist/testing/conformanceGates.d.ts +32 -0
  159. package/dist/testing/conformanceGates.d.ts.map +1 -0
  160. package/dist/testing/conformanceGates.js +35 -0
  161. package/dist/testing/conformanceGates.js.map +1 -0
  162. package/dist/testing/conformanceMicrophone.d.ts +10 -0
  163. package/dist/testing/conformanceMicrophone.d.ts.map +1 -0
  164. package/dist/testing/conformanceMicrophone.js +71 -0
  165. package/dist/testing/conformanceMicrophone.js.map +1 -0
  166. package/dist/testing/conformanceTimeline.d.ts +128 -0
  167. package/dist/testing/conformanceTimeline.d.ts.map +1 -0
  168. package/dist/testing/conformanceTimeline.js +32 -0
  169. package/dist/testing/conformanceTimeline.js.map +1 -0
  170. package/dist/testing/conformanceVideoStream.d.ts +29 -0
  171. package/dist/testing/conformanceVideoStream.d.ts.map +1 -0
  172. package/dist/testing/conformanceVideoStream.js +39 -0
  173. package/dist/testing/conformanceVideoStream.js.map +1 -0
  174. package/dist/testing/index.d.ts +29 -0
  175. package/dist/testing/index.d.ts.map +1 -0
  176. package/dist/testing/index.js +28 -0
  177. package/dist/testing/index.js.map +1 -0
  178. package/dist/testing/realtimeVideoConformance.d.ts +23 -0
  179. package/dist/testing/realtimeVideoConformance.d.ts.map +1 -0
  180. package/dist/testing/realtimeVideoConformance.js +139 -0
  181. package/dist/testing/realtimeVideoConformance.js.map +1 -0
  182. package/dist/testing/realtimeVideoConformanceChecks.d.ts +4 -0
  183. package/dist/testing/realtimeVideoConformanceChecks.d.ts.map +1 -0
  184. package/dist/testing/realtimeVideoConformanceChecks.js +164 -0
  185. package/dist/testing/realtimeVideoConformanceChecks.js.map +1 -0
  186. package/dist/testing/realtimeVideoConformanceFrameChecks.d.ts +4 -0
  187. package/dist/testing/realtimeVideoConformanceFrameChecks.d.ts.map +1 -0
  188. package/dist/testing/realtimeVideoConformanceFrameChecks.js +63 -0
  189. package/dist/testing/realtimeVideoConformanceFrameChecks.js.map +1 -0
  190. package/dist/testing/realtimeVideoConformanceSession.d.ts +108 -0
  191. package/dist/testing/realtimeVideoConformanceSession.d.ts.map +1 -0
  192. package/dist/testing/realtimeVideoConformanceSession.js +270 -0
  193. package/dist/testing/realtimeVideoConformanceSession.js.map +1 -0
  194. package/dist/testing/realtimeVideoConformanceTurnChecks.d.ts +4 -0
  195. package/dist/testing/realtimeVideoConformanceTurnChecks.d.ts.map +1 -0
  196. package/dist/testing/realtimeVideoConformanceTurnChecks.js +248 -0
  197. package/dist/testing/realtimeVideoConformanceTurnChecks.js.map +1 -0
  198. package/dist/testing/realtimeVideoConformanceTypes.d.ts +155 -0
  199. package/dist/testing/realtimeVideoConformanceTypes.d.ts.map +1 -0
  200. package/dist/testing/realtimeVideoConformanceTypes.js +2 -0
  201. package/dist/testing/realtimeVideoConformanceTypes.js.map +1 -0
  202. package/dist/testing/recordingPlayout.d.ts +84 -0
  203. package/dist/testing/recordingPlayout.d.ts.map +1 -0
  204. package/dist/testing/recordingPlayout.js +120 -0
  205. package/dist/testing/recordingPlayout.js.map +1 -0
  206. package/package.json +29 -4
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview VIDEO FRAME DECODER REGISTRY: the page's video frame decoders, which `VideoPlayout` chooses from for each
3
+ * frame type. The three built-in decoders (`'mse-fmp4'`, `'webcodecs'`, `'image'`) are registered when the registry is
4
+ * first used; an app adds its own with {@link VideoFrameDecoderRegistry.Register}, or replaces a built-in by registering
5
+ * the same name.
6
+ *
7
+ * @module @memberjunction/ai-realtime-client/media
8
+ */
9
+ import { BaseSingleton } from '@memberjunction/global';
10
+ import type { RealtimeVideoFrameKind } from '@memberjunction/ai';
11
+ import type { VideoFrameDecoderRegistration } from './videoFrameDecoder.js';
12
+ /** The page's video frame decoders, by name. */
13
+ export declare class VideoFrameDecoderRegistry extends BaseSingleton<VideoFrameDecoderRegistry> {
14
+ private readonly registrations;
15
+ protected constructor();
16
+ /** The page's registry, with the built-in decoders registered. */
17
+ static get Instance(): VideoFrameDecoderRegistry;
18
+ /**
19
+ * Adds a decoder. One with the same name is replaced; players that already use the old one keep it until they choose a
20
+ * decoder again.
21
+ *
22
+ * @param registration The decoder: its name, frame kind, priority, support check and factory.
23
+ */
24
+ Register(registration: VideoFrameDecoderRegistration): void;
25
+ /**
26
+ * Removes a decoder by name. Players that already use it keep it until they choose a decoder again.
27
+ *
28
+ * @param name The decoder's name.
29
+ */
30
+ Unregister(name: string): void;
31
+ /**
32
+ * The decoders that can play frames of `kind` and `mimeType` in this browser, highest priority first (in registration
33
+ * order among equals). A decoder whose support check throws is left out.
34
+ *
35
+ * @param kind The frame kind.
36
+ * @param mimeType The frame's MIME type.
37
+ */
38
+ Candidates(kind: RealtimeVideoFrameKind, mimeType: string): readonly VideoFrameDecoderRegistration[];
39
+ }
40
+ //# sourceMappingURL=videoFrameDecoderRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoFrameDecoderRegistry.d.ts","sourceRoot":"","sources":["../../src/media/videoFrameDecoderRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAQzE,gDAAgD;AAChD,qBAAa,yBAA0B,SAAQ,aAAa,CAAC,yBAAyB,CAAC;IACnF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoD;IAElF,SAAS;IAOT,kEAAkE;IAClE,WAAkB,QAAQ,IAAI,yBAAyB,CAEtD;IAED;;;;;OAKG;IACI,QAAQ,CAAC,YAAY,EAAE,6BAA6B,GAAG,IAAI;IAIlE;;;;OAIG;IACI,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIrC;;;;;;OAMG;IACI,UAAU,CAAC,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,6BAA6B,EAAE;CAK9G"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @fileoverview VIDEO FRAME DECODER REGISTRY: the page's video frame decoders, which `VideoPlayout` chooses from for each
3
+ * frame type. The three built-in decoders (`'mse-fmp4'`, `'webcodecs'`, `'image'`) are registered when the registry is
4
+ * first used; an app adds its own with {@link VideoFrameDecoderRegistry.Register}, or replaces a built-in by registering
5
+ * the same name.
6
+ *
7
+ * @module @memberjunction/ai-realtime-client/media
8
+ */
9
+ import { BaseSingleton } from '@memberjunction/global';
10
+ import { IMAGE_FRAME_DECODER } from './decoders/imageFrameDecoder.js';
11
+ import { MSE_FMP4_DECODER } from './decoders/mseFmp4Decoder.js';
12
+ import { WEBCODECS_CHUNK_DECODER } from './decoders/webCodecsChunkDecoder.js';
13
+ /** The decoders every page starts with. */
14
+ const BUILT_IN_DECODERS = [MSE_FMP4_DECODER, WEBCODECS_CHUNK_DECODER, IMAGE_FRAME_DECODER];
15
+ /** The page's video frame decoders, by name. */
16
+ export class VideoFrameDecoderRegistry extends BaseSingleton {
17
+ constructor() {
18
+ super();
19
+ this.registrations = new Map();
20
+ for (const registration of BUILT_IN_DECODERS) {
21
+ this.Register(registration);
22
+ }
23
+ }
24
+ /** The page's registry, with the built-in decoders registered. */
25
+ static get Instance() {
26
+ return super.getInstance();
27
+ }
28
+ /**
29
+ * Adds a decoder. One with the same name is replaced; players that already use the old one keep it until they choose a
30
+ * decoder again.
31
+ *
32
+ * @param registration The decoder: its name, frame kind, priority, support check and factory.
33
+ */
34
+ Register(registration) {
35
+ this.registrations.set(registration.Name, registration);
36
+ }
37
+ /**
38
+ * Removes a decoder by name. Players that already use it keep it until they choose a decoder again.
39
+ *
40
+ * @param name The decoder's name.
41
+ */
42
+ Unregister(name) {
43
+ this.registrations.delete(name);
44
+ }
45
+ /**
46
+ * The decoders that can play frames of `kind` and `mimeType` in this browser, highest priority first (in registration
47
+ * order among equals). A decoder whose support check throws is left out.
48
+ *
49
+ * @param kind The frame kind.
50
+ * @param mimeType The frame's MIME type.
51
+ */
52
+ Candidates(kind, mimeType) {
53
+ return [...this.registrations.values()]
54
+ .filter((registration) => registration.Kind === kind && canPlay(registration, mimeType))
55
+ .sort((a, b) => b.Priority - a.Priority);
56
+ }
57
+ }
58
+ /** A registration's support check, with a check that throws read as "no" (logged). */
59
+ function canPlay(registration, mimeType) {
60
+ try {
61
+ return registration.CanPlay(mimeType);
62
+ }
63
+ catch (error) {
64
+ const reason = error instanceof Error ? error.message : String(error);
65
+ console.warn(`[VideoPlayout] The ${registration.Name} decoder's support check failed for ${mimeType}: ${reason}`);
66
+ return false;
67
+ }
68
+ }
69
+ //# sourceMappingURL=videoFrameDecoderRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoFrameDecoderRegistry.js","sourceRoot":"","sources":["../../src/media/videoFrameDecoderRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,2CAA2C;AAC3C,MAAM,iBAAiB,GAA6C,CAAC,gBAAgB,EAAE,uBAAuB,EAAE,mBAAmB,CAAC,CAAC;AAErI,gDAAgD;AAChD,MAAM,OAAO,yBAA0B,SAAQ,aAAwC;IAGnF;QACI,KAAK,EAAE,CAAC;QAHK,kBAAa,GAAG,IAAI,GAAG,EAAyC,CAAC;QAI9E,KAAK,MAAM,YAAY,IAAI,iBAAiB,EAAE,CAAC;YAC3C,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;IAED,kEAAkE;IAC3D,MAAM,KAAK,QAAQ;QACtB,OAAO,KAAK,CAAC,WAAW,EAA6B,CAAC;IAC1D,CAAC;IAED;;;;;OAKG;IACI,QAAQ,CAAC,YAA2C;QACvD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC5D,CAAC;IAED;;;;OAIG;IACI,UAAU,CAAC,IAAY;QAC1B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACI,UAAU,CAAC,IAA4B,EAAE,QAAgB;QAC5D,OAAO,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;aAClC,MAAM,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;aACvF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;CACJ;AAED,sFAAsF;AACtF,SAAS,OAAO,CAAC,YAA2C,EAAE,QAAgB;IAC1E,IAAI,CAAC;QACD,OAAO,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO,CAAC,IAAI,CAAC,sBAAsB,YAAY,CAAC,IAAI,uCAAuC,QAAQ,KAAK,MAAM,EAAE,CAAC,CAAC;QAClH,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview Frame pacing derived from the NEGOTIATED inbound video rate.
3
+ *
4
+ * The rate a model accepts is per-model data (`MaxInboundVideoRate` in the provider's profile table),
5
+ * carried into the live track descriptor by track negotiation. Every layer that paces frames — the
6
+ * source arbiter, the driver's own backstop — asks THIS module what that rate means in milliseconds, so
7
+ * there is one answer to "how often may a frame go out" rather than a constant per layer.
8
+ *
9
+ * The math itself lives in `@memberjunction/ai` (`realtimeVideoFrames`), shared with the meeting bot that also sends
10
+ * frames to the model; these names are thin wrappers over it.
11
+ *
12
+ * @module @memberjunction/ai-realtime-client
13
+ */
14
+ /**
15
+ * The share of the nominal frame interval a pacer enforces as a MINIMUM spacing. Pacers upstream of
16
+ * the driver run on timers that jitter; enforcing exactly `1000 / rate` would drop a frame that arrived a
17
+ * few milliseconds early. At 1 fps this is the 750 ms backstop the Gemini driver has always applied.
18
+ */
19
+ export declare const VIDEO_FRAME_JITTER_HEADROOM = 0.75;
20
+ /** The rate assumed when none was negotiated: 1 frame per second, the Live API ceiling today. */
21
+ export declare const DEFAULT_INBOUND_VIDEO_RATE = 1;
22
+ /**
23
+ * The nominal interval between frames, in milliseconds, for a negotiated rate (frames per second).
24
+ * An absent, zero, negative or non-finite rate falls back to {@link DEFAULT_INBOUND_VIDEO_RATE}.
25
+ */
26
+ export declare function NominalVideoFrameIntervalMs(rate: number | undefined | null): number;
27
+ /**
28
+ * The minimum spacing, in milliseconds, a pacer should enforce between two frames to the model: the
29
+ * nominal interval less jitter headroom ({@link VIDEO_FRAME_JITTER_HEADROOM}).
30
+ */
31
+ export declare function MinVideoFrameSpacingMs(rate: number | undefined | null): number;
32
+ //# sourceMappingURL=videoPacing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoPacing.d.ts","sourceRoot":"","sources":["../../src/media/videoPacing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,OAAuC,CAAC;AAEhF,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,IAAsC,CAAC;AAE9E;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAEnF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAE9E"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @fileoverview Frame pacing derived from the NEGOTIATED inbound video rate.
3
+ *
4
+ * The rate a model accepts is per-model data (`MaxInboundVideoRate` in the provider's profile table),
5
+ * carried into the live track descriptor by track negotiation. Every layer that paces frames — the
6
+ * source arbiter, the driver's own backstop — asks THIS module what that rate means in milliseconds, so
7
+ * there is one answer to "how often may a frame go out" rather than a constant per layer.
8
+ *
9
+ * The math itself lives in `@memberjunction/ai` (`realtimeVideoFrames`), shared with the meeting bot that also sends
10
+ * frames to the model; these names are thin wrappers over it.
11
+ *
12
+ * @module @memberjunction/ai-realtime-client
13
+ */
14
+ import { REALTIME_DEFAULT_INBOUND_VIDEO_RATE, REALTIME_VIDEO_FRAME_JITTER_HEADROOM, RealtimeMinVideoFrameSpacingMs, RealtimeVideoFrameIntervalMs, } from '@memberjunction/ai';
15
+ /**
16
+ * The share of the nominal frame interval a pacer enforces as a MINIMUM spacing. Pacers upstream of
17
+ * the driver run on timers that jitter; enforcing exactly `1000 / rate` would drop a frame that arrived a
18
+ * few milliseconds early. At 1 fps this is the 750 ms backstop the Gemini driver has always applied.
19
+ */
20
+ export const VIDEO_FRAME_JITTER_HEADROOM = REALTIME_VIDEO_FRAME_JITTER_HEADROOM;
21
+ /** The rate assumed when none was negotiated: 1 frame per second, the Live API ceiling today. */
22
+ export const DEFAULT_INBOUND_VIDEO_RATE = REALTIME_DEFAULT_INBOUND_VIDEO_RATE;
23
+ /**
24
+ * The nominal interval between frames, in milliseconds, for a negotiated rate (frames per second).
25
+ * An absent, zero, negative or non-finite rate falls back to {@link DEFAULT_INBOUND_VIDEO_RATE}.
26
+ */
27
+ export function NominalVideoFrameIntervalMs(rate) {
28
+ return RealtimeVideoFrameIntervalMs(rate);
29
+ }
30
+ /**
31
+ * The minimum spacing, in milliseconds, a pacer should enforce between two frames to the model: the
32
+ * nominal interval less jitter headroom ({@link VIDEO_FRAME_JITTER_HEADROOM}).
33
+ */
34
+ export function MinVideoFrameSpacingMs(rate) {
35
+ return RealtimeMinVideoFrameSpacingMs(rate);
36
+ }
37
+ //# sourceMappingURL=videoPacing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoPacing.js","sourceRoot":"","sources":["../../src/media/videoPacing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACH,mCAAmC,EACnC,oCAAoC,EACpC,8BAA8B,EAC9B,4BAA4B,GAC/B,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,oCAAoC,CAAC;AAEhF,iGAAiG;AACjG,MAAM,CAAC,MAAM,0BAA0B,GAAG,mCAAmC,CAAC;AAE9E;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAA+B;IACvE,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAA+B;IAClE,OAAO,8BAA8B,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @fileoverview VIDEO PLAYOUT: plays a model's video into one `<video>` element at a time, whatever form it arrives in.
3
+ * A provider hands each frame to {@link VideoPlayout.Append} as a `RealtimeVideoFrame` from `@memberjunction/ai`; a
4
+ * renderer shows {@link VideoPlayout.Source} with `AttachVideoSource`.
5
+ *
6
+ * - **A decoder per frame type.** The player keeps one decoder at a time, chosen from the page's
7
+ * {@link VideoFrameDecoderRegistry} by the frame's kind and MIME type: `'mse-fmp4'` plays fragmented MP4 (a Gemini Live
8
+ * avatar) through Media Source Extensions, `'webcodecs'` plays encoded chunks (H.264, VP8, VP9, AV1) through WebCodecs,
9
+ * `'image'` plays still images. A frame of another type switches decoders; a type no decoder plays is dropped and
10
+ * reported once (`'no-decoder'`).
11
+ * - **Ready before the first frame.** When an element is attached first, the player starts the decoder for its
12
+ * configured type ({@link VideoPlayoutOptions.MimeType}), so MSE has its media source open when the first piece comes.
13
+ * A configured type no decoder plays reports `'unsupported'` and leaves the element alone.
14
+ * - **Fallback.** A decoder that gives up (`Failed`) is disposed and not chosen again for that type; the next one that can
15
+ * play it starts at the next init segment or key frame. Out of decoders, frames of that type are dropped; the voice is
16
+ * unaffected, and the tile's stall fallback shows in place of the video.
17
+ * - **The voice.** An avatar's MP4 carries its voice, so the MSE decoder unmutes the element and
18
+ * {@link VideoPlayout.IsPlaying} says whether the agent is audibly speaking. When the voice plays elsewhere,
19
+ * {@link VideoPlayout.CarriesVoice} `false` mutes the element. Chunk and image decoders carry no voice.
20
+ * {@link VideoPlayoutOptions.OnElementAttached} lets the realtime client route the element's audio into its own Web
21
+ * Audio graph, so its meter and its recording carry the avatar's voice.
22
+ * - **Lip sync.** With {@link VideoPlayoutOptions.Clock}, the voice's playback clock, the chunk and image decoders show
23
+ * each frame when the voice reaches its `PresentationTimeMs`. MSE needs no clock: an MP4 that carries the voice keeps
24
+ * voice and face together on its own timestamps.
25
+ * - **End of turn, barge-in, gaps.** {@link VideoPlayout.EndOfTurn} lets playback run to the true end of the turn;
26
+ * {@link VideoPlayout.Flush} drops everything not yet played; between turns, or while a connection resumes, the element
27
+ * holds the last frame. Each decoder does this its own way.
28
+ * - **Browsers.** {@link VideoPlayout.IsSupported} says, at once, whether some decoder can play a type here.
29
+ *
30
+ * @module @memberjunction/ai-realtime-client/media
31
+ */
32
+ import { type RealtimeVideoFrame } from '@memberjunction/ai';
33
+ import type { MediaVideoSource } from './model.js';
34
+ import type { IPlaybackClock } from './playbackClock.js';
35
+ /** The type of a Gemini Live avatar: H.264 Constrained Baseline 3.1 video, AAC-LC audio. */
36
+ export declare const GEMINI_AVATAR_MP4_TYPE = "video/mp4; codecs=\"avc1.42c01f, mp4a.40.2\"";
37
+ export interface VideoPlayoutOptions {
38
+ /**
39
+ * The type, with codecs, the player expects: the decoder for it starts when an element is attached, before any frame
40
+ * arrives, and the MSE source buffer opens with it until an init segment names its own codecs (or names one the reader
41
+ * doesn't know). Defaults to {@link GEMINI_AVATAR_MP4_TYPE}.
42
+ */
43
+ MimeType?: string;
44
+ /** Seconds of played media to keep behind the playhead. Defaults to 10. */
45
+ BackBufferSeconds?: number;
46
+ /**
47
+ * Whether the element plays the media's audio. Defaults to `true`: an avatar's MP4 carries its voice. `false`
48
+ * mutes the element, for a voice that plays elsewhere. {@link VideoPlayout.CarriesVoice} changes it later.
49
+ */
50
+ CarriesVoice?: boolean;
51
+ /**
52
+ * Called with each element the player takes over, before playback starts, such as to route the element's audio
53
+ * into a Web Audio graph. A player that moves to another element calls it again with that one.
54
+ */
55
+ OnElementAttached?: (element: HTMLVideoElement) => void;
56
+ /**
57
+ * The voice's playback clock, for lip sync: the chunk and image decoders show each frame when the voice reaches its
58
+ * `PresentationTimeMs`. Give it when the driver times its PCM (`IRealtimePcmPlayback.Enqueue(pcm16, mediaTimeMs)`) and
59
+ * its frames on one media timeline; `RealtimePcmPlayback` is such a clock. Without it, frames go by their own times.
60
+ * MSE ignores it.
61
+ */
62
+ Clock?: IPlaybackClock;
63
+ }
64
+ /** Why playout reported a problem. */
65
+ export type VideoPlayoutProblem =
66
+ /** The browser cannot play this type: no decoder plays the configured type, or a decoder can't decode the stream's codec. */
67
+ 'unsupported'
68
+ /** No decoder in this browser plays the frame's kind and type; it was dropped. */
69
+ | 'no-decoder'
70
+ /** A media fragment arrived before any init segment, or an encoded chunk before any key frame, and was dropped. */
71
+ | 'fragment-before-init'
72
+ /** The browser rejected a piece (decode error, or out of buffer space after trimming). */
73
+ | 'append-failed'
74
+ /** Media arrived faster than it could be played, or while no element was attached, and the oldest was dropped. */
75
+ | 'pending-overflow';
76
+ /** Whether a piece is an MP4 init segment: its first box is `ftyp`. */
77
+ export declare function IsMp4InitSegment(piece: ArrayBuffer): boolean;
78
+ /**
79
+ * What a realtime driver needs from an avatar's video player: {@link VideoPlayout} in a browser, a fake in tests.
80
+ */
81
+ export interface IAvatarVideoPlayout {
82
+ /** The video to show. A driver hands it to the host once. */
83
+ readonly Source: MediaVideoSource;
84
+ /** Whether it plays forward with media buffered ahead of the playhead: the avatar is audibly speaking. */
85
+ readonly IsPlaying: boolean;
86
+ /**
87
+ * Where the playhead is, in frames: how many of the frames handed to {@link Append} are still to play (waiting,
88
+ * being decoded or appended, or buffered ahead of the playhead). A frame that was dropped, or that no decoder plays,
89
+ * is not counted. Frames play in the order they were handed over, so the frames ahead are the latest ones handed
90
+ * over, less any of those that were dropped: a driver that counts what it hands over can tell whether a given frame
91
+ * has played.
92
+ */
93
+ readonly FramesAhead: number;
94
+ /** Whether the element plays the media's audio; `false` mutes it. */
95
+ CarriesVoice: boolean;
96
+ /**
97
+ * Hands over one frame, in the order they arrived: a piece of fragmented MP4 (an init segment or a media fragment),
98
+ * an encoded chunk or an image. A frame the player has no decoder for is dropped and reported once (`'no-decoder'`).
99
+ */
100
+ Append(frame: RealtimeVideoFrame): void;
101
+ /** The turn's last piece has arrived: playback runs to its true end and holds the last frame. */
102
+ EndOfTurn(): void;
103
+ /** Barge-in: drops everything not yet played and stops at once, holding the last frame. */
104
+ Flush(): void;
105
+ /** Reports problems, once per kind. Returns a function that removes the handler. */
106
+ OnProblem(handler: (problem: VideoPlayoutProblem, message: string) => void): () => void;
107
+ /** Stops playout and releases the element. */
108
+ Dispose(): void;
109
+ }
110
+ /** Plays a model's video frames into one `<video>` element at a time, through the decoder each frame type needs. */
111
+ export declare class VideoPlayout implements IAvatarVideoPlayout {
112
+ private readonly mimeType;
113
+ private readonly backBufferSeconds;
114
+ private readonly onElementAttached;
115
+ private readonly clock;
116
+ private readonly problemHandlers;
117
+ private readonly reported;
118
+ /** Decoders that gave up, by frame type: they are not chosen again for it. */
119
+ private readonly failed;
120
+ private carriesVoice;
121
+ private element;
122
+ private active;
123
+ private disposed;
124
+ /** Whether some decoder can play `mimeType` in this browser, answered at once (for negotiation, before connect). */
125
+ static IsSupported(mimeType?: string): boolean;
126
+ constructor(options?: VideoPlayoutOptions);
127
+ /** The video to show: the player takes over the element it is attached to. */
128
+ get Source(): MediaVideoSource;
129
+ /**
130
+ * Whether the element plays the media's audio. `false` mutes it: the voice plays elsewhere, such as separate PCM
131
+ * audio when the MP4 has no audio track. Takes effect at once on an attached element.
132
+ */
133
+ get CarriesVoice(): boolean;
134
+ set CarriesVoice(value: boolean);
135
+ /** Whether the decoder's own audio is audibly playing: an MP4 that carries the voice, buffered ahead of the playhead. */
136
+ get IsPlaying(): boolean;
137
+ /**
138
+ * How many of the frames handed over are still to play, as the decoder in use counts them. Frames a decoder that
139
+ * gave up held, and frames no decoder plays, never play, so they are not counted.
140
+ */
141
+ get FramesAhead(): number;
142
+ /**
143
+ * Hands over one frame, in the order they arrived, to the decoder for its type: the one in use, or the first the
144
+ * registry offers. A frame no decoder plays is dropped and reported once (`'no-decoder'`).
145
+ */
146
+ Append(frame: RealtimeVideoFrame): void;
147
+ /** The turn's last frame has arrived: playback runs to its true end and holds the last frame. */
148
+ EndOfTurn(): void;
149
+ /** Barge-in: drops everything not yet played and stops at once. The last frame stays on screen. */
150
+ Flush(): void;
151
+ /** Reports problems, once per kind. Returns a function that removes the handler. */
152
+ OnProblem(handler: (problem: VideoPlayoutProblem, message: string) => void): () => void;
153
+ /** Stops playout, releases the element and disposes the decoder. */
154
+ Dispose(): void;
155
+ private attach;
156
+ private detachElement;
157
+ /** The decoder for the configured type, started before any frame so it is ready when the first one comes. */
158
+ private startConfiguredDecoder;
159
+ /** The decoder for a frame type: the one in use when it plays that type, else a new one from the registry. */
160
+ private decoderFor;
161
+ /** What a decoder chosen for `type` plays with and reports through. */
162
+ private contextFor;
163
+ /** A decoder gave up: it is disposed and not chosen again for its type. The next frame of the type picks another. */
164
+ private decoderFailed;
165
+ private releaseDecoder;
166
+ private report;
167
+ }
168
+ //# sourceMappingURL=videoPlayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoPlayout.d.ts","sourceRoot":"","sources":["../../src/media/videoPlayout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @fileoverview VIDEO PLAYOUT: plays a model's video into one `<video>` element at a time, whatever form it arrives in.
3
+ * A provider hands each frame to {@link VideoPlayout.Append} as a `RealtimeVideoFrame` from `@memberjunction/ai`; a
4
+ * renderer shows {@link VideoPlayout.Source} with `AttachVideoSource`.
5
+ *
6
+ * - **A decoder per frame type.** The player keeps one decoder at a time, chosen from the page's
7
+ * {@link VideoFrameDecoderRegistry} by the frame's kind and MIME type: `'mse-fmp4'` plays fragmented MP4 (a Gemini Live
8
+ * avatar) through Media Source Extensions, `'webcodecs'` plays encoded chunks (H.264, VP8, VP9, AV1) through WebCodecs,
9
+ * `'image'` plays still images. A frame of another type switches decoders; a type no decoder plays is dropped and
10
+ * reported once (`'no-decoder'`).
11
+ * - **Ready before the first frame.** When an element is attached first, the player starts the decoder for its
12
+ * configured type ({@link VideoPlayoutOptions.MimeType}), so MSE has its media source open when the first piece comes.
13
+ * A configured type no decoder plays reports `'unsupported'` and leaves the element alone.
14
+ * - **Fallback.** A decoder that gives up (`Failed`) is disposed and not chosen again for that type; the next one that can
15
+ * play it starts at the next init segment or key frame. Out of decoders, frames of that type are dropped; the voice is
16
+ * unaffected, and the tile's stall fallback shows in place of the video.
17
+ * - **The voice.** An avatar's MP4 carries its voice, so the MSE decoder unmutes the element and
18
+ * {@link VideoPlayout.IsPlaying} says whether the agent is audibly speaking. When the voice plays elsewhere,
19
+ * {@link VideoPlayout.CarriesVoice} `false` mutes the element. Chunk and image decoders carry no voice.
20
+ * {@link VideoPlayoutOptions.OnElementAttached} lets the realtime client route the element's audio into its own Web
21
+ * Audio graph, so its meter and its recording carry the avatar's voice.
22
+ * - **Lip sync.** With {@link VideoPlayoutOptions.Clock}, the voice's playback clock, the chunk and image decoders show
23
+ * each frame when the voice reaches its `PresentationTimeMs`. MSE needs no clock: an MP4 that carries the voice keeps
24
+ * voice and face together on its own timestamps.
25
+ * - **End of turn, barge-in, gaps.** {@link VideoPlayout.EndOfTurn} lets playback run to the true end of the turn;
26
+ * {@link VideoPlayout.Flush} drops everything not yet played; between turns, or while a connection resumes, the element
27
+ * holds the last frame. Each decoder does this its own way.
28
+ * - **Browsers.** {@link VideoPlayout.IsSupported} says, at once, whether some decoder can play a type here.
29
+ *
30
+ * @module @memberjunction/ai-realtime-client/media
31
+ */
32
+ import { SniffFmp4Piece } from '@memberjunction/ai';
33
+ import { VideoFrameDecoderRegistry } from './videoFrameDecoderRegistry.js';
34
+ import { FrameKindOfMimeType, FrameTypeKey } from './decoders/videoMimeTypes.js';
35
+ /** The type of a Gemini Live avatar: H.264 Constrained Baseline 3.1 video, AAC-LC audio. */
36
+ export const GEMINI_AVATAR_MP4_TYPE = 'video/mp4; codecs="avc1.42c01f, mp4a.40.2"';
37
+ /** Seconds of already-played media kept behind the playhead before it is removed. */
38
+ const DEFAULT_BACK_BUFFER_SECONDS = 10;
39
+ /** Whether a piece is an MP4 init segment: its first box is `ftyp`. */
40
+ export function IsMp4InitSegment(piece) {
41
+ return SniffFmp4Piece(piece) === 'init';
42
+ }
43
+ /** Plays a model's video frames into one `<video>` element at a time, through the decoder each frame type needs. */
44
+ export class VideoPlayout {
45
+ /** Whether some decoder can play `mimeType` in this browser, answered at once (for negotiation, before connect). */
46
+ static IsSupported(mimeType = GEMINI_AVATAR_MP4_TYPE) {
47
+ const kind = FrameKindOfMimeType(mimeType);
48
+ return kind !== null && VideoFrameDecoderRegistry.Instance.Candidates(kind, mimeType).length > 0;
49
+ }
50
+ constructor(options = {}) {
51
+ this.problemHandlers = new Set();
52
+ this.reported = new Set();
53
+ /** Decoders that gave up, by frame type: they are not chosen again for it. */
54
+ this.failed = new Map();
55
+ this.element = null;
56
+ this.active = null;
57
+ this.disposed = false;
58
+ this.mimeType = options.MimeType ?? GEMINI_AVATAR_MP4_TYPE;
59
+ this.backBufferSeconds = options.BackBufferSeconds ?? DEFAULT_BACK_BUFFER_SECONDS;
60
+ this.carriesVoice = options.CarriesVoice ?? true;
61
+ this.onElementAttached = options.OnElementAttached;
62
+ this.clock = options.Clock;
63
+ }
64
+ /** The video to show: the player takes over the element it is attached to. */
65
+ get Source() {
66
+ return { Kind: 'element', Attach: (element) => this.attach(element) };
67
+ }
68
+ /**
69
+ * Whether the element plays the media's audio. `false` mutes it: the voice plays elsewhere, such as separate PCM
70
+ * audio when the MP4 has no audio track. Takes effect at once on an attached element.
71
+ */
72
+ get CarriesVoice() {
73
+ return this.carriesVoice;
74
+ }
75
+ set CarriesVoice(value) {
76
+ this.carriesVoice = value;
77
+ if (this.element) {
78
+ this.element.muted = !value;
79
+ }
80
+ }
81
+ /** Whether the decoder's own audio is audibly playing: an MP4 that carries the voice, buffered ahead of the playhead. */
82
+ get IsPlaying() {
83
+ return this.active?.Decoder.IsPlaying ?? false;
84
+ }
85
+ /**
86
+ * How many of the frames handed over are still to play, as the decoder in use counts them. Frames a decoder that
87
+ * gave up held, and frames no decoder plays, never play, so they are not counted.
88
+ */
89
+ get FramesAhead() {
90
+ return this.active?.Decoder.FramesAhead ?? 0;
91
+ }
92
+ /**
93
+ * Hands over one frame, in the order they arrived, to the decoder for its type: the one in use, or the first the
94
+ * registry offers. A frame no decoder plays is dropped and reported once (`'no-decoder'`).
95
+ */
96
+ Append(frame) {
97
+ if (this.disposed) {
98
+ return;
99
+ }
100
+ const decoder = this.decoderFor(frame.Kind, frame.MimeType);
101
+ if (!decoder) {
102
+ // A frame it can't play adds no media: the decoder in use, and a turn it was about to end, carry on.
103
+ this.report('no-decoder', `No decoder in this browser plays ${frame.Kind} frames of type ${frame.MimeType}; the frame was dropped.`);
104
+ return;
105
+ }
106
+ decoder.Append(frame);
107
+ }
108
+ /** The turn's last frame has arrived: playback runs to its true end and holds the last frame. */
109
+ EndOfTurn() {
110
+ this.active?.Decoder.EndOfTurn();
111
+ }
112
+ /** Barge-in: drops everything not yet played and stops at once. The last frame stays on screen. */
113
+ Flush() {
114
+ this.active?.Decoder.Flush();
115
+ }
116
+ /** Reports problems, once per kind. Returns a function that removes the handler. */
117
+ OnProblem(handler) {
118
+ this.problemHandlers.add(handler);
119
+ return () => this.problemHandlers.delete(handler);
120
+ }
121
+ /** Stops playout, releases the element and disposes the decoder. */
122
+ Dispose() {
123
+ this.disposed = true;
124
+ this.releaseDecoder();
125
+ this.element = null;
126
+ this.problemHandlers.clear();
127
+ }
128
+ attach(element) {
129
+ if (this.disposed) {
130
+ return () => undefined;
131
+ }
132
+ this.detachElement();
133
+ const decoder = this.active?.Decoder ?? this.startConfiguredDecoder();
134
+ if (!decoder) {
135
+ this.report('unsupported', `This browser cannot play ${this.mimeType}: no decoder plays it.`);
136
+ return () => undefined;
137
+ }
138
+ this.element = element;
139
+ decoder.Attach(element);
140
+ return () => {
141
+ if (this.element === element) {
142
+ this.detachElement();
143
+ }
144
+ };
145
+ }
146
+ detachElement() {
147
+ if (this.element) {
148
+ this.active?.Decoder.Detach();
149
+ this.element = null;
150
+ }
151
+ }
152
+ /** The decoder for the configured type, started before any frame so it is ready when the first one comes. */
153
+ startConfiguredDecoder() {
154
+ const kind = FrameKindOfMimeType(this.mimeType);
155
+ return kind ? this.decoderFor(kind, this.mimeType) : null;
156
+ }
157
+ /** The decoder for a frame type: the one in use when it plays that type, else a new one from the registry. */
158
+ decoderFor(kind, mimeType) {
159
+ const type = FrameTypeKey(kind, mimeType);
160
+ if (this.active?.Type === type) {
161
+ return this.active.Decoder;
162
+ }
163
+ const failed = this.failed.get(type);
164
+ const registration = VideoFrameDecoderRegistry.Instance.Candidates(kind, mimeType).find((candidate) => !failed?.has(candidate.Name));
165
+ if (!registration) {
166
+ return null;
167
+ }
168
+ this.releaseDecoder();
169
+ const decoder = registration.Create(this.contextFor(type, registration.Name));
170
+ this.active = { Type: type, Name: registration.Name, Decoder: decoder };
171
+ if (this.element) {
172
+ decoder.Attach(this.element);
173
+ }
174
+ return decoder;
175
+ }
176
+ /** What a decoder chosen for `type` plays with and reports through. */
177
+ contextFor(type, name) {
178
+ const carriesVoice = () => this.carriesVoice;
179
+ return {
180
+ MimeType: this.mimeType,
181
+ BackBufferSeconds: this.backBufferSeconds,
182
+ get CarriesVoice() {
183
+ return carriesVoice();
184
+ },
185
+ OnElementAttached: this.onElementAttached,
186
+ Clock: this.clock,
187
+ Report: (problem, message) => this.report(problem, message),
188
+ Failed: (message) => this.decoderFailed(type, name, message),
189
+ };
190
+ }
191
+ /** A decoder gave up: it is disposed and not chosen again for its type. The next frame of the type picks another. */
192
+ decoderFailed(type, name, message) {
193
+ console.warn(`[VideoPlayout] The ${name} decoder stopped: ${message}`);
194
+ const failed = this.failed.get(type) ?? new Set();
195
+ failed.add(name);
196
+ this.failed.set(type, failed);
197
+ if (this.active?.Type === type && this.active.Name === name) {
198
+ this.releaseDecoder();
199
+ }
200
+ }
201
+ releaseDecoder() {
202
+ const active = this.active;
203
+ this.active = null;
204
+ active?.Decoder.Dispose();
205
+ }
206
+ report(problem, message) {
207
+ if (this.reported.has(problem)) {
208
+ return;
209
+ }
210
+ this.reported.add(problem);
211
+ console.warn(`[VideoPlayout] ${message}`);
212
+ for (const handler of [...this.problemHandlers]) {
213
+ handler(problem, message);
214
+ }
215
+ }
216
+ }
217
+ //# sourceMappingURL=videoPlayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoPlayout.js","sourceRoot":"","sources":["../../src/media/videoPlayout.ts"],"names":[],"mappings":"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"}