@vizor-vr/player 0.2.0 → 0.2.1

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 (184) hide show
  1. package/README.md +4 -0
  2. package/dist/__tests__/autoplay.test.d.ts +2 -0
  3. package/dist/__tests__/autoplay.test.d.ts.map +1 -0
  4. package/dist/__tests__/chapter-logic.test.d.ts +2 -0
  5. package/dist/__tests__/chapter-logic.test.d.ts.map +1 -0
  6. package/dist/__tests__/chapter-manager.test.d.ts +2 -0
  7. package/dist/__tests__/chapter-manager.test.d.ts.map +1 -0
  8. package/dist/__tests__/chapter-menu.test.d.ts +2 -0
  9. package/dist/__tests__/chapter-menu.test.d.ts.map +1 -0
  10. package/dist/__tests__/dom-drm.test.d.ts +2 -0
  11. package/dist/__tests__/dom-drm.test.d.ts.map +1 -0
  12. package/dist/__tests__/drm-gate.test.d.ts +2 -0
  13. package/dist/__tests__/drm-gate.test.d.ts.map +1 -0
  14. package/dist/__tests__/fairplay.test.d.ts +2 -0
  15. package/dist/__tests__/fairplay.test.d.ts.map +1 -0
  16. package/dist/__tests__/hub-controller.test.d.ts +2 -0
  17. package/dist/__tests__/hub-controller.test.d.ts.map +1 -0
  18. package/dist/__tests__/hub-layout.test.d.ts +2 -0
  19. package/dist/__tests__/hub-layout.test.d.ts.map +1 -0
  20. package/dist/__tests__/hub-model.test.d.ts +2 -0
  21. package/dist/__tests__/hub-model.test.d.ts.map +1 -0
  22. package/dist/__tests__/hub-panel-renderer.test.d.ts +2 -0
  23. package/dist/__tests__/hub-panel-renderer.test.d.ts.map +1 -0
  24. package/dist/__tests__/lens-angle.test.d.ts +2 -0
  25. package/dist/__tests__/lens-angle.test.d.ts.map +1 -0
  26. package/dist/__tests__/media-element-drm-gate.test.d.ts +2 -0
  27. package/dist/__tests__/media-element-drm-gate.test.d.ts.map +1 -0
  28. package/dist/__tests__/media-element-gating.test.d.ts +2 -0
  29. package/dist/__tests__/media-element-gating.test.d.ts.map +1 -0
  30. package/dist/__tests__/player-version-lock.test.d.ts +2 -0
  31. package/dist/__tests__/player-version-lock.test.d.ts.map +1 -0
  32. package/dist/__tests__/source-manager-drm.test.d.ts +2 -0
  33. package/dist/__tests__/source-manager-drm.test.d.ts.map +1 -0
  34. package/dist/__tests__/vz-chapter.test.d.ts +2 -0
  35. package/dist/__tests__/vz-chapter.test.d.ts.map +1 -0
  36. package/dist/__tests__/vz-cinema-gating.test.d.ts +2 -0
  37. package/dist/__tests__/vz-cinema-gating.test.d.ts.map +1 -0
  38. package/dist/__tests__/vz-hub-item.test.d.ts +2 -0
  39. package/dist/__tests__/vz-hub-item.test.d.ts.map +1 -0
  40. package/dist/__tests__/vz-tour-gating.test.d.ts +2 -0
  41. package/dist/__tests__/vz-tour-gating.test.d.ts.map +1 -0
  42. package/dist/__tests__/watch-time.test.d.ts +2 -0
  43. package/dist/__tests__/watch-time.test.d.ts.map +1 -0
  44. package/dist/analytics/index.d.ts.map +1 -1
  45. package/dist/annotations/annotation-popup.d.ts.map +1 -1
  46. package/dist/{annotations-C0ugfCn4.js → annotations-Du6ZWrqC.js} +82 -13
  47. package/dist/annotations-Du6ZWrqC.js.map +1 -0
  48. package/dist/captions/caption-renderer.d.ts.map +1 -1
  49. package/dist/{captions-BP0H2E03.js → captions-YvnHAwLY.js} +4 -4
  50. package/dist/captions-YvnHAwLY.js.map +1 -0
  51. package/dist/chapters/chapter-logic.d.ts +47 -0
  52. package/dist/chapters/chapter-logic.d.ts.map +1 -0
  53. package/dist/chapters/chapter-manager.d.ts +42 -0
  54. package/dist/chapters/chapter-manager.d.ts.map +1 -0
  55. package/dist/chapters/chapter-menu.d.ts +24 -0
  56. package/dist/chapters/chapter-menu.d.ts.map +1 -0
  57. package/dist/{collab-BXPomrS0.js → collab-BLpllHtb.js} +20 -14
  58. package/dist/collab-BLpllHtb.js.map +1 -0
  59. package/dist/collaborative/chat-button.d.ts.map +1 -1
  60. package/dist/collaborative/voice-button.d.ts.map +1 -1
  61. package/dist/controls/cc-button.d.ts.map +1 -1
  62. package/dist/controls/compass.d.ts.map +1 -1
  63. package/dist/controls/controls-overlay.d.ts.map +1 -1
  64. package/dist/controls/fullscreen-button.d.ts.map +1 -1
  65. package/dist/controls/icons.d.ts +1 -0
  66. package/dist/controls/icons.d.ts.map +1 -1
  67. package/dist/controls/live-indicator.d.ts.map +1 -1
  68. package/dist/controls/play-button.d.ts.map +1 -1
  69. package/dist/controls/playlist-controls.d.ts.map +1 -1
  70. package/dist/controls/quality-menu.d.ts.map +1 -1
  71. package/dist/controls/seek-bar.d.ts +9 -0
  72. package/dist/controls/seek-bar.d.ts.map +1 -1
  73. package/dist/controls/volume-control.d.ts.map +1 -1
  74. package/dist/controls/vr-button.d.ts.map +1 -1
  75. package/dist/core/base-element.d.ts +8 -0
  76. package/dist/core/base-element.d.ts.map +1 -1
  77. package/dist/core/media-element.d.ts +70 -1
  78. package/dist/core/media-element.d.ts.map +1 -1
  79. package/dist/{dash-adapter-tid9ZyhO.js → dash-adapter-BMpHWcBA.js} +9 -2
  80. package/dist/dash-adapter-BMpHWcBA.js.map +1 -0
  81. package/dist/delivery/__tests__/delivery-client.test.d.ts +2 -0
  82. package/dist/delivery/__tests__/delivery-client.test.d.ts.map +1 -0
  83. package/dist/delivery/__tests__/delivery-hydrate.test.d.ts +2 -0
  84. package/dist/delivery/__tests__/delivery-hydrate.test.d.ts.map +1 -0
  85. package/dist/delivery/delivery-client.d.ts +27 -0
  86. package/dist/delivery/delivery-client.d.ts.map +1 -0
  87. package/dist/delivery/delivery-hydrate.d.ts +18 -0
  88. package/dist/delivery/delivery-hydrate.d.ts.map +1 -0
  89. package/dist/delivery/dom-drm.d.ts +9 -0
  90. package/dist/delivery/dom-drm.d.ts.map +1 -0
  91. package/dist/elements/vz-chapter.d.ts +27 -0
  92. package/dist/elements/vz-chapter.d.ts.map +1 -0
  93. package/dist/elements/vz-cinema.d.ts +20 -0
  94. package/dist/elements/vz-cinema.d.ts.map +1 -1
  95. package/dist/elements/vz-hub-item.d.ts +23 -0
  96. package/dist/elements/vz-hub-item.d.ts.map +1 -0
  97. package/dist/elements/vz-hub.d.ts +57 -0
  98. package/dist/elements/vz-hub.d.ts.map +1 -0
  99. package/dist/elements/vz-playlist.d.ts +14 -0
  100. package/dist/elements/vz-playlist.d.ts.map +1 -1
  101. package/dist/elements/vz-tour.d.ts +16 -0
  102. package/dist/elements/vz-tour.d.ts.map +1 -1
  103. package/dist/hls-adapter-C_a65zia.js +161 -0
  104. package/dist/hls-adapter-C_a65zia.js.map +1 -0
  105. package/dist/hub/hub-controller.d.ts +31 -0
  106. package/dist/hub/hub-controller.d.ts.map +1 -0
  107. package/dist/hub/hub-layout.d.ts +23 -0
  108. package/dist/hub/hub-layout.d.ts.map +1 -0
  109. package/dist/hub/hub-model.d.ts +27 -0
  110. package/dist/hub/hub-model.d.ts.map +1 -0
  111. package/dist/hub/hub-panel-manager.d.ts +16 -0
  112. package/dist/hub/hub-panel-manager.d.ts.map +1 -0
  113. package/dist/hub/hub-panel-renderer.d.ts +26 -0
  114. package/dist/hub/hub-panel-renderer.d.ts.map +1 -0
  115. package/dist/license/__tests__/cache.test.d.ts +2 -0
  116. package/dist/license/__tests__/cache.test.d.ts.map +1 -0
  117. package/dist/license/__tests__/feature-gate.test.d.ts +2 -0
  118. package/dist/license/__tests__/feature-gate.test.d.ts.map +1 -0
  119. package/dist/license/__tests__/format-gate.test.d.ts +2 -0
  120. package/dist/license/__tests__/format-gate.test.d.ts.map +1 -0
  121. package/dist/license/__tests__/license-checker.test.d.ts.map +1 -0
  122. package/dist/license/__tests__/master-key.test.d.ts.map +1 -0
  123. package/dist/license/__tests__/verify-no-ed25519.test.d.ts +2 -0
  124. package/dist/license/__tests__/verify-no-ed25519.test.d.ts.map +1 -0
  125. package/dist/license/__tests__/verify.test.d.ts +2 -0
  126. package/dist/license/__tests__/verify.test.d.ts.map +1 -0
  127. package/dist/license/cache.d.ts +4 -0
  128. package/dist/license/cache.d.ts.map +1 -0
  129. package/dist/license/drm-gate.d.ts +15 -0
  130. package/dist/license/drm-gate.d.ts.map +1 -0
  131. package/dist/license/feature-gate.d.ts +27 -0
  132. package/dist/license/feature-gate.d.ts.map +1 -0
  133. package/dist/license/format-gate.d.ts +23 -0
  134. package/dist/license/format-gate.d.ts.map +1 -0
  135. package/dist/license/license-checker.d.ts.map +1 -1
  136. package/dist/license/master-key.d.ts +11 -7
  137. package/dist/license/master-key.d.ts.map +1 -1
  138. package/dist/license/trusted-key.d.ts +7 -0
  139. package/dist/license/trusted-key.d.ts.map +1 -0
  140. package/dist/license/verify.d.ts +7 -0
  141. package/dist/license/verify.d.ts.map +1 -0
  142. package/dist/{register-CwnmiGEs.js → register-BmZt_300.js} +1482 -163
  143. package/dist/register-BmZt_300.js.map +1 -0
  144. package/dist/register.d.ts.map +1 -1
  145. package/dist/register.js +1 -1
  146. package/dist/renderer/camera-controller.d.ts +1 -1
  147. package/dist/renderer/camera-controller.d.ts.map +1 -1
  148. package/dist/renderer/projections/fisheye-projection.d.ts +4 -0
  149. package/dist/renderer/projections/fisheye-projection.d.ts.map +1 -1
  150. package/dist/renderer/projections/spherical-projection.d.ts +26 -0
  151. package/dist/renderer/projections/spherical-projection.d.ts.map +1 -0
  152. package/dist/renderer/renderer.d.ts.map +1 -1
  153. package/dist/renderer/shaders/fisheye.glsl.d.ts +10 -1
  154. package/dist/renderer/shaders/fisheye.glsl.d.ts.map +1 -1
  155. package/dist/sri.json +8 -8
  156. package/dist/streaming/dash-adapter.d.ts +2 -2
  157. package/dist/streaming/dash-adapter.d.ts.map +1 -1
  158. package/dist/streaming/fairplay.d.ts +21 -0
  159. package/dist/streaming/fairplay.d.ts.map +1 -0
  160. package/dist/streaming/hls-adapter.d.ts +2 -2
  161. package/dist/streaming/hls-adapter.d.ts.map +1 -1
  162. package/dist/streaming/progressive-adapter.d.ts +2 -2
  163. package/dist/streaming/progressive-adapter.d.ts.map +1 -1
  164. package/dist/streaming/source-manager.d.ts +3 -1
  165. package/dist/streaming/source-manager.d.ts.map +1 -1
  166. package/dist/streaming/types.d.ts +9 -3
  167. package/dist/streaming/types.d.ts.map +1 -1
  168. package/dist/styles/controls.d.ts +1 -1
  169. package/dist/styles/controls.d.ts.map +1 -1
  170. package/dist/utils/static-html.d.ts +12 -0
  171. package/dist/utils/static-html.d.ts.map +1 -0
  172. package/dist/vizor.js +4 -4
  173. package/package.json +8 -5
  174. package/dist/__tests__/license-checker.test.d.ts.map +0 -1
  175. package/dist/__tests__/master-key.test.d.ts.map +0 -1
  176. package/dist/annotations-C0ugfCn4.js.map +0 -1
  177. package/dist/captions-BP0H2E03.js.map +0 -1
  178. package/dist/collab-BXPomrS0.js.map +0 -1
  179. package/dist/dash-adapter-tid9ZyhO.js.map +0 -1
  180. package/dist/hls-adapter-BivH46L_.js +0 -97
  181. package/dist/hls-adapter-BivH46L_.js.map +0 -1
  182. package/dist/register-CwnmiGEs.js.map +0 -1
  183. /package/dist/{__tests__ → license/__tests__}/license-checker.test.d.ts +0 -0
  184. /package/dist/{__tests__ → license/__tests__}/master-key.test.d.ts +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"collab-BXPomrS0.js","sources":["../src/controls/icons.ts","../src/collaborative/collab-client.ts","../src/collaborative/gaze-cursor-manager.ts","../src/collaborative/gaze-sender.ts","../src/collaborative/chat-panel.ts","../src/collaborative/chat-button.ts","../src/collaborative/voice-manager.ts","../src/collaborative/voice-button.ts"],"sourcesContent":["/**\n * SVG icon strings for controls.\n * Uses stroke-based icons with 1.5px weight for a refined, Vimeo-inspired look.\n * Play/pause remain filled for quick recognition.\n */\n\nexport const ICON_PLAY = `<svg viewBox=\"0 0 24 24\"><path d=\"M8 5v14l11-7z\"/></svg>`;\n\nexport const ICON_PAUSE = `<svg viewBox=\"0 0 24 24\"><path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\"/></svg>`;\n\nexport const ICON_VOLUME_HIGH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M19.07 4.93a10 10 0 0 1 0 14.14\"/><path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/></svg>`;\n\nexport const ICON_VOLUME_LOW = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"/></svg>`;\n\nexport const ICON_VOLUME_MUTED = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" fill=\"currentColor\" stroke=\"none\"/><line x1=\"23\" y1=\"9\" x2=\"17\" y2=\"15\"/><line x1=\"17\" y1=\"9\" x2=\"23\" y2=\"15\"/></svg>`;\n\nexport const ICON_FULLSCREEN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"15 3 21 3 21 9\"/><polyline points=\"9 21 3 21 3 15\"/><line x1=\"21\" y1=\"3\" x2=\"14\" y2=\"10\"/><line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\"/></svg>`;\n\nexport const ICON_FULLSCREEN_EXIT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 14 10 14 10 20\"/><polyline points=\"20 10 14 10 14 4\"/><line x1=\"14\" y1=\"10\" x2=\"21\" y2=\"3\"/><line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\"/></svg>`;\n\nexport const ICON_VR = `<svg viewBox=\"0 0 24 24\"><path d=\"M20.74 6H3.21C2.55 6 2 6.57 2 7.28v10.44c0 .7.55 1.28 1.23 1.28h4.79c.52 0 .96-.3 1.19-.77l1.58-3.15c.28-.58.84-.94 1.43-.94h1.56c.59 0 1.15.36 1.43.94l1.58 3.15c.23.47.67.77 1.19.77h4.79c.68 0 1.23-.57 1.23-1.28V7.28C22 6.57 21.43 6 20.74 6zM7.5 14.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5zm9 0c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z\"/></svg>`;\n\nexport const ICON_COMPASS = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polygon points=\"16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76\" fill=\"currentColor\"/></svg>`;\n\nexport const ICON_SETTINGS = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>`;\n\n// Playlist & Live icons (for future steps)\nexport const ICON_PLAYLIST_NEXT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"5 4 15 12 5 20 5 4\" fill=\"currentColor\"/><line x1=\"19\" y1=\"5\" x2=\"19\" y2=\"19\"/></svg>`;\n\nexport const ICON_PLAYLIST_PREV = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"19 20 9 12 19 4 19 20\" fill=\"currentColor\"/><line x1=\"5\" y1=\"19\" x2=\"5\" y2=\"5\"/></svg>`;\n\nexport const ICON_LIVE_DOT = `<svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"6\" fill=\"currentColor\"/></svg>`;\n\nexport const ICON_CC = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" ry=\"2\"/><path d=\"M7 10.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5\"/><path d=\"M7 13.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5\"/><path d=\"M14 10.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5\"/><path d=\"M14 13.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5\"/></svg>`;\n\nexport const ICON_CHAT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>`;\n\nexport const ICON_MIC = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/><line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/></svg>`;\n\nexport const ICON_MIC_MUTED = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/><path d=\"M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6\"/><path d=\"M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .64-.09 1.26-.26 1.85\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/><line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/></svg>`;\n","import type { PlayerState } from '../core/player-state.js';\nimport type { ClientMessage, ServerMessage, CollabParticipant } from '@vizor-vr/shared';\n\n/** Drift threshold in seconds — viewer only seeks if drift exceeds this */\nconst DRIFT_THRESHOLD = 1.0;\n\n/** Reconnect delay in milliseconds */\nconst RECONNECT_DELAY = 3000;\n\n/** Throttle interval for playback_state messages (ms) */\nconst PLAYBACK_THROTTLE = 250;\n\nexport interface CollabClientOptions {\n serverUrl: string;\n roomCode: string;\n role: 'host' | 'viewer';\n userId: string;\n displayName: string;\n state: PlayerState;\n /** The media element for play/pause/seek control */\n mediaElement: {\n play(): void | Promise<void>;\n pause(): void;\n seek(time: number): void;\n currentTime: number;\n };\n /** Optional callback when participants change */\n onParticipantsChange?: (users: CollabParticipant[]) => void;\n /** Callback when a peer's gaze position is received */\n onPeerGaze?: (userId: string, lat: number, lon: number) => void;\n /** Callback when a user joins the room */\n onUserJoined?: (user: CollabParticipant) => void;\n /** Callback when a user leaves the room */\n onUserLeft?: (userId: string) => void;\n /** Callback when a chat message is received */\n onChatMessage?: (userId: string, displayName: string, text: string, timestamp: number) => void;\n /** Callback when a voice signal (SDP/ICE) is received from a peer */\n onVoiceSignal?: (fromUserId: string, sdp?: string, candidate?: string) => void;\n /** Room password (host sets, viewers must match) */\n password?: string;\n}\n\n/**\n * Browser-side WebSocket client for collaborative viewing.\n *\n * - Host: subscribes to local state changes → sends playback_state/seek to server\n * - Viewer: receives sync messages → applies play/pause/seek locally\n * - Auto-reconnects on unexpected close\n */\nexport class CollaborativeClient {\n private ws: WebSocket | null = null;\n private options: CollabClientOptions;\n private unsubscribes: Array<() => void> = [];\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private disposed = false;\n private lastPlaybackSend = 0;\n private connected = false;\n\n constructor(options: CollabClientOptions) {\n this.options = options;\n }\n\n /** Connect to the collaborative server */\n connect(): void {\n if (this.disposed) return;\n\n try {\n this.ws = new WebSocket(this.options.serverUrl);\n } catch {\n this.scheduleReconnect();\n return;\n }\n\n this.ws.onopen = () => {\n this.connected = true;\n // Send join message\n this.send({\n type: 'join',\n roomCode: this.options.roomCode,\n userId: this.options.userId,\n displayName: this.options.displayName,\n password: this.options.password,\n });\n\n // If host, subscribe to state changes to broadcast\n if (this.options.role === 'host') {\n this.subscribeHostState();\n }\n };\n\n this.ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(event.data as string) as ServerMessage;\n this.handleMessage(msg);\n } catch {\n // Ignore invalid messages\n }\n };\n\n this.ws.onclose = () => {\n this.connected = false;\n this.cleanupSubscriptions();\n if (!this.disposed) {\n this.scheduleReconnect();\n }\n };\n\n this.ws.onerror = () => {\n // onclose will fire after onerror\n };\n }\n\n /** Whether the client is currently connected */\n isConnected(): boolean {\n return this.connected;\n }\n\n /** Gracefully disconnect and clean up */\n dispose(): void {\n this.disposed = true;\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n this.cleanupSubscriptions();\n\n if (this.ws) {\n // Send leave before closing\n if (this.ws.readyState === WebSocket.OPEN) {\n this.send({ type: 'leave' });\n }\n this.ws.close();\n this.ws = null;\n }\n\n this.connected = false;\n }\n\n // --- Private ---\n\n private handleMessage(msg: ServerMessage): void {\n switch (msg.type) {\n case 'room_state':\n this.handleRoomState(msg.users, msg.playing, msg.currentTime);\n break;\n\n case 'sync':\n this.handleSync(msg.playing, msg.currentTime);\n break;\n\n case 'user_joined':\n this.options.onUserJoined?.(msg.user);\n this.options.onParticipantsChange?.([]); // Signal change; full list comes with room_state\n break;\n\n case 'user_left':\n this.options.onUserLeft?.(msg.userId);\n this.options.onParticipantsChange?.([]); // Signal change\n break;\n\n case 'error':\n console.warn(`[Vizor Collab] Server error: ${msg.code} — ${msg.message}`);\n break;\n\n case 'peer_gaze':\n this.options.onPeerGaze?.(msg.userId, msg.lat, msg.lon);\n break;\n\n case 'chat_message':\n this.options.onChatMessage?.(msg.userId, msg.displayName, msg.text, msg.timestamp);\n break;\n\n case 'voice_signal':\n this.options.onVoiceSignal?.(msg.fromUserId, msg.sdp, msg.candidate);\n break;\n }\n }\n\n private handleRoomState(\n users: CollabParticipant[],\n playing: boolean,\n currentTime: number,\n ): void {\n this.options.onParticipantsChange?.(users);\n\n // Viewer: sync to room state on join\n if (this.options.role === 'viewer') {\n this.applySync(playing, currentTime);\n }\n }\n\n private handleSync(playing: boolean, currentTime: number): void {\n // Only viewers apply sync\n if (this.options.role !== 'viewer') return;\n this.applySync(playing, currentTime);\n }\n\n private applySync(playing: boolean, currentTime: number): void {\n const { state, mediaElement } = this.options;\n const localTime = mediaElement.currentTime;\n const drift = Math.abs(currentTime - localTime);\n\n // Seek if drift exceeds threshold\n if (drift > DRIFT_THRESHOLD) {\n mediaElement.seek(currentTime);\n }\n\n // Sync play/pause\n const localPlaying = state.get('playing');\n if (playing && !localPlaying) {\n Promise.resolve(mediaElement.play()).catch(() => {});\n } else if (!playing && localPlaying) {\n mediaElement.pause();\n }\n }\n\n /** Host: subscribe to local state changes and broadcast them */\n private subscribeHostState(): void {\n const { state } = this.options;\n\n // Subscribe to playing state changes (play/pause)\n const unsubPlaying = state.subscribe('playing', (playing) => {\n this.sendPlaybackState(playing, state.get('currentTime'));\n });\n this.unsubscribes.push(unsubPlaying);\n\n // Subscribe to seeking (user seeks)\n const unsubSeeking = state.subscribe('seeking', (seeking, prevSeeking) => {\n // Send seek when seeking ends (user released seek bar)\n if (!seeking && prevSeeking) {\n this.send({\n type: 'seek',\n currentTime: state.get('currentTime'),\n });\n }\n });\n this.unsubscribes.push(unsubSeeking);\n }\n\n /** Throttled playback state sender */\n private sendPlaybackState(playing: boolean, currentTime: number): void {\n const now = Date.now();\n if (now - this.lastPlaybackSend < PLAYBACK_THROTTLE) return;\n this.lastPlaybackSend = now;\n\n this.send({\n type: 'playback_state',\n playing,\n currentTime,\n timestamp: now,\n });\n }\n\n /** Send a chat message */\n sendChat(text: string): void {\n this.send({ type: 'chat', text });\n }\n\n /** Send a voice signal (SDP offer/answer or ICE candidate) to a specific peer */\n sendVoiceSignal(targetUserId: string, sdp?: string, candidate?: string): void {\n this.send({ type: 'voice_signal', targetUserId, sdp, candidate });\n }\n\n /** Send a message to the server */\n send(msg: ClientMessage): void {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private scheduleReconnect(): void {\n if (this.disposed || this.reconnectTimer) return;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, RECONNECT_DELAY);\n }\n\n /** Register a cleanup function to run on dispose */\n addCleanup(fn: () => void): void {\n this.unsubscribes.push(fn);\n }\n\n private cleanupSubscriptions(): void {\n for (const unsub of this.unsubscribes) unsub();\n this.unsubscribes = [];\n }\n}\n","import {\n Sprite,\n SpriteMaterial,\n CanvasTexture,\n LinearFilter,\n} from 'three';\nimport type { Scene } from 'three';\nimport { latLonToSpherical, COLLAB_GAZE_LERP_FACTOR } from '@vizor-vr/shared';\n\n/** Tracked state for a single peer's gaze cursor */\ninterface PeerCursor {\n sprite: Sprite;\n nameLabel: Sprite;\n targetLat: number;\n targetLon: number;\n currentLat: number;\n currentLon: number;\n displayName: string;\n color: string;\n}\n\n/** Color palette for peer cursors — assigned by join order */\nconst PEER_COLORS = [\n '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',\n '#F7DC6F', '#BB8FCE', '#85C1E9', '#F1948A', '#82E0AA',\n];\n\n/** Distance from center for cursor sprites (in front of sphere at 200) */\nconst CURSOR_DISTANCE = 195;\nconst CURSOR_SCALE = 6;\nconst LABEL_SCALE_X = 14;\nconst LABEL_SCALE_Y = 5;\nconst CURSOR_CANVAS_SIZE = 64;\nconst LABEL_CANVAS_WIDTH = 256;\nconst LABEL_CANVAS_HEIGHT = 64;\n\n/**\n * Manages 3D sprite cursors for all remote peers.\n * Each peer gets a colored dot on the sphere at their camera orientation.\n * Positions are smoothly interpolated each frame via lerp.\n */\nexport class GazeCursorManager {\n private scene: Scene;\n private cursors = new Map<string, PeerCursor>();\n private colorIndex = 0;\n\n constructor(scene: Scene) {\n this.scene = scene;\n }\n\n /** Add a cursor for a new peer */\n addPeer(userId: string, displayName: string): void {\n if (this.cursors.has(userId)) return;\n\n const color = PEER_COLORS[this.colorIndex % PEER_COLORS.length]!;\n this.colorIndex++;\n\n const sprite = this.createCursorSprite(color);\n const nameLabel = this.createLabelSprite(displayName, color);\n nameLabel.visible = true;\n\n this.scene.add(sprite);\n this.scene.add(nameLabel);\n\n this.cursors.set(userId, {\n sprite,\n nameLabel,\n targetLat: 0,\n targetLon: 0,\n currentLat: 0,\n currentLon: 0,\n displayName,\n color: color,\n });\n }\n\n /** Remove a peer's cursor */\n removePeer(userId: string): void {\n const cursor = this.cursors.get(userId);\n if (!cursor) return;\n\n this.scene.remove(cursor.sprite);\n this.scene.remove(cursor.nameLabel);\n cursor.sprite.material.map?.dispose();\n cursor.sprite.material.dispose();\n cursor.nameLabel.material.map?.dispose();\n cursor.nameLabel.material.dispose();\n this.cursors.delete(userId);\n }\n\n /** Update the target gaze position for a peer */\n updateGaze(userId: string, lat: number, lon: number): void {\n const cursor = this.cursors.get(userId);\n if (!cursor) return;\n cursor.targetLat = lat;\n cursor.targetLon = lon;\n }\n\n /** Called each animation frame. Lerps all cursors toward their target positions. */\n update(): void {\n for (const [, cursor] of this.cursors) {\n cursor.currentLat += (cursor.targetLat - cursor.currentLat) * COLLAB_GAZE_LERP_FACTOR;\n cursor.currentLon += (cursor.targetLon - cursor.currentLon) * COLLAB_GAZE_LERP_FACTOR;\n\n const { phi, theta } = latLonToSpherical(cursor.currentLat, cursor.currentLon);\n const x = CURSOR_DISTANCE * Math.sin(phi) * Math.cos(theta);\n const y = CURSOR_DISTANCE * Math.cos(phi);\n const z = CURSOR_DISTANCE * Math.sin(phi) * Math.sin(theta);\n\n cursor.sprite.position.set(x, y, z);\n\n // Name label sits above the cursor\n cursor.nameLabel.position.set(x, y + CURSOR_SCALE * 0.8, z);\n }\n }\n\n /** Get the number of tracked peers */\n getPeerCount(): number {\n return this.cursors.size;\n }\n\n /** Clean up all cursors */\n dispose(): void {\n for (const [userId] of this.cursors) {\n this.removePeer(userId);\n }\n }\n\n /** Create a colored circle sprite for the cursor dot */\n private createCursorSprite(color: string): Sprite {\n const canvas = document.createElement('canvas');\n canvas.width = CURSOR_CANVAS_SIZE;\n canvas.height = CURSOR_CANVAS_SIZE;\n const ctx = canvas.getContext('2d')!;\n\n const cx = CURSOR_CANVAS_SIZE / 2;\n const cy = CURSOR_CANVAS_SIZE / 2;\n const radius = CURSOR_CANVAS_SIZE / 2 - 4;\n\n // Colored circle\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.arc(cx, cy, radius, 0, Math.PI * 2);\n ctx.fill();\n\n // White ring border\n ctx.strokeStyle = '#ffffff';\n ctx.lineWidth = 3;\n ctx.stroke();\n\n const texture = new CanvasTexture(canvas);\n texture.minFilter = LinearFilter;\n texture.magFilter = LinearFilter;\n\n const material = new SpriteMaterial({\n map: texture,\n sizeAttenuation: true,\n depthTest: false,\n transparent: true,\n });\n\n const sprite = new Sprite(material);\n sprite.scale.set(CURSOR_SCALE, CURSOR_SCALE, 1);\n return sprite;\n }\n\n /** Create a text label sprite showing the peer's display name */\n private createLabelSprite(name: string, color: string): Sprite {\n const canvas = document.createElement('canvas');\n canvas.width = LABEL_CANVAS_WIDTH;\n canvas.height = LABEL_CANVAS_HEIGHT;\n const ctx = canvas.getContext('2d')!;\n\n // Semi-transparent background bar\n const padding = 10;\n ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';\n ctx.beginPath();\n ctx.roundRect(padding, padding, LABEL_CANVAS_WIDTH - padding * 2, LABEL_CANVAS_HEIGHT - padding * 2, 8);\n ctx.fill();\n\n // Name text\n ctx.fillStyle = color;\n ctx.font = 'bold 24px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(name, LABEL_CANVAS_WIDTH / 2, LABEL_CANVAS_HEIGHT / 2);\n\n const texture = new CanvasTexture(canvas);\n texture.minFilter = LinearFilter;\n texture.magFilter = LinearFilter;\n\n const material = new SpriteMaterial({\n map: texture,\n sizeAttenuation: true,\n depthTest: false,\n transparent: true,\n });\n\n const sprite = new Sprite(material);\n sprite.scale.set(LABEL_SCALE_X, LABEL_SCALE_Y, 1);\n return sprite;\n }\n}\n","import type { ClientMessage } from '@vizor-vr/shared';\nimport { COLLAB_GAZE_INTERVAL, COLLAB_GAZE_THRESHOLD } from '@vizor-vr/shared';\n\n/** Orientation provider callback (matches CameraController.getOrientation) */\nexport interface OrientationProvider {\n getOrientation(): { lat: number; lon: number; fov: number };\n}\n\n/**\n * Samples local camera orientation at ~5Hz and sends throttled `gaze` messages.\n * Only sends when orientation changes by more than COLLAB_GAZE_THRESHOLD degrees.\n */\nexport class GazeSender {\n private timer: ReturnType<typeof setInterval> | null = null;\n private lastSentLat = NaN;\n private lastSentLon = NaN;\n private orientationProvider: OrientationProvider;\n private sendFn: (msg: ClientMessage) => void;\n\n constructor(orientationProvider: OrientationProvider, sendFn: (msg: ClientMessage) => void) {\n this.orientationProvider = orientationProvider;\n this.sendFn = sendFn;\n }\n\n /** Start sampling and sending gaze messages */\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => this.sample(), COLLAB_GAZE_INTERVAL);\n }\n\n /** Stop sampling */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /** Clean up */\n dispose(): void {\n this.stop();\n }\n\n private sample(): void {\n const { lat, lon } = this.orientationProvider.getOrientation();\n const dLat = Math.abs(lat - this.lastSentLat);\n const dLon = Math.abs(lon - this.lastSentLon);\n\n if (dLat > COLLAB_GAZE_THRESHOLD || dLon > COLLAB_GAZE_THRESHOLD || isNaN(this.lastSentLat)) {\n this.lastSentLat = lat;\n this.lastSentLon = lon;\n this.sendFn({ type: 'gaze', lat, lon });\n }\n }\n}\n","/** A single chat message displayed in the panel */\nexport interface ChatMessage {\n userId: string;\n displayName: string;\n text: string;\n timestamp: number;\n isLocal: boolean;\n}\n\n/** Color palette matching gaze cursor colors */\nconst PEER_COLORS = [\n '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',\n '#F7DC6F', '#BB8FCE', '#85C1E9', '#F1948A', '#82E0AA',\n];\n\nconst CHAT_CSS = `\n.vz-chat-panel {\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 300px;\n max-width: 70%;\n background: rgba(15, 15, 25, 0.95);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n z-index: 25;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n.vz-chat-panel.open {\n transform: translateX(0);\n}\n.vz-chat-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n flex-shrink: 0;\n}\n.vz-chat-title {\n color: #fff;\n font-size: 14px;\n font-weight: 600;\n}\n.vz-chat-close {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n font-size: 18px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n}\n.vz-chat-close:hover {\n color: #fff;\n}\n.vz-chat-messages {\n flex: 1;\n overflow-y: auto;\n padding: 8px 12px;\n scrollbar-width: thin;\n scrollbar-color: rgba(255,255,255,0.2) transparent;\n}\n.vz-chat-messages::-webkit-scrollbar {\n width: 4px;\n}\n.vz-chat-messages::-webkit-scrollbar-thumb {\n background: rgba(255,255,255,0.2);\n border-radius: 2px;\n}\n.vz-chat-message {\n margin: 4px 0;\n padding: 4px 0;\n}\n.vz-chat-msg-header {\n display: flex;\n align-items: baseline;\n gap: 6px;\n}\n.vz-chat-msg-name {\n font-weight: 600;\n font-size: 12px;\n}\n.vz-chat-msg-time {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n}\n.vz-chat-msg-text {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.9);\n word-break: break-word;\n margin-top: 2px;\n line-height: 1.4;\n}\n.vz-chat-input-row {\n display: flex;\n gap: 6px;\n padding: 8px 12px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n flex-shrink: 0;\n}\n.vz-chat-input {\n flex: 1;\n height: 32px;\n border-radius: 6px;\n border: none;\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n padding: 0 10px;\n font-size: 13px;\n outline: none;\n}\n.vz-chat-input::placeholder {\n color: rgba(255, 255, 255, 0.3);\n}\n.vz-chat-input:focus {\n background: rgba(255, 255, 255, 0.15);\n}\n.vz-chat-send {\n width: 32px;\n height: 32px;\n border-radius: 6px;\n border: none;\n background: var(--vz-primary, #f43f5e);\n color: #fff;\n font-size: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.vz-chat-send:hover {\n opacity: 0.85;\n}\n.vz-chat-empty {\n color: rgba(255, 255, 255, 0.3);\n font-size: 13px;\n text-align: center;\n padding: 24px 16px;\n}\n`;\n\n/**\n * Side panel overlay for collaborative chat.\n * Slides in from the right with frosted glass styling.\n */\nexport class ChatPanel {\n private panel: HTMLDivElement;\n private messagesEl: HTMLDivElement;\n private inputEl: HTMLInputElement;\n private emptyEl: HTMLDivElement;\n private styleEl: HTMLStyleElement;\n private isOpen = false;\n private messages: ChatMessage[] = [];\n private colorMap = new Map<string, string>();\n private colorIndex = 0;\n private onSend: (text: string) => void;\n private shadowRoot: ShadowRoot;\n\n constructor(shadowRoot: ShadowRoot, onSend: (text: string) => void) {\n this.shadowRoot = shadowRoot;\n this.onSend = onSend;\n\n // Inject CSS\n this.styleEl = document.createElement('style');\n this.styleEl.textContent = CHAT_CSS;\n shadowRoot.appendChild(this.styleEl);\n\n // Build panel DOM\n this.panel = document.createElement('div');\n this.panel.className = 'vz-chat-panel';\n\n // Header\n const header = document.createElement('div');\n header.className = 'vz-chat-header';\n const title = document.createElement('span');\n title.className = 'vz-chat-title';\n title.textContent = 'Chat';\n const closeBtn = document.createElement('button');\n closeBtn.className = 'vz-chat-close';\n closeBtn.textContent = '\\u2715';\n closeBtn.addEventListener('click', () => this.close());\n header.appendChild(title);\n header.appendChild(closeBtn);\n this.panel.appendChild(header);\n\n // Messages area\n this.messagesEl = document.createElement('div');\n this.messagesEl.className = 'vz-chat-messages';\n this.emptyEl = document.createElement('div');\n this.emptyEl.className = 'vz-chat-empty';\n this.emptyEl.textContent = 'No messages yet';\n this.messagesEl.appendChild(this.emptyEl);\n this.panel.appendChild(this.messagesEl);\n\n // Input row\n const inputRow = document.createElement('div');\n inputRow.className = 'vz-chat-input-row';\n this.inputEl = document.createElement('input');\n this.inputEl.className = 'vz-chat-input';\n this.inputEl.type = 'text';\n this.inputEl.placeholder = 'Type a message...';\n this.inputEl.maxLength = 500;\n this.inputEl.addEventListener('keydown', (e) => {\n e.stopPropagation(); // Prevent keyboard shortcuts from firing\n if (e.key === 'Enter') {\n this.handleSend();\n }\n });\n const sendBtn = document.createElement('button');\n sendBtn.className = 'vz-chat-send';\n sendBtn.innerHTML = '\\u27A4'; // Right arrow\n sendBtn.addEventListener('click', () => this.handleSend());\n inputRow.appendChild(this.inputEl);\n inputRow.appendChild(sendBtn);\n this.panel.appendChild(inputRow);\n\n shadowRoot.appendChild(this.panel);\n }\n\n /** Add a chat message to the panel */\n addMessage(msg: ChatMessage): void {\n this.messages.push(msg);\n if (this.emptyEl.parentNode) {\n this.emptyEl.remove();\n }\n this.renderMessage(msg);\n this.scrollToBottom();\n }\n\n /** Toggle panel open/closed */\n toggle(): void {\n if (this.isOpen) {\n this.close();\n } else {\n this.open();\n }\n }\n\n /** Open the panel */\n open(): void {\n this.isOpen = true;\n this.panel.classList.add('open');\n this.inputEl.focus();\n }\n\n /** Close the panel */\n close(): void {\n this.isOpen = false;\n this.panel.classList.remove('open');\n }\n\n /** Whether the panel is currently visible */\n isVisible(): boolean {\n return this.isOpen;\n }\n\n /** Get message count */\n getMessageCount(): number {\n return this.messages.length;\n }\n\n /** Clean up */\n dispose(): void {\n this.panel.remove();\n this.styleEl.remove();\n }\n\n private handleSend(): void {\n const text = this.inputEl.value.trim();\n if (!text) return;\n this.inputEl.value = '';\n this.onSend(text);\n }\n\n private renderMessage(msg: ChatMessage): void {\n const el = document.createElement('div');\n el.className = 'vz-chat-message';\n\n const headerEl = document.createElement('div');\n headerEl.className = 'vz-chat-msg-header';\n\n const nameEl = document.createElement('span');\n nameEl.className = 'vz-chat-msg-name';\n nameEl.style.color = this.getColorForUser(msg.userId);\n nameEl.textContent = msg.isLocal ? 'You' : msg.displayName;\n\n const timeEl = document.createElement('span');\n timeEl.className = 'vz-chat-msg-time';\n timeEl.textContent = this.formatTime(msg.timestamp);\n\n headerEl.appendChild(nameEl);\n headerEl.appendChild(timeEl);\n\n const textEl = document.createElement('div');\n textEl.className = 'vz-chat-msg-text';\n textEl.textContent = msg.text; // textContent auto-escapes HTML\n\n el.appendChild(headerEl);\n el.appendChild(textEl);\n this.messagesEl.appendChild(el);\n }\n\n private scrollToBottom(): void {\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight;\n }\n\n private getColorForUser(userId: string): string {\n if (!this.colorMap.has(userId)) {\n this.colorMap.set(userId, PEER_COLORS[this.colorIndex % PEER_COLORS.length]!);\n this.colorIndex++;\n }\n return this.colorMap.get(userId)!;\n }\n\n private formatTime(ts: number): string {\n const d = new Date(ts);\n const h = d.getHours().toString().padStart(2, '0');\n const m = d.getMinutes().toString().padStart(2, '0');\n return `${h}:${m}`;\n }\n}\n","import { ICON_CHAT } from '../controls/icons.js';\nimport type { ChatPanel } from './chat-panel.js';\n\n/**\n * Chat toggle button with unread message badge.\n * Placed in the controls bar right section.\n */\nexport class ChatButton {\n private container: HTMLDivElement;\n private badge: HTMLSpanElement;\n private unreadCount = 0;\n private chatPanel: ChatPanel;\n\n constructor(chatPanel: ChatPanel) {\n this.chatPanel = chatPanel;\n\n this.container = document.createElement('div');\n this.container.style.position = 'relative';\n this.container.style.display = 'inline-flex';\n\n const btn = document.createElement('button');\n btn.className = 'vz-btn vz-chat-btn';\n btn.innerHTML = ICON_CHAT;\n btn.title = 'Chat (T)';\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.chatPanel.toggle();\n if (this.chatPanel.isVisible()) {\n this.resetUnread();\n }\n });\n\n this.badge = document.createElement('span');\n this.badge.className = 'vz-chat-badge';\n this.badge.style.display = 'none';\n\n this.container.appendChild(btn);\n this.container.appendChild(this.badge);\n }\n\n /** Increment unread count and show badge */\n incrementUnread(): void {\n this.unreadCount++;\n this.updateBadge();\n }\n\n /** Reset unread count and hide badge */\n resetUnread(): void {\n this.unreadCount = 0;\n this.updateBadge();\n }\n\n /** Get the DOM element to insert into controls */\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n /** Clean up */\n dispose(): void {\n this.container.remove();\n }\n\n private updateBadge(): void {\n if (this.unreadCount > 0) {\n this.badge.textContent = this.unreadCount > 99 ? '99+' : String(this.unreadCount);\n this.badge.style.display = 'flex';\n } else {\n this.badge.textContent = '';\n this.badge.style.display = 'none';\n }\n }\n}\n","/** STUN servers for ICE connectivity */\nconst STUN_SERVERS: RTCIceServer[] = [\n { urls: 'stun:stun.l.google.com:19302' },\n { urls: 'stun:stun1.l.google.com:19302' },\n];\n\n/** State of the voice manager */\nexport type VoiceState = 'inactive' | 'active' | 'muted';\n\n/** Per-peer connection info */\ninterface PeerConnection {\n pc: RTCPeerConnection;\n audioEl: HTMLAudioElement;\n}\n\n/**\n * WebRTC mesh voice manager.\n *\n * Handles microphone capture, peer-to-peer audio connections,\n * and SDP/ICE signaling relay. Mesh topology — each participant\n * connects directly to every other participant (suitable for <8 peers).\n */\nexport class VoiceManager {\n private localStream: MediaStream | null = null;\n private peers = new Map<string, PeerConnection>();\n private state: VoiceState = 'inactive';\n private sendSignal: (targetUserId: string, sdp?: string, candidate?: string) => void;\n\n constructor(\n sendSignal: (targetUserId: string, sdp?: string, candidate?: string) => void,\n ) {\n this.sendSignal = sendSignal;\n }\n\n /** Get current voice state */\n getState(): VoiceState {\n return this.state;\n }\n\n /** Start voice — request microphone and begin connections */\n async start(): Promise<void> {\n if (this.state !== 'inactive') return;\n\n try {\n this.localStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n this.state = 'active';\n } catch (err) {\n console.warn('[Vizor Voice] getUserMedia failed:', err);\n throw err;\n }\n }\n\n /** Toggle mute on/off */\n toggleMute(): void {\n if (!this.localStream) return;\n\n if (this.state === 'active') {\n this.localStream.getAudioTracks().forEach((t) => (t.enabled = false));\n this.state = 'muted';\n } else if (this.state === 'muted') {\n this.localStream.getAudioTracks().forEach((t) => (t.enabled = true));\n this.state = 'active';\n }\n }\n\n /** Whether voice is currently active (active or muted) */\n isActive(): boolean {\n return this.state !== 'inactive';\n }\n\n /** Whether mic is currently muted */\n isMuted(): boolean {\n return this.state === 'muted';\n }\n\n /**\n * Initiate a WebRTC connection to a peer (caller side).\n * Creates an offer and sends it via signaling.\n */\n async connectToPeer(peerId: string): Promise<void> {\n if (!this.localStream || this.peers.has(peerId)) return;\n\n const pc = this.createPeerConnection(peerId);\n\n // Add local audio tracks\n this.localStream.getTracks().forEach((track) => {\n pc.addTrack(track, this.localStream!);\n });\n\n // Create and send SDP offer\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n\n this.sendSignal(peerId, JSON.stringify(offer));\n }\n\n /**\n * Handle an incoming voice signal (SDP offer/answer or ICE candidate).\n */\n async handleSignal(fromUserId: string, sdp?: string, candidate?: string): Promise<void> {\n if (!this.localStream) return;\n\n if (sdp) {\n const desc = JSON.parse(sdp) as RTCSessionDescriptionInit;\n\n if (desc.type === 'offer') {\n // Incoming offer — create answer\n const pc = this.createPeerConnection(fromUserId);\n\n // Add local tracks\n this.localStream.getTracks().forEach((track) => {\n pc.addTrack(track, this.localStream!);\n });\n\n await pc.setRemoteDescription(new RTCSessionDescription(desc));\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n this.sendSignal(fromUserId, JSON.stringify(answer));\n } else if (desc.type === 'answer') {\n // Answer to our offer\n const peerConn = this.peers.get(fromUserId);\n if (peerConn) {\n await peerConn.pc.setRemoteDescription(new RTCSessionDescription(desc));\n }\n }\n } else if (candidate) {\n // ICE candidate\n const peerConn = this.peers.get(fromUserId);\n if (peerConn) {\n const iceCandidate = new RTCIceCandidate(JSON.parse(candidate));\n await peerConn.pc.addIceCandidate(iceCandidate);\n }\n }\n }\n\n /** Remove a peer connection (when peer leaves) */\n removePeer(peerId: string): void {\n const peerConn = this.peers.get(peerId);\n if (!peerConn) return;\n\n peerConn.pc.close();\n peerConn.audioEl.srcObject = null;\n peerConn.audioEl.remove();\n this.peers.delete(peerId);\n }\n\n /** Stop voice — release microphone and close all connections */\n stop(): void {\n // Close all peer connections\n this.peers.forEach((peerConn, peerId) => {\n peerConn.pc.close();\n peerConn.audioEl.srcObject = null;\n peerConn.audioEl.remove();\n });\n this.peers.clear();\n\n // Release local microphone\n if (this.localStream) {\n this.localStream.getTracks().forEach((t) => t.stop());\n this.localStream = null;\n }\n\n this.state = 'inactive';\n }\n\n /** Get the number of active peer connections */\n getPeerCount(): number {\n return this.peers.size;\n }\n\n /** Clean up everything */\n dispose(): void {\n this.stop();\n }\n\n // --- Private ---\n\n private createPeerConnection(peerId: string): RTCPeerConnection {\n // If a connection already exists, close it first\n if (this.peers.has(peerId)) {\n this.removePeer(peerId);\n }\n\n const pc = new RTCPeerConnection({ iceServers: STUN_SERVERS });\n\n // Create audio element for remote stream\n const audioEl = document.createElement('audio');\n audioEl.autoplay = true;\n\n // Handle incoming remote track\n pc.ontrack = (event) => {\n audioEl.srcObject = event.streams[0] ?? new MediaStream([event.track]);\n };\n\n // Relay ICE candidates to the peer\n pc.onicecandidate = (event) => {\n if (event.candidate) {\n this.sendSignal(peerId, undefined, JSON.stringify(event.candidate));\n }\n };\n\n // Clean up on connection failure\n pc.onconnectionstatechange = () => {\n if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') {\n this.removePeer(peerId);\n }\n };\n\n this.peers.set(peerId, { pc, audioEl });\n return pc;\n }\n}\n","import { ICON_MIC, ICON_MIC_MUTED } from '../controls/icons.js';\nimport type { VoiceManager } from './voice-manager.js';\n\n/**\n * Microphone toggle button for collaborative voice chat.\n *\n * First click starts voice (requests microphone).\n * Subsequent clicks toggle mute.\n * Shows mic icon (active) or mic-muted icon (muted).\n */\nexport class VoiceButton {\n private container: HTMLDivElement;\n private btn: HTMLButtonElement;\n private voiceManager: VoiceManager;\n private onVoiceStarted?: () => void;\n\n constructor(\n voiceManager: VoiceManager,\n onVoiceStarted?: () => void,\n ) {\n this.voiceManager = voiceManager;\n this.onVoiceStarted = onVoiceStarted;\n\n this.container = document.createElement('div');\n this.container.style.position = 'relative';\n this.container.style.display = 'inline-flex';\n\n this.btn = document.createElement('button');\n this.btn.className = 'vz-btn vz-voice-btn';\n this.btn.innerHTML = ICON_MIC;\n this.btn.title = 'Voice Chat (V)';\n this.btn.style.opacity = '0.5'; // Dimmed when inactive\n this.btn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.handleClick();\n });\n\n this.container.appendChild(this.btn);\n }\n\n /** Get the DOM element to insert into controls */\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n /** Update button icon based on current voice state */\n updateState(): void {\n const state = this.voiceManager.getState();\n switch (state) {\n case 'inactive':\n this.btn.innerHTML = ICON_MIC;\n this.btn.style.opacity = '0.5';\n this.btn.title = 'Start Voice Chat (V)';\n break;\n case 'active':\n this.btn.innerHTML = ICON_MIC;\n this.btn.style.opacity = '1';\n this.btn.title = 'Mute (V)';\n break;\n case 'muted':\n this.btn.innerHTML = ICON_MIC_MUTED;\n this.btn.style.opacity = '1';\n this.btn.title = 'Unmute (V)';\n break;\n }\n }\n\n /** Handle toggle via keyboard shortcut */\n toggle(): void {\n this.handleClick();\n }\n\n /** Clean up */\n dispose(): void {\n this.container.remove();\n }\n\n // --- Private ---\n\n private handleClick(): void {\n if (!this.voiceManager.isActive()) {\n // First click — start voice\n this.voiceManager.start().then(() => {\n this.updateState();\n this.onVoiceStarted?.();\n }).catch((err) => {\n console.warn('[Vizor Voice] Failed to start:', err);\n });\n } else {\n // Toggle mute\n this.voiceManager.toggleMute();\n this.updateState();\n }\n }\n}\n"],"names":["PEER_COLORS"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dash-adapter-tid9ZyhO.js","sources":["../src/streaming/dash-adapter.ts"],"sourcesContent":["import type { QualityLevel } from '@vizor-vr/shared';\nimport type { IStreamingAdapter } from './types.js';\n\n/**\n * DASH streaming adapter wrapping dashjs.\n * Lazy-loaded via dynamic import — only included in the bundle\n * when a DASH source is detected.\n */\nexport class DashAdapter implements IStreamingAdapter {\n private videoElement: HTMLVideoElement | null = null;\n private player: unknown | null = null;\n private _isAttached = false;\n private levels: QualityLevel[] = [];\n\n get supportsABR(): boolean {\n return true;\n }\n\n get isAttached(): boolean {\n return this._isAttached;\n }\n\n attach(videoElement: HTMLVideoElement): void {\n this.videoElement = videoElement;\n this._isAttached = true;\n }\n\n async load(url: string): Promise<void> {\n if (!this.videoElement) return;\n\n try {\n const dashjs = await import('dashjs');\n\n // Destroy previous instance\n if (this.player) {\n (this.player as { destroy(): void }).destroy();\n }\n\n const player = dashjs.MediaPlayer().create();\n player.initialize(this.videoElement, url, false);\n\n // Extract quality levels once the stream info is available\n player.on('streamInitialized' as string, () => {\n try {\n const bitrateList = player.getBitrateInfoListFor('video');\n this.levels = bitrateList.map(\n (info: { height: number; width: number; bitrate: number }, i: number) => ({\n label: info.height ? `${info.height}p` : `Level ${i}`,\n width: info.width,\n height: info.height,\n bitrate: info.bitrate,\n active: false,\n }),\n );\n } catch {\n // Stream may not have video quality info\n }\n });\n\n this.player = player;\n } catch (e) {\n console.error('[Vizor] Failed to load dashjs:', e);\n }\n }\n\n getQualityLevels(): QualityLevel[] {\n return this.levels;\n }\n\n setQualityLevel(index: number): void {\n if (!this.player) return;\n const player = this.player as {\n setQualityFor(type: string, index: number): void;\n updateSettings(settings: unknown): void;\n };\n\n if (index === -1) {\n // Auto ABR\n player.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: true } } },\n });\n } else {\n player.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: false } } },\n });\n player.setQualityFor('video', index);\n }\n }\n\n destroy(): void {\n if (this.player) {\n (this.player as { destroy(): void }).destroy();\n }\n this.player = null;\n this.videoElement = null;\n this._isAttached = false;\n this.levels = [];\n }\n}\n"],"names":[],"mappings":"AAQO,MAAM,YAAyC;AAAA,EAC5C,eAAwC;AAAA,EACxC,SAAyB;AAAA,EACzB,cAAc;AAAA,EACd,SAAyB,CAAA;AAAA,EAEjC,IAAI,cAAuB;AACzB,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,OAAO,cAAsC;AAC3C,SAAK,eAAe;AACpB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,KAAK,KAA4B;AACrC,QAAI,CAAC,KAAK,aAAc;AAExB,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,QAAQ;AAGpC,UAAI,KAAK,QAAQ;AACd,aAAK,OAA+B,QAAA;AAAA,MACvC;AAEA,YAAM,SAAS,OAAO,YAAA,EAAc,OAAA;AACpC,aAAO,WAAW,KAAK,cAAc,KAAK,KAAK;AAG/C,aAAO,GAAG,qBAA+B,MAAM;AAC7C,YAAI;AACF,gBAAM,cAAc,OAAO,sBAAsB,OAAO;AACxD,eAAK,SAAS,YAAY;AAAA,YACxB,CAAC,MAA0D,OAAe;AAAA,cACxE,OAAO,KAAK,SAAS,GAAG,KAAK,MAAM,MAAM,SAAS,CAAC;AAAA,cACnD,OAAO,KAAK;AAAA,cACZ,QAAQ,KAAK;AAAA,cACb,SAAS,KAAK;AAAA,cACd,QAAQ;AAAA,YAAA;AAAA,UACV;AAAA,QAEJ,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAED,WAAK,SAAS;AAAA,IAChB,SAAS,GAAG;AACV,cAAQ,MAAM,kCAAkC,CAAC;AAAA,IACnD;AAAA,EACF;AAAA,EAEA,mBAAmC;AACjC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,gBAAgB,OAAqB;AACnC,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,SAAS,KAAK;AAKpB,QAAI,UAAU,IAAI;AAEhB,aAAO,eAAe;AAAA,QACpB,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,OAAK,EAAE;AAAA,MAAE,CAC1D;AAAA,IACH,OAAO;AACL,aAAO,eAAe;AAAA,QACpB,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,QAAM,EAAE;AAAA,MAAE,CAC3D;AACD,aAAO,cAAc,SAAS,KAAK;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,QAAQ;AACd,WAAK,OAA+B,QAAA;AAAA,IACvC;AACA,SAAK,SAAS;AACd,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;"}
@@ -1,97 +0,0 @@
1
- class HlsAdapter {
2
- videoElement = null;
3
- hls = null;
4
- _isAttached = false;
5
- _isNative = false;
6
- levels = [];
7
- /** Enable low-latency mode for live streams */
8
- lowLatencyMode = false;
9
- get supportsABR() {
10
- return !this._isNative;
11
- }
12
- get isAttached() {
13
- return this._isAttached;
14
- }
15
- attach(videoElement) {
16
- this.videoElement = videoElement;
17
- this._isAttached = true;
18
- }
19
- async load(url) {
20
- if (!this.videoElement) return;
21
- if (this.videoElement.canPlayType("application/vnd.apple.mpegurl")) {
22
- this.videoElement.src = url;
23
- this._isNative = true;
24
- return;
25
- }
26
- try {
27
- const HlsModule = await import("hls.js");
28
- const Hls = HlsModule.default;
29
- if (!Hls.isSupported()) {
30
- console.error("[Vizor] HLS.js is not supported in this browser");
31
- return;
32
- }
33
- if (this.hls) {
34
- this.hls.destroy();
35
- }
36
- const hls = new Hls({
37
- enableWorker: true,
38
- lowLatencyMode: this.lowLatencyMode,
39
- ...this.lowLatencyMode ? { liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 6 } : {}
40
- });
41
- hls.attachMedia(this.videoElement);
42
- hls.loadSource(url);
43
- hls.on(Hls.Events.MANIFEST_PARSED, () => {
44
- this.levels = hls.levels.map(
45
- (level, i) => ({
46
- label: level.height ? `${level.height}p` : `Level ${i}`,
47
- width: level.width,
48
- height: level.height,
49
- bitrate: level.bitrate,
50
- active: i === hls.currentLevel
51
- })
52
- );
53
- });
54
- hls.on(Hls.Events.ERROR, (_event, data) => {
55
- if (data.fatal) {
56
- console.error("[Vizor] Fatal HLS error:", data.type, data.details);
57
- switch (data.type) {
58
- case Hls.ErrorTypes.NETWORK_ERROR:
59
- hls.startLoad();
60
- break;
61
- case Hls.ErrorTypes.MEDIA_ERROR:
62
- hls.recoverMediaError();
63
- break;
64
- default:
65
- this.destroy();
66
- break;
67
- }
68
- }
69
- });
70
- this.hls = hls;
71
- } catch (e) {
72
- console.error("[Vizor] Failed to load hls.js:", e);
73
- }
74
- }
75
- getQualityLevels() {
76
- return this.levels;
77
- }
78
- setQualityLevel(index) {
79
- if (!this.hls || this._isNative) return;
80
- const hls = this.hls;
81
- hls.currentLevel = index;
82
- }
83
- destroy() {
84
- if (this.hls && !this._isNative) {
85
- this.hls.destroy();
86
- }
87
- this.hls = null;
88
- this.videoElement = null;
89
- this._isAttached = false;
90
- this._isNative = false;
91
- this.levels = [];
92
- }
93
- }
94
- export {
95
- HlsAdapter
96
- };
97
- //# sourceMappingURL=hls-adapter-BivH46L_.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hls-adapter-BivH46L_.js","sources":["../src/streaming/hls-adapter.ts"],"sourcesContent":["import type { QualityLevel } from '@vizor-vr/shared';\nimport type { IStreamingAdapter } from './types.js';\n\n/**\n * HLS streaming adapter wrapping hls.js.\n * Lazy-loaded via dynamic import — only included in the bundle\n * when an HLS source is detected.\n *\n * Falls back to native HLS on Safari (no hls.js needed).\n */\nexport class HlsAdapter implements IStreamingAdapter {\n private videoElement: HTMLVideoElement | null = null;\n private hls: unknown | null = null;\n private _isAttached = false;\n private _isNative = false;\n private levels: QualityLevel[] = [];\n\n /** Enable low-latency mode for live streams */\n lowLatencyMode = false;\n\n get supportsABR(): boolean {\n return !this._isNative;\n }\n\n get isAttached(): boolean {\n return this._isAttached;\n }\n\n attach(videoElement: HTMLVideoElement): void {\n this.videoElement = videoElement;\n this._isAttached = true;\n }\n\n async load(url: string): Promise<void> {\n if (!this.videoElement) return;\n\n // Safari supports HLS natively — no need for hls.js\n if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) {\n this.videoElement.src = url;\n this._isNative = true;\n return;\n }\n\n try {\n const HlsModule = await import('hls.js');\n const Hls = HlsModule.default;\n\n if (!Hls.isSupported()) {\n console.error('[Vizor] HLS.js is not supported in this browser');\n return;\n }\n\n // Destroy previous instance\n if (this.hls) {\n (this.hls as { destroy(): void }).destroy();\n }\n\n const hls = new Hls({\n enableWorker: true,\n lowLatencyMode: this.lowLatencyMode,\n ...(this.lowLatencyMode\n ? { liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 6 }\n : {}),\n });\n\n hls.attachMedia(this.videoElement);\n hls.loadSource(url);\n\n hls.on(Hls.Events.MANIFEST_PARSED, () => {\n this.levels = hls.levels.map(\n (level: { height: number; width: number; bitrate: number }, i: number) => ({\n label: level.height ? `${level.height}p` : `Level ${i}`,\n width: level.width,\n height: level.height,\n bitrate: level.bitrate,\n active: i === hls.currentLevel,\n }),\n );\n });\n\n hls.on(Hls.Events.ERROR, (_event: string, data: { fatal: boolean; type: string; details: string }) => {\n if (data.fatal) {\n console.error('[Vizor] Fatal HLS error:', data.type, data.details);\n switch (data.type) {\n case Hls.ErrorTypes.NETWORK_ERROR:\n hls.startLoad();\n break;\n case Hls.ErrorTypes.MEDIA_ERROR:\n hls.recoverMediaError();\n break;\n default:\n this.destroy();\n break;\n }\n }\n });\n\n this.hls = hls;\n } catch (e) {\n console.error('[Vizor] Failed to load hls.js:', e);\n }\n }\n\n getQualityLevels(): QualityLevel[] {\n return this.levels;\n }\n\n setQualityLevel(index: number): void {\n if (!this.hls || this._isNative) return;\n const hls = this.hls as { currentLevel: number };\n hls.currentLevel = index; // -1 = auto\n }\n\n destroy(): void {\n if (this.hls && !this._isNative) {\n (this.hls as { destroy(): void }).destroy();\n }\n this.hls = null;\n this.videoElement = null;\n this._isAttached = false;\n this._isNative = false;\n this.levels = [];\n }\n}\n"],"names":[],"mappings":"AAUO,MAAM,WAAwC;AAAA,EAC3C,eAAwC;AAAA,EACxC,MAAsB;AAAA,EACtB,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,SAAyB,CAAA;AAAA;AAAA,EAGjC,iBAAiB;AAAA,EAEjB,IAAI,cAAuB;AACzB,WAAO,CAAC,KAAK;AAAA,EACf;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,OAAO,cAAsC;AAC3C,SAAK,eAAe;AACpB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,KAAK,KAA4B;AACrC,QAAI,CAAC,KAAK,aAAc;AAGxB,QAAI,KAAK,aAAa,YAAY,+BAA+B,GAAG;AAClE,WAAK,aAAa,MAAM;AACxB,WAAK,YAAY;AACjB;AAAA,IACF;AAEA,QAAI;AACF,YAAM,YAAY,MAAM,OAAO,QAAQ;AACvC,YAAM,MAAM,UAAU;AAEtB,UAAI,CAAC,IAAI,eAAe;AACtB,gBAAQ,MAAM,iDAAiD;AAC/D;AAAA,MACF;AAGA,UAAI,KAAK,KAAK;AACX,aAAK,IAA4B,QAAA;AAAA,MACpC;AAEA,YAAM,MAAM,IAAI,IAAI;AAAA,QAClB,cAAc;AAAA,QACd,gBAAgB,KAAK;AAAA,QACrB,GAAI,KAAK,iBACL,EAAE,uBAAuB,GAAG,6BAA6B,MACzD,CAAA;AAAA,MAAC,CACN;AAED,UAAI,YAAY,KAAK,YAAY;AACjC,UAAI,WAAW,GAAG;AAElB,UAAI,GAAG,IAAI,OAAO,iBAAiB,MAAM;AACvC,aAAK,SAAS,IAAI,OAAO;AAAA,UACvB,CAAC,OAA2D,OAAe;AAAA,YACzE,OAAO,MAAM,SAAS,GAAG,MAAM,MAAM,MAAM,SAAS,CAAC;AAAA,YACrD,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,YACd,SAAS,MAAM;AAAA,YACf,QAAQ,MAAM,IAAI;AAAA,UAAA;AAAA,QACpB;AAAA,MAEJ,CAAC;AAED,UAAI,GAAG,IAAI,OAAO,OAAO,CAAC,QAAgB,SAA4D;AACpG,YAAI,KAAK,OAAO;AACd,kBAAQ,MAAM,4BAA4B,KAAK,MAAM,KAAK,OAAO;AACjE,kBAAQ,KAAK,MAAA;AAAA,YACX,KAAK,IAAI,WAAW;AAClB,kBAAI,UAAA;AACJ;AAAA,YACF,KAAK,IAAI,WAAW;AAClB,kBAAI,kBAAA;AACJ;AAAA,YACF;AACE,mBAAK,QAAA;AACL;AAAA,UAAA;AAAA,QAEN;AAAA,MACF,CAAC;AAED,WAAK,MAAM;AAAA,IACb,SAAS,GAAG;AACV,cAAQ,MAAM,kCAAkC,CAAC;AAAA,IACnD;AAAA,EACF;AAAA,EAEA,mBAAmC;AACjC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,gBAAgB,OAAqB;AACnC,QAAI,CAAC,KAAK,OAAO,KAAK,UAAW;AACjC,UAAM,MAAM,KAAK;AACjB,QAAI,eAAe;AAAA,EACrB;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,OAAO,CAAC,KAAK,WAAW;AAC9B,WAAK,IAA4B,QAAA;AAAA,IACpC;AACA,SAAK,MAAM;AACX,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,YAAY;AACjB,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;"}