im-rtc-call-engine 1.0.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 (218) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bytes.d.ts +11 -0
  3. package/dist/bytes.d.ts.map +1 -0
  4. package/dist/bytes.js +20 -0
  5. package/dist/bytes.js.map +1 -0
  6. package/dist/callOptions.d.ts +27 -0
  7. package/dist/callOptions.d.ts.map +1 -0
  8. package/dist/callOptions.js +19 -0
  9. package/dist/callOptions.js.map +1 -0
  10. package/dist/deviceId.d.ts +10 -0
  11. package/dist/deviceId.d.ts.map +1 -0
  12. package/dist/deviceId.js +55 -0
  13. package/dist/deviceId.js.map +1 -0
  14. package/dist/engine.d.ts +302 -0
  15. package/dist/engine.d.ts.map +1 -0
  16. package/dist/engine.js +489 -0
  17. package/dist/engine.js.map +1 -0
  18. package/dist/engineBus.d.ts +16 -0
  19. package/dist/engineBus.d.ts.map +1 -0
  20. package/dist/engineBus.js +89 -0
  21. package/dist/engineBus.js.map +1 -0
  22. package/dist/engineWiring.d.ts +40 -0
  23. package/dist/engineWiring.d.ts.map +1 -0
  24. package/dist/engineWiring.js +57 -0
  25. package/dist/engineWiring.js.map +1 -0
  26. package/dist/errors.d.ts +185 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +253 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/eventBus.d.ts +24 -0
  31. package/dist/eventBus.d.ts.map +1 -0
  32. package/dist/eventBus.js +50 -0
  33. package/dist/eventBus.js.map +1 -0
  34. package/dist/events.d.ts +220 -0
  35. package/dist/events.d.ts.map +1 -0
  36. package/dist/events.js +35 -0
  37. package/dist/events.js.map +1 -0
  38. package/dist/frameLoop.d.ts +93 -0
  39. package/dist/frameLoop.d.ts.map +1 -0
  40. package/dist/frameLoop.js +346 -0
  41. package/dist/frameLoop.js.map +1 -0
  42. package/dist/index.d.ts +60 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +42 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/logger.d.ts +64 -0
  47. package/dist/logger.d.ts.map +1 -0
  48. package/dist/logger.js +101 -0
  49. package/dist/logger.js.map +1 -0
  50. package/dist/media/engineMediaApi.d.ts +40 -0
  51. package/dist/media/engineMediaApi.d.ts.map +1 -0
  52. package/dist/media/engineMediaApi.js +71 -0
  53. package/dist/media/engineMediaApi.js.map +1 -0
  54. package/dist/media/firstFrameGate.d.ts +47 -0
  55. package/dist/media/firstFrameGate.d.ts.map +1 -0
  56. package/dist/media/firstFrameGate.js +117 -0
  57. package/dist/media/firstFrameGate.js.map +1 -0
  58. package/dist/media/mediaAdapter.d.ts +135 -0
  59. package/dist/media/mediaAdapter.d.ts.map +1 -0
  60. package/dist/media/mediaAdapter.js +2 -0
  61. package/dist/media/mediaAdapter.js.map +1 -0
  62. package/dist/media/mediaBridge.d.ts +99 -0
  63. package/dist/media/mediaBridge.d.ts.map +1 -0
  64. package/dist/media/mediaBridge.js +186 -0
  65. package/dist/media/mediaBridge.js.map +1 -0
  66. package/dist/media/mediaPlane.d.ts +58 -0
  67. package/dist/media/mediaPlane.d.ts.map +1 -0
  68. package/dist/media/mediaPlane.js +119 -0
  69. package/dist/media/mediaPlane.js.map +1 -0
  70. package/dist/media/videoProfile.d.ts +79 -0
  71. package/dist/media/videoProfile.d.ts.map +1 -0
  72. package/dist/media/videoProfile.js +46 -0
  73. package/dist/media/videoProfile.js.map +1 -0
  74. package/dist/media/videoProfile.test.d.ts +2 -0
  75. package/dist/media/videoProfile.test.d.ts.map +1 -0
  76. package/dist/media/videoProfile.test.js +26 -0
  77. package/dist/media/videoProfile.test.js.map +1 -0
  78. package/dist/media/viewRegistry.d.ts +75 -0
  79. package/dist/media/viewRegistry.d.ts.map +1 -0
  80. package/dist/media/viewRegistry.js +130 -0
  81. package/dist/media/viewRegistry.js.map +1 -0
  82. package/dist/media/webrtcAdapter.d.ts +158 -0
  83. package/dist/media/webrtcAdapter.d.ts.map +1 -0
  84. package/dist/media/webrtcAdapter.js +502 -0
  85. package/dist/media/webrtcAdapter.js.map +1 -0
  86. package/dist/protocolId.d.ts +14 -0
  87. package/dist/protocolId.d.ts.map +1 -0
  88. package/dist/protocolId.js +109 -0
  89. package/dist/protocolId.js.map +1 -0
  90. package/dist/reasons.d.ts +63 -0
  91. package/dist/reasons.d.ts.map +1 -0
  92. package/dist/reasons.js +81 -0
  93. package/dist/reasons.js.map +1 -0
  94. package/dist/redact.d.ts +19 -0
  95. package/dist/redact.d.ts.map +1 -0
  96. package/dist/redact.js +50 -0
  97. package/dist/redact.js.map +1 -0
  98. package/dist/signaling/backoff.d.ts +17 -0
  99. package/dist/signaling/backoff.d.ts.map +1 -0
  100. package/dist/signaling/backoff.js +23 -0
  101. package/dist/signaling/backoff.js.map +1 -0
  102. package/dist/signaling/candidate.d.ts +19 -0
  103. package/dist/signaling/candidate.d.ts.map +1 -0
  104. package/dist/signaling/candidate.js +22 -0
  105. package/dist/signaling/candidate.js.map +1 -0
  106. package/dist/signaling/caseMapping.d.ts +19 -0
  107. package/dist/signaling/caseMapping.d.ts.map +1 -0
  108. package/dist/signaling/caseMapping.js +34 -0
  109. package/dist/signaling/caseMapping.js.map +1 -0
  110. package/dist/signaling/connection.d.ts +110 -0
  111. package/dist/signaling/connection.d.ts.map +1 -0
  112. package/dist/signaling/connection.js +365 -0
  113. package/dist/signaling/connection.js.map +1 -0
  114. package/dist/signaling/connectionFactory.d.ts +55 -0
  115. package/dist/signaling/connectionFactory.d.ts.map +1 -0
  116. package/dist/signaling/connectionFactory.js +29 -0
  117. package/dist/signaling/connectionFactory.js.map +1 -0
  118. package/dist/signaling/connectionTypes.d.ts +94 -0
  119. package/dist/signaling/connectionTypes.d.ts.map +1 -0
  120. package/dist/signaling/connectionTypes.js +2 -0
  121. package/dist/signaling/connectionTypes.js.map +1 -0
  122. package/dist/signaling/discipline.d.ts +6 -0
  123. package/dist/signaling/discipline.d.ts.map +1 -0
  124. package/dist/signaling/discipline.js +100 -0
  125. package/dist/signaling/discipline.js.map +1 -0
  126. package/dist/signaling/enums.d.ts +53 -0
  127. package/dist/signaling/enums.d.ts.map +1 -0
  128. package/dist/signaling/enums.js +52 -0
  129. package/dist/signaling/enums.js.map +1 -0
  130. package/dist/signaling/envelope.d.ts +31 -0
  131. package/dist/signaling/envelope.d.ts.map +1 -0
  132. package/dist/signaling/envelope.js +91 -0
  133. package/dist/signaling/envelope.js.map +1 -0
  134. package/dist/signaling/fieldSpec.d.ts +101 -0
  135. package/dist/signaling/fieldSpec.d.ts.map +1 -0
  136. package/dist/signaling/fieldSpec.js +130 -0
  137. package/dist/signaling/fieldSpec.js.map +1 -0
  138. package/dist/signaling/frameSender.d.ts +34 -0
  139. package/dist/signaling/frameSender.d.ts.map +1 -0
  140. package/dist/signaling/frameSender.js +57 -0
  141. package/dist/signaling/frameSender.js.map +1 -0
  142. package/dist/signaling/frames.call.d.ts +299 -0
  143. package/dist/signaling/frames.call.d.ts.map +1 -0
  144. package/dist/signaling/frames.call.js +149 -0
  145. package/dist/signaling/frames.call.js.map +1 -0
  146. package/dist/signaling/frames.room.d.ts +566 -0
  147. package/dist/signaling/frames.room.d.ts.map +1 -0
  148. package/dist/signaling/frames.room.js +165 -0
  149. package/dist/signaling/frames.room.js.map +1 -0
  150. package/dist/signaling/frames.sys.d.ts +145 -0
  151. package/dist/signaling/frames.sys.d.ts.map +1 -0
  152. package/dist/signaling/frames.sys.js +53 -0
  153. package/dist/signaling/frames.sys.js.map +1 -0
  154. package/dist/signaling/handshakeGiveUp.d.ts +36 -0
  155. package/dist/signaling/handshakeGiveUp.d.ts.map +1 -0
  156. package/dist/signaling/handshakeGiveUp.js +48 -0
  157. package/dist/signaling/handshakeGiveUp.js.map +1 -0
  158. package/dist/signaling/heartbeat.d.ts +34 -0
  159. package/dist/signaling/heartbeat.d.ts.map +1 -0
  160. package/dist/signaling/heartbeat.js +52 -0
  161. package/dist/signaling/heartbeat.js.map +1 -0
  162. package/dist/signaling/pendingRequests.d.ts +33 -0
  163. package/dist/signaling/pendingRequests.d.ts.map +1 -0
  164. package/dist/signaling/pendingRequests.js +57 -0
  165. package/dist/signaling/pendingRequests.js.map +1 -0
  166. package/dist/signaling/reconnector.d.ts +43 -0
  167. package/dist/signaling/reconnector.d.ts.map +1 -0
  168. package/dist/signaling/reconnector.js +76 -0
  169. package/dist/signaling/reconnector.js.map +1 -0
  170. package/dist/signaling/registry.d.ts +100 -0
  171. package/dist/signaling/registry.d.ts.map +1 -0
  172. package/dist/signaling/registry.js +217 -0
  173. package/dist/signaling/registry.js.map +1 -0
  174. package/dist/signaling/resumeDeadline.d.ts +48 -0
  175. package/dist/signaling/resumeDeadline.d.ts.map +1 -0
  176. package/dist/signaling/resumeDeadline.js +89 -0
  177. package/dist/signaling/resumeDeadline.js.map +1 -0
  178. package/dist/signaling/tokenExpiry.d.ts +89 -0
  179. package/dist/signaling/tokenExpiry.d.ts.map +1 -0
  180. package/dist/signaling/tokenExpiry.js +106 -0
  181. package/dist/signaling/tokenExpiry.js.map +1 -0
  182. package/dist/signaling/webSocket.d.ts +59 -0
  183. package/dist/signaling/webSocket.d.ts.map +1 -0
  184. package/dist/signaling/webSocket.js +50 -0
  185. package/dist/signaling/webSocket.js.map +1 -0
  186. package/dist/state/callMachine.d.ts +56 -0
  187. package/dist/state/callMachine.d.ts.map +1 -0
  188. package/dist/state/callMachine.js +176 -0
  189. package/dist/state/callMachine.js.map +1 -0
  190. package/dist/state/callRecv.d.ts +12 -0
  191. package/dist/state/callRecv.d.ts.map +1 -0
  192. package/dist/state/callRecv.js +255 -0
  193. package/dist/state/callRecv.js.map +1 -0
  194. package/dist/state/engineMachine.d.ts +41 -0
  195. package/dist/state/engineMachine.d.ts.map +1 -0
  196. package/dist/state/engineMachine.js +210 -0
  197. package/dist/state/engineMachine.js.map +1 -0
  198. package/dist/state/forceEnd.d.ts +44 -0
  199. package/dist/state/forceEnd.d.ts.map +1 -0
  200. package/dist/state/forceEnd.js +96 -0
  201. package/dist/state/forceEnd.js.map +1 -0
  202. package/dist/state/roomMachine.d.ts +92 -0
  203. package/dist/state/roomMachine.d.ts.map +1 -0
  204. package/dist/state/roomMachine.js +319 -0
  205. package/dist/state/roomMachine.js.map +1 -0
  206. package/dist/state/roomRecv.d.ts +7 -0
  207. package/dist/state/roomRecv.d.ts.map +1 -0
  208. package/dist/state/roomRecv.js +209 -0
  209. package/dist/state/roomRecv.js.map +1 -0
  210. package/dist/state/types.d.ts +61 -0
  211. package/dist/state/types.d.ts.map +1 -0
  212. package/dist/state/types.js +30 -0
  213. package/dist/state/types.js.map +1 -0
  214. package/dist/version.d.ts +11 -0
  215. package/dist/version.d.ts.map +1 -0
  216. package/dist/version.js +11 -0
  217. package/dist/version.js.map +1 -0
  218. package/package.json +35 -0
@@ -0,0 +1,99 @@
1
+ import type { MediaAdapter, MediaAdapterEvents } from './mediaAdapter.js';
2
+ import type { ViewElement } from './viewRegistry.js';
3
+ /** RemoteTrackOwner 是「这条 track_id 是谁的」的最小查询接口。 */
4
+ export interface RemoteTrackOwner {
5
+ readonly uid: string;
6
+ }
7
+ /**
8
+ * MediaBridge 把媒体适配器与 uid↔元素的挂载登记捆在一起。
9
+ *
10
+ * # 为什么捆在一起
11
+ *
12
+ * 这三样东西的生命周期是同一个:**一轮房间**。通话结束、离房、房间被关掉时,
13
+ * PeerConnection 要重建、挂载登记要清空、「首帧抛过没有」也要忘掉。
14
+ * 散在门面里的话,总有一处会在某条结束路径上被漏掉——而漏掉的表现
15
+ * 是下一通电话带着上一通的轨道去协商,服务端只会记一句
16
+ * 「收到未登记的上行 Track」然后丢掉,界面上则是黑屏。
17
+ */
18
+ export declare class MediaBridge {
19
+ readonly adapter: MediaAdapter;
20
+ private readonly views;
21
+ /** 已经抛过「首帧」的轨道,避免重复抛。 */
22
+ private readonly seenVideo;
23
+ /** 开摄像头之后等新画面上屏的那些人(见 awaitFirstVideoFrame)。 */
24
+ private readonly firstFrames;
25
+ private events;
26
+ constructor(adapter: MediaAdapter);
27
+ /** open 建两条 PeerConnection。 */
28
+ open(events: MediaAdapterEvents): void;
29
+ /**
30
+ * reset 把这一轮的媒体全部归零,并重建 PeerConnection。
31
+ *
32
+ * PC 是**跟着房间走**的:服务端每次进房都新建一对,客户端这边不重建的话,
33
+ * 上一轮的 transceiver 还挂在上面,下一轮的 offer 会多出几条服务端不认识的 m-line。
34
+ */
35
+ reset(): void;
36
+ /** close 彻底关掉(logout)。 */
37
+ close(): void;
38
+ /**
39
+ * addRemoteTrack 收下一条下行轨道。
40
+ *
41
+ * uid 允许为空——`ontrack` 与 `room.track_published` 谁先到都可能,
42
+ * 归属后到时由 claim 补上。
43
+ *
44
+ * `onFirstVideo` 在这条视频轨**真的开始出数据**时回调一次,见 waitForVideo。
45
+ */
46
+ addRemoteTrack(trackId: string, track: MediaStreamTrack, uid: string, onFirstVideo: (trackId: string) => void): void;
47
+ /**
48
+ * waitForVideo 等这条视频轨真的开始出数据。
49
+ *
50
+ * **`ontrack` 触发 ≠ 有画面**:远端轨道刚协商完时 `muted === true`,
51
+ * 要等第一个 RTP 包到达才 `unmute`。在 ontrack 那一刻就抛「首帧到达」,
52
+ * UI 会**提前撤掉 loading 然后露出黑屏**——正是这个事件要避免的那件事。
53
+ *
54
+ * 已经 unmute 的(比如轨道复用)就立刻回调,不必等一个永远不会再来的事件。
55
+ */
56
+ private waitForVideo;
57
+ /**
58
+ * syncRemoteTracks 把挂载登记与状态机的远端轨道表对账:**认领新的,摘掉没了的**。
59
+ *
60
+ * # 为什么摘除这一半不能少
61
+ *
62
+ * 原先这里只认领不摘除,`ViewRegistry.removeTrack` 整个是死代码。于是对方关掉摄像头
63
+ * (`room.track_unpublished`)或直接离房之后,那条已经不存在的轨道**仍然挂在该 uid 的
64
+ * `MediaStream` 上、仍然绑在 `<video>.srcObject` 上**:自画 UI 的宿主看到的是一帧
65
+ * 冻住的画面而不是清空(uikit 只是拿 `visibility:hidden` 盖住了它);同一个人反复
66
+ * 开关摄像头还会让他的流里越堆越多条废轨道,直到整轮房间结束才随 `clear()` 释放。
67
+ *
68
+ * # 两条边界
69
+ *
70
+ * - **orphans 不参与对账**:`ontrack` 先到、`track_published` 后到是常态,那时轨道
71
+ * 还不在 remoteTracks 里——按对账扫的话会把刚到的轨道当场摘掉,正好打断认领机制。
72
+ * 只扫**已认领**的。
73
+ * - **本端预览不归房间管**:它用 `:local:` 前缀登记,永远不在 remoteTracks 里,
74
+ * 扫到就跳过。不跳的话拨出中的自拍小窗会被第一次 dispatch 摘掉。
75
+ */
76
+ syncRemoteTracks(remoteTracks: Readonly<Record<string, RemoteTrackOwner>>): void;
77
+ /**
78
+ * awaitFirstVideoFrame 等 uid **开摄像头之后的新画面真的上屏**,到了回调一次(带他此刻的视频轨)。
79
+ *
80
+ * 与 `addRemoteTrack` 的首帧判据是两件事:那条按轨道只抛一次,管的是「轨道开始出数据」;
81
+ * 这条每开一次摄像头等一次,管的是「元素上换成新画面了」。为什么要等见 `FirstFrameGate`。
82
+ */
83
+ awaitFirstVideoFrame(uid: string, onFrame: (trackId: string) => void): void;
84
+ /** attachView 把某个 uid 的远端画面挂到元素上;传 null 卸载。 */
85
+ attachView(uid: string, el: ViewElement | null): void;
86
+ /** attachLocalView 把本端某条轨道挂到元素上做预览;传 null 卸载。 */
87
+ attachLocalView(cid: string, el: ViewElement | null): void;
88
+ /**
89
+ * refreshLocalViews 让本端预览的挂载跟上适配器里的轨道。
90
+ *
91
+ * 本端轨道会在 cid 不变的情况下**换人**:通话中关了摄像头再打开,是重新采集一条、
92
+ * `replaceTrack` 换上去的;进房前关掉摄像头,轨道干脆就没了。`attachLocalView` 只在挂载时取一次轨道,
93
+ * 界面的 effect 又只看 cid——不在这里换掉的话,元素上挂着的还是那条停掉的轨道,自己看到的是定格 / 黑屏。
94
+ */
95
+ refreshLocalViews(): void;
96
+ private clear;
97
+ private videoTrackOf;
98
+ }
99
+ //# sourceMappingURL=mediaBridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaBridge.d.ts","sourceRoot":"","sources":["../../src/media/mediaBridge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,yBAAyB;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;IACpD,OAAO,CAAC,MAAM,CAAmC;gBAErC,OAAO,EAAE,YAAY;IAIjC,+BAA+B;IAC/B,IAAI,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAKtC;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IAMb,0BAA0B;IAC1B,KAAK,IAAI,IAAI;IAMb;;;;;;;OAOG;IACH,cAAc,CACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACtC,IAAI;IAOP;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;IAkBpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,gBAAgB,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,GAAG,IAAI;IAahF;;;;;OAKG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAI3E,+CAA+C;IAC/C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAKrD,iDAAiD;IACjD,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAW1D;;;;;;OAMG;IACH,iBAAiB,IAAI,IAAI;IAWzB,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,YAAY;CAGrB"}
@@ -0,0 +1,186 @@
1
+ import { FirstFrameGate } from './firstFrameGate.js';
2
+ import { ViewRegistry } from './viewRegistry.js';
3
+ /**
4
+ * MediaBridge 把媒体适配器与 uid↔元素的挂载登记捆在一起。
5
+ *
6
+ * # 为什么捆在一起
7
+ *
8
+ * 这三样东西的生命周期是同一个:**一轮房间**。通话结束、离房、房间被关掉时,
9
+ * PeerConnection 要重建、挂载登记要清空、「首帧抛过没有」也要忘掉。
10
+ * 散在门面里的话,总有一处会在某条结束路径上被漏掉——而漏掉的表现
11
+ * 是下一通电话带着上一通的轨道去协商,服务端只会记一句
12
+ * 「收到未登记的上行 Track」然后丢掉,界面上则是黑屏。
13
+ */
14
+ export class MediaBridge {
15
+ adapter;
16
+ views = new ViewRegistry();
17
+ /** 已经抛过「首帧」的轨道,避免重复抛。 */
18
+ seenVideo = new Set();
19
+ /** 开摄像头之后等新画面上屏的那些人(见 awaitFirstVideoFrame)。 */
20
+ firstFrames = new FirstFrameGate();
21
+ events = null;
22
+ constructor(adapter) {
23
+ this.adapter = adapter;
24
+ }
25
+ /** open 建两条 PeerConnection。 */
26
+ open(events) {
27
+ this.events = events;
28
+ this.adapter.open(events);
29
+ }
30
+ /**
31
+ * reset 把这一轮的媒体全部归零,并重建 PeerConnection。
32
+ *
33
+ * PC 是**跟着房间走**的:服务端每次进房都新建一对,客户端这边不重建的话,
34
+ * 上一轮的 transceiver 还挂在上面,下一轮的 offer 会多出几条服务端不认识的 m-line。
35
+ */
36
+ reset() {
37
+ this.clear();
38
+ this.adapter.close();
39
+ if (this.events !== null)
40
+ this.adapter.open(this.events);
41
+ }
42
+ /** close 彻底关掉(logout)。 */
43
+ close() {
44
+ this.clear();
45
+ this.adapter.close();
46
+ this.events = null;
47
+ }
48
+ /**
49
+ * addRemoteTrack 收下一条下行轨道。
50
+ *
51
+ * uid 允许为空——`ontrack` 与 `room.track_published` 谁先到都可能,
52
+ * 归属后到时由 claim 补上。
53
+ *
54
+ * `onFirstVideo` 在这条视频轨**真的开始出数据**时回调一次,见 waitForVideo。
55
+ */
56
+ addRemoteTrack(trackId, track, uid, onFirstVideo) {
57
+ this.views.addTrack(trackId, track, uid);
58
+ if (track.kind !== 'video' || this.seenVideo.has(trackId))
59
+ return;
60
+ this.seenVideo.add(trackId);
61
+ this.waitForVideo(trackId, track, onFirstVideo);
62
+ }
63
+ /**
64
+ * waitForVideo 等这条视频轨真的开始出数据。
65
+ *
66
+ * **`ontrack` 触发 ≠ 有画面**:远端轨道刚协商完时 `muted === true`,
67
+ * 要等第一个 RTP 包到达才 `unmute`。在 ontrack 那一刻就抛「首帧到达」,
68
+ * UI 会**提前撤掉 loading 然后露出黑屏**——正是这个事件要避免的那件事。
69
+ *
70
+ * 已经 unmute 的(比如轨道复用)就立刻回调,不必等一个永远不会再来的事件。
71
+ */
72
+ waitForVideo(trackId, track, onFirstVideo) {
73
+ if (track.muted === false) {
74
+ onFirstVideo(trackId);
75
+ return;
76
+ }
77
+ // 测试里的假轨道可能没有事件接口;没有就退回「协商完即视为到达」,
78
+ // 总比一条都不抛强。
79
+ if (typeof track.addEventListener !== 'function') {
80
+ onFirstVideo(trackId);
81
+ return;
82
+ }
83
+ track.addEventListener('unmute', () => onFirstVideo(trackId), { once: true });
84
+ }
85
+ /**
86
+ * syncRemoteTracks 把挂载登记与状态机的远端轨道表对账:**认领新的,摘掉没了的**。
87
+ *
88
+ * # 为什么摘除这一半不能少
89
+ *
90
+ * 原先这里只认领不摘除,`ViewRegistry.removeTrack` 整个是死代码。于是对方关掉摄像头
91
+ * (`room.track_unpublished`)或直接离房之后,那条已经不存在的轨道**仍然挂在该 uid 的
92
+ * `MediaStream` 上、仍然绑在 `<video>.srcObject` 上**:自画 UI 的宿主看到的是一帧
93
+ * 冻住的画面而不是清空(uikit 只是拿 `visibility:hidden` 盖住了它);同一个人反复
94
+ * 开关摄像头还会让他的流里越堆越多条废轨道,直到整轮房间结束才随 `clear()` 释放。
95
+ *
96
+ * # 两条边界
97
+ *
98
+ * - **orphans 不参与对账**:`ontrack` 先到、`track_published` 后到是常态,那时轨道
99
+ * 还不在 remoteTracks 里——按对账扫的话会把刚到的轨道当场摘掉,正好打断认领机制。
100
+ * 只扫**已认领**的。
101
+ * - **本端预览不归房间管**:它用 `:local:` 前缀登记,永远不在 remoteTracks 里,
102
+ * 扫到就跳过。不跳的话拨出中的自拍小窗会被第一次 dispatch 摘掉。
103
+ */
104
+ syncRemoteTracks(remoteTracks) {
105
+ for (const [trackId, info] of Object.entries(remoteTracks)) {
106
+ this.views.claim(trackId, info.uid);
107
+ }
108
+ for (const [trackId, owner] of this.views.claimedTracks()) {
109
+ if (owner.startsWith(LOCAL_VIEW_PREFIX))
110
+ continue;
111
+ if (Object.hasOwn(remoteTracks, trackId))
112
+ continue;
113
+ this.views.removeTrack(trackId);
114
+ // 忘掉「首帧抛过了」:同一条 track_id 再回来时该重新抛一次 firstVideoFrame。
115
+ this.seenVideo.delete(trackId);
116
+ }
117
+ }
118
+ /**
119
+ * awaitFirstVideoFrame 等 uid **开摄像头之后的新画面真的上屏**,到了回调一次(带他此刻的视频轨)。
120
+ *
121
+ * 与 `addRemoteTrack` 的首帧判据是两件事:那条按轨道只抛一次,管的是「轨道开始出数据」;
122
+ * 这条每开一次摄像头等一次,管的是「元素上换成新画面了」。为什么要等见 `FirstFrameGate`。
123
+ */
124
+ awaitFirstVideoFrame(uid, onFrame) {
125
+ this.firstFrames.arm(uid, this.views.viewOf(uid), () => onFrame(this.videoTrackOf(uid)));
126
+ }
127
+ /** attachView 把某个 uid 的远端画面挂到元素上;传 null 卸载。 */
128
+ attachView(uid, el) {
129
+ this.views.attach(uid, el);
130
+ if (el !== null)
131
+ this.firstFrames.attached(uid, el);
132
+ }
133
+ /** attachLocalView 把本端某条轨道挂到元素上做预览;传 null 卸载。 */
134
+ attachLocalView(cid, el) {
135
+ const key = localViewKey(cid);
136
+ if (el === null) {
137
+ this.views.attach(key, null);
138
+ return;
139
+ }
140
+ const track = this.adapter.localTrack(cid);
141
+ if (track !== undefined)
142
+ this.views.addTrack(track.id, track, key);
143
+ this.views.attach(key, el);
144
+ }
145
+ /**
146
+ * refreshLocalViews 让本端预览的挂载跟上适配器里的轨道。
147
+ *
148
+ * 本端轨道会在 cid 不变的情况下**换人**:通话中关了摄像头再打开,是重新采集一条、
149
+ * `replaceTrack` 换上去的;进房前关掉摄像头,轨道干脆就没了。`attachLocalView` 只在挂载时取一次轨道,
150
+ * 界面的 effect 又只看 cid——不在这里换掉的话,元素上挂着的还是那条停掉的轨道,自己看到的是定格 / 黑屏。
151
+ */
152
+ refreshLocalViews() {
153
+ for (const [trackId, key] of this.views.claimedTracks()) {
154
+ if (!key.startsWith(LOCAL_VIEW_PREFIX))
155
+ continue;
156
+ const current = this.adapter.localTrack(key.slice(LOCAL_VIEW_PREFIX.length));
157
+ if (current?.id === trackId)
158
+ continue;
159
+ // 先摘后挂:流空了会被删掉,再挂时是一条新 MediaStream,<video> 会重新加载。
160
+ this.views.removeTrack(trackId);
161
+ if (current !== undefined)
162
+ this.views.addTrack(current.id, current, key);
163
+ }
164
+ }
165
+ clear() {
166
+ this.views.clear();
167
+ this.seenVideo.clear();
168
+ this.firstFrames.clear();
169
+ }
170
+ videoTrackOf(uid) {
171
+ return this.views.streamFor(uid)?.getTracks().find((track) => track.kind === 'video')?.id ?? '';
172
+ }
173
+ }
174
+ /**
175
+ * LOCAL_VIEW_PREFIX 是本端预览的登记键前缀。
176
+ *
177
+ * 本端与远端共用一张登记表(挂载/卸载逻辑完全一样),所以只需要一个前缀区分开。
178
+ * 前缀里带冒号:uid 是宿主给的业务 id,冒号开头的 uid 本来就不该出现在业务里。
179
+ * `syncRemoteTracks` 的对账要靠它把本端预览摘出去。
180
+ */
181
+ const LOCAL_VIEW_PREFIX = ':local:';
182
+ /** localViewKey 给本端预览一个不会与 uid 撞车的登记键。 */
183
+ function localViewKey(cid) {
184
+ return `${LOCAL_VIEW_PREFIX}${cid}`;
185
+ }
186
+ //# sourceMappingURL=mediaBridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaBridge.js","sourceRoot":"","sources":["../../src/media/mediaBridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { EngineBus } from '../engineBus.js';
2
+ import type { Connection } from '../signaling/connection.js';
3
+ import type { PcRole } from '../signaling/enums.js';
4
+ import type { FrameSender } from '../signaling/frameSender.js';
5
+ import type { MachineInput } from '../state/types.js';
6
+ import type { MediaAdapterEvents } from './mediaAdapter.js';
7
+ import type { MediaBridge } from './mediaBridge.js';
8
+ /**
9
+ * 门面与媒体面之间的**接线**:候选进出、远端轨道落地、PC 状态转成内部事件。
10
+ *
11
+ * 从 `engine.ts` 拆出来的理由与 `roomMachine` / `roomRecv` 那一刀相同(CONVENTIONS §2):
12
+ * 门面负责「路由输入、填 SDP、派发事件」,这一组只负责「把媒体适配器接上总线」,
13
+ * 本来就是两组关注点。
14
+ *
15
+ * **写成自由函数 + 显式依赖,而不是让它认识 `CallEngine`**:
16
+ * 下面这个接口就是这条接线用到的全部东西,多一样都要先写进来——
17
+ * 反向依赖门面的话,「门面自己不做决策」这条约束用肉眼就再也看不出来了。
18
+ */
19
+ export interface MediaPlaneDeps {
20
+ readonly bridge: MediaBridge;
21
+ readonly bus: EngineBus;
22
+ readonly sender: FrameSender;
23
+ /** 当前连接;没登录时为 null。取成函数是因为它会随重连换对象。 */
24
+ connection: () => Connection | null;
25
+ /** 某条远端轨道属于谁;不知道时返回空串。 */
26
+ uidOf: (trackId: string) => string;
27
+ dispatch: (input: MachineInput) => Promise<void>;
28
+ }
29
+ /** mediaEvents 组装交给 MediaAdapter 的那组回调。 */
30
+ export declare function mediaEvents(deps: MediaPlaneDeps): MediaAdapterEvents;
31
+ /**
32
+ * addRemoteCandidate 把服务端来的候选交给媒体层。
33
+ *
34
+ * **这条路径一开始整条漏了**:候选只往上发、不往下收,于是下行连接能不能建立
35
+ * 全看运气——服务端的 SDP 里**碰巧**已经带上了主机候选就通,
36
+ * 没带上(进房即订阅时协商得早,服务端还没收集完)就永远停在 `new`,
37
+ * 界面上是「格子在、画面黑」,而且不报任何错。
38
+ *
39
+ * `candidate` 为空串表示收集结束,协议要求容忍(§3.3)。
40
+ */
41
+ /**
42
+ * 会话恢复之后重新协商上行(协议 §1.4)。
43
+ *
44
+ * 服务端那侧主动重发 `room.offer{pc:"sub"}`,`pub` 这条的 offerer 是本端,只能自己重发。
45
+ *
46
+ * **触发点必须是「恢复之后」,不能只有 [onPcState] 里「PC 判 failed 的那一刻」那一条**:
47
+ * 网一断信令也跟着断,房间立刻变成 `reconnecting`,而 PC 要等约 30 秒才判 `failed`——
48
+ * 那时 `restart_pub_ice` 会被状态机以 `invalid_state` 拒掉,且它**不进 BUFFERABLE_OPS**,
49
+ * 于是永远丢失。iOS 真机 2026-09-07 抓到过实证(`动作被状态机本地拒绝
50
+ * op=restart_pub_ice room_state=reconnecting`),三端同一条路。
51
+ *
52
+ * **不查 PC 当前状态、无条件重启**:换了连接就等于换了网络路径,旧候选多半已废;
53
+ * 服务端那侧也是无条件重启 `sub`,两边对称。多一次协商比漏一次自愈便宜得多。
54
+ * 房间不在 joined 时状态机自会拒掉,不必在这里判。
55
+ */
56
+ export declare function renegotiateAfterResume(deps: MediaPlaneDeps): Promise<void>;
57
+ export declare function addRemoteCandidate(deps: MediaPlaneDeps, addTo: (pc: PcRole, init: RTCIceCandidateInit) => Promise<void>, data: Readonly<Record<string, unknown>>): Promise<void>;
58
+ //# sourceMappingURL=mediaPlane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaPlane.d.ts","sourceRoot":"","sources":["../../src/media/mediaPlane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,uCAAuC;IACvC,UAAU,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC;IACpC,0BAA0B;IAC1B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAgBD,2CAA2C;AAC3C,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,kBAAkB,CAOpE;AAED;;;;;;;;;GASG;AACH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhF;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,EAC/D,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -0,0 +1,119 @@
1
+ import { ErrorCode, RtcError } from '../errors.js';
2
+ import { logger } from '../logger.js';
3
+ import { parseCandidate } from '../signaling/candidate.js';
4
+ /** 连续这么多次 ICE restart 之后仍判 failed,就认为救不回来了(协议 §7.2)。 */
5
+ const PUB_ICE_GIVE_UP = 3;
6
+ /** mediaEvents 组装交给 MediaAdapter 的那组回调。 */
7
+ export function mediaEvents(deps) {
8
+ const pubIce = { restarts: 0, gaveUp: false };
9
+ return {
10
+ onLocalCandidate: (pc, candidate) => sendCandidate(deps, pc, candidate),
11
+ onRemoteTrack: (trackId, track) => onRemoteTrack(deps, trackId, track),
12
+ onConnectionStateChange: (pc, state) => onPcState(deps, pc, state, pubIce),
13
+ };
14
+ }
15
+ /**
16
+ * addRemoteCandidate 把服务端来的候选交给媒体层。
17
+ *
18
+ * **这条路径一开始整条漏了**:候选只往上发、不往下收,于是下行连接能不能建立
19
+ * 全看运气——服务端的 SDP 里**碰巧**已经带上了主机候选就通,
20
+ * 没带上(进房即订阅时协商得早,服务端还没收集完)就永远停在 `new`,
21
+ * 界面上是「格子在、画面黑」,而且不报任何错。
22
+ *
23
+ * `candidate` 为空串表示收集结束,协议要求容忍(§3.3)。
24
+ */
25
+ /**
26
+ * 会话恢复之后重新协商上行(协议 §1.4)。
27
+ *
28
+ * 服务端那侧主动重发 `room.offer{pc:"sub"}`,`pub` 这条的 offerer 是本端,只能自己重发。
29
+ *
30
+ * **触发点必须是「恢复之后」,不能只有 [onPcState] 里「PC 判 failed 的那一刻」那一条**:
31
+ * 网一断信令也跟着断,房间立刻变成 `reconnecting`,而 PC 要等约 30 秒才判 `failed`——
32
+ * 那时 `restart_pub_ice` 会被状态机以 `invalid_state` 拒掉,且它**不进 BUFFERABLE_OPS**,
33
+ * 于是永远丢失。iOS 真机 2026-09-07 抓到过实证(`动作被状态机本地拒绝
34
+ * op=restart_pub_ice room_state=reconnecting`),三端同一条路。
35
+ *
36
+ * **不查 PC 当前状态、无条件重启**:换了连接就等于换了网络路径,旧候选多半已废;
37
+ * 服务端那侧也是无条件重启 `sub`,两边对称。多一次协商比漏一次自愈便宜得多。
38
+ * 房间不在 joined 时状态机自会拒掉,不必在这里判。
39
+ */
40
+ export async function renegotiateAfterResume(deps) {
41
+ deps.bridge.adapter.restartPubICE();
42
+ await deps.dispatch({ kind: 'act', op: 'restart_pub_ice' });
43
+ }
44
+ export async function addRemoteCandidate(deps, addTo, data) {
45
+ const parsed = parseCandidate(data);
46
+ if (parsed === null)
47
+ return; // 空候选 = 收集结束,忽略(§3.3)
48
+ try {
49
+ await addTo(parsed.pc, parsed.init);
50
+ }
51
+ catch (err) {
52
+ // 乱序候选是常态(协议 §3.3 要求容忍):转成 error 事件,不中断事件流。
53
+ deps.bus.emitError(err);
54
+ }
55
+ }
56
+ function sendCandidate(deps, pc, candidate) {
57
+ const connection = deps.connection();
58
+ if (connection === null)
59
+ return;
60
+ void deps.sender
61
+ .sendCandidate(connection, pc, candidate)
62
+ .catch((err) => deps.bus.emitError(err));
63
+ }
64
+ function onRemoteTrack(deps, trackId, track) {
65
+ // uid 可能还不知道(ontrack 与 track_published 谁先到都可能)——
66
+ // 那就先收着,bridge.claim 会在状态机补上归属之后认领。
67
+ const uid = deps.uidOf(trackId);
68
+ // firstVideoFrame 没有对应的信令帧——它是本地事件,UI 用来撤 loading。
69
+ // **等轨道真的出数据才抛**(见 MediaBridge.waitForVideo):ontrack 那一刻
70
+ // 还没有任何一帧,提前抛等于让 UI 撤了 loading 去露黑屏。
71
+ deps.bridge.addRemoteTrack(trackId, track, uid, (id) => {
72
+ // uid 这时可能已经被 track_published 补上了,重新取一次比缓存的准。
73
+ const owner = deps.uidOf(id) || uid;
74
+ deps.bus.emit('firstVideoFrame', { uid: owner, trackId: id });
75
+ });
76
+ deps.bus.emit('remoteTrack', { trackId, track });
77
+ }
78
+ function onPcState(deps, pc, state, pubIce) {
79
+ logger.debug('PC 状态', { pc, state });
80
+ if (pc === 'sub' && state === 'connected') {
81
+ void deps.dispatch({ kind: 'internal', name: 'media_ready' });
82
+ }
83
+ if (pc === 'pub' && state === 'connected') {
84
+ // 救回来了,下一轮重新计数。
85
+ pubIce.restarts = 0;
86
+ pubIce.gaveUp = false;
87
+ }
88
+ /*
89
+ **ICE 失败不是终点,是该重连的信号。**
90
+
91
+ `pub` 那条的 offerer 是本端,只能自己救;`sub` 那条由服务端救(协议 §3.3)。
92
+ 不救的后果:网抖一下(切网、休眠、标签页被节流久了)人就**永久掉出这通通话**,
93
+ 对端的格子从此是一块黑,而界面上一切正常、谁也不挂断。
94
+ 真机联调时抓到过两条 PC 从某一刻起五分钟一轮地失败,再没回到 connected。
95
+ 重启失败还会再进 failed,于是天然形成一个重试节奏。
96
+
97
+ **但重试节奏不能没有尽头**(协议 §7.2):一律自愈、永不上报的话,宿主从头到尾
98
+ 收不到任何信号——上面那段描述的现象会一直挂着,而界面上什么都不会变。
99
+ 连续 PUB_ICE_GIVE_UP 次重启后仍判 failed,抛一次 2006;之后继续重试但不再重复抛。
100
+ */
101
+ if (pc === 'pub' && state === 'failed') {
102
+ logger.info('上行通路失败,重启 ICE', {});
103
+ pubIce.restarts += 1;
104
+ if (pubIce.restarts >= PUB_ICE_GIVE_UP && !pubIce.gaveUp) {
105
+ pubIce.gaveUp = true;
106
+ logger.warn('上行通路连续重启仍失败,上报宿主', { restarts: pubIce.restarts });
107
+ deps.bus.emitError(new RtcError(ErrorCode.mediaNegotiationFailed));
108
+ }
109
+ deps.bridge.adapter.restartPubICE();
110
+ void deps.dispatch({ kind: 'act', op: 'restart_pub_ice' });
111
+ return;
112
+ }
113
+ if (pc === 'sub' && state === 'failed') {
114
+ // sub 那条我们救不了(offerer 是服务端,§3.3),只能立即报给宿主。
115
+ logger.warn('下行通路失败,等服务端重启', {});
116
+ deps.bus.emitError(new RtcError(ErrorCode.mediaNegotiationFailed));
117
+ }
118
+ }
119
+ //# sourceMappingURL=mediaPlane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaPlane.js","sourceRoot":"","sources":["../../src/media/mediaPlane.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 视频画质档位。
3
+ *
4
+ * # 为什么是「宿主给」而不是「服务端下发」
5
+ *
6
+ * 与换 token 是同一条边界(协议 §1.5):**策略归宿主,engine 不自己去要。**
7
+ * 画质该多高取决于宿主的业务(付费档位、当前网络、机型),engine 不认识那些东西。
8
+ * 宿主要「后台可控」就把这个值放进自己的配置接口里下发给 App——
9
+ * 那正是宿主后台该管的事,也不需要动 RTC 协议。
10
+ *
11
+ * (真要让 **RTC 服务端**统一下发,那是往 `sys.hello.ok.limits` 加字段,
12
+ * 等于改五个仓 + 一致性向量,是单独一刀,不该混在别的改动里。)
13
+ *
14
+ * # 档位怎么定
15
+ *
16
+ * 分辨率按主流三档;码率取的是 simulcast 最高层的目标值(协议 §3.5 的表)。
17
+ * **`maxBitrateBps` 必须和服务端 `internal/sfu/bwe.go` 的 `bitrateHigh` 对得上**:
18
+ * 那边拿它做带宽预算,两边不一致的话,带宽估计会按一个错的数字去决定降不降层。
19
+ */
20
+ export interface VideoProfile {
21
+ readonly name: string;
22
+ readonly width: number;
23
+ readonly height: number;
24
+ readonly frameRate: number;
25
+ /** 最高层的目标码率。simulcast 的 m / l 层按 1/3、1/10 折算。 */
26
+ readonly maxBitrateBps: number;
27
+ }
28
+ /** 三个预设档位。默认 720p——1080p 在九宫格里没有意义,只是白烧上行带宽。 */
29
+ export declare const VideoProfiles: {
30
+ readonly p360: {
31
+ readonly name: "360p";
32
+ readonly width: 640;
33
+ readonly height: 360;
34
+ readonly frameRate: 24;
35
+ readonly maxBitrateBps: 500000;
36
+ };
37
+ readonly p720: {
38
+ readonly name: "720p";
39
+ readonly width: 1280;
40
+ readonly height: 720;
41
+ readonly frameRate: 30;
42
+ readonly maxBitrateBps: 1500000;
43
+ };
44
+ readonly p1080: {
45
+ readonly name: "1080p";
46
+ readonly width: 1920;
47
+ readonly height: 1080;
48
+ readonly frameRate: 30;
49
+ readonly maxBitrateBps: 3000000;
50
+ };
51
+ };
52
+ /** 缺省档位。 */
53
+ export declare const defaultVideoProfile: VideoProfile;
54
+ /**
55
+ * simulcastEncodings 把档位翻成三层 simulcast 的编码参数。
56
+ *
57
+ * # rid 必须恰好是 l / m / h
58
+ *
59
+ * 服务端直接拿 RID 当层名(`internal/sfu/layer.go` 的 `ridToLayer`),**不做映射表**。
60
+ * 拼错一个字母就会被当成「空 RID = 单层发布」,于是三层全被认成 h,
61
+ * 层选择彻底失效而且不报错。
62
+ *
63
+ * # 顺序是 l → m → h
64
+ *
65
+ * `scaleResolutionDownBy` 是相对采集分辨率的**缩小倍数**,所以 h 恒为 1。
66
+ * 按由低到高排是各家 SFU 的通行做法(LiveKit 的 q/h/f 同序),
67
+ * Chrome 在这个顺序下最稳。
68
+ *
69
+ * 码率折算沿用 VideoProfile 里写的那套:m 取 1/3、l 取 1/10。
70
+ */
71
+ export declare function simulcastEncodings(profile: VideoProfile): RTCRtpEncodingParameters[];
72
+ /**
73
+ * videoConstraints 把档位翻成 `getUserMedia` 的约束。
74
+ *
75
+ * 用 `ideal` 不用 `exact`:**`exact` 在不支持该分辨率的摄像头上直接抛
76
+ * `OverconstrainedError`,通话就打不出去了**。画质是偏好,不是硬条件。
77
+ */
78
+ export declare function videoConstraints(profile: VideoProfile): MediaTrackConstraints;
79
+ //# sourceMappingURL=videoProfile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoProfile.d.ts","sourceRoot":"","sources":["../../src/media/videoProfile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,gDAAgD;AAChD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;CAIuB,CAAC;AAElD,YAAY;AACZ,eAAO,MAAM,mBAAmB,EAAE,YAAiC,CAAC;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,wBAAwB,EAAE,CAMpF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,GAAG,qBAAqB,CAM7E"}
@@ -0,0 +1,46 @@
1
+ /** 三个预设档位。默认 720p——1080p 在九宫格里没有意义,只是白烧上行带宽。 */
2
+ export const VideoProfiles = {
3
+ p360: { name: '360p', width: 640, height: 360, frameRate: 24, maxBitrateBps: 500_000 },
4
+ p720: { name: '720p', width: 1280, height: 720, frameRate: 30, maxBitrateBps: 1_500_000 },
5
+ p1080: { name: '1080p', width: 1920, height: 1080, frameRate: 30, maxBitrateBps: 3_000_000 },
6
+ };
7
+ /** 缺省档位。 */
8
+ export const defaultVideoProfile = VideoProfiles.p720;
9
+ /**
10
+ * simulcastEncodings 把档位翻成三层 simulcast 的编码参数。
11
+ *
12
+ * # rid 必须恰好是 l / m / h
13
+ *
14
+ * 服务端直接拿 RID 当层名(`internal/sfu/layer.go` 的 `ridToLayer`),**不做映射表**。
15
+ * 拼错一个字母就会被当成「空 RID = 单层发布」,于是三层全被认成 h,
16
+ * 层选择彻底失效而且不报错。
17
+ *
18
+ * # 顺序是 l → m → h
19
+ *
20
+ * `scaleResolutionDownBy` 是相对采集分辨率的**缩小倍数**,所以 h 恒为 1。
21
+ * 按由低到高排是各家 SFU 的通行做法(LiveKit 的 q/h/f 同序),
22
+ * Chrome 在这个顺序下最稳。
23
+ *
24
+ * 码率折算沿用 VideoProfile 里写的那套:m 取 1/3、l 取 1/10。
25
+ */
26
+ export function simulcastEncodings(profile) {
27
+ return [
28
+ { rid: 'l', scaleResolutionDownBy: 4, maxBitrate: Math.round(profile.maxBitrateBps / 10) },
29
+ { rid: 'm', scaleResolutionDownBy: 2, maxBitrate: Math.round(profile.maxBitrateBps / 3) },
30
+ { rid: 'h', scaleResolutionDownBy: 1, maxBitrate: profile.maxBitrateBps },
31
+ ];
32
+ }
33
+ /**
34
+ * videoConstraints 把档位翻成 `getUserMedia` 的约束。
35
+ *
36
+ * 用 `ideal` 不用 `exact`:**`exact` 在不支持该分辨率的摄像头上直接抛
37
+ * `OverconstrainedError`,通话就打不出去了**。画质是偏好,不是硬条件。
38
+ */
39
+ export function videoConstraints(profile) {
40
+ return {
41
+ width: { ideal: profile.width },
42
+ height: { ideal: profile.height },
43
+ frameRate: { ideal: profile.frameRate },
44
+ };
45
+ }
46
+ //# sourceMappingURL=videoProfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoProfile.js","sourceRoot":"","sources":["../../src/media/videoProfile.ts"],"names":[],"mappings":"AA4BA,gDAAgD;AAChD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE;IACtF,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE;IACzF,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE;CAC7C,CAAC;AAElD,YAAY;AACZ,MAAM,CAAC,MAAM,mBAAmB,GAAiB,aAAa,CAAC,IAAI,CAAC;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAqB;IACtD,OAAO;QACL,EAAE,GAAG,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,GAAG,EAAE,CAAC,EAAE;QAC1F,EAAE,GAAG,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,GAAG,CAAC,CAAC,EAAE;QACzF,EAAE,GAAG,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,aAAa,EAAE;KAC1E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAqB;IACpD,OAAO;QACL,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;QAC/B,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE;QACjC,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=videoProfile.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoProfile.test.d.ts","sourceRoot":"","sources":["../../src/media/videoProfile.test.ts"],"names":[],"mappings":""}