ts-video-player 0.1.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 (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +438 -0
  3. package/dist/cdn.d.ts +10 -0
  4. package/dist/cdn.d.ts.map +1 -0
  5. package/dist/cdn.min.js +1046 -0
  6. package/dist/cdn.min.js.map +54 -0
  7. package/dist/cdn.test.d.ts +2 -0
  8. package/dist/cdn.test.d.ts.map +1 -0
  9. package/dist/chunk-36x4yhjn.js +765 -0
  10. package/dist/chunk-36x4yhjn.js.map +12 -0
  11. package/dist/chunk-eajts2jv.js +4243 -0
  12. package/dist/chunk-eajts2jv.js.map +42 -0
  13. package/dist/chunk-z1ehbz9f.js +380 -0
  14. package/dist/chunk-z1ehbz9f.js.map +19 -0
  15. package/dist/config.d.ts +9 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/core/audio-gain.d.ts +67 -0
  18. package/dist/core/audio-gain.d.ts.map +1 -0
  19. package/dist/core/caption-styles.d.ts +121 -0
  20. package/dist/core/caption-styles.d.ts.map +1 -0
  21. package/dist/core/chapters.d.ts +150 -0
  22. package/dist/core/chapters.d.ts.map +1 -0
  23. package/dist/core/error-overlay.d.ts +71 -0
  24. package/dist/core/error-overlay.d.ts.map +1 -0
  25. package/dist/core/events.d.ts +119 -0
  26. package/dist/core/events.d.ts.map +1 -0
  27. package/dist/core/features.d.ts +64 -0
  28. package/dist/core/features.d.ts.map +1 -0
  29. package/dist/core/i18n.d.ts +132 -0
  30. package/dist/core/i18n.d.ts.map +1 -0
  31. package/dist/core/index.d.ts +37 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/live.d.ts +130 -0
  34. package/dist/core/live.d.ts.map +1 -0
  35. package/dist/core/media-session.d.ts +38 -0
  36. package/dist/core/media-session.d.ts.map +1 -0
  37. package/dist/core/orientation.d.ts +22 -0
  38. package/dist/core/orientation.d.ts.map +1 -0
  39. package/dist/core/popover.d.ts +42 -0
  40. package/dist/core/popover.d.ts.map +1 -0
  41. package/dist/core/remote-playback.d.ts +195 -0
  42. package/dist/core/remote-playback.d.ts.map +1 -0
  43. package/dist/core/settings-menu.d.ts +132 -0
  44. package/dist/core/settings-menu.d.ts.map +1 -0
  45. package/dist/core/spinner.d.ts +67 -0
  46. package/dist/core/spinner.d.ts.map +1 -0
  47. package/dist/core/state.d.ts +142 -0
  48. package/dist/core/state.d.ts.map +1 -0
  49. package/dist/core/text-tracks.d.ts +162 -0
  50. package/dist/core/text-tracks.d.ts.map +1 -0
  51. package/dist/core/themes.d.ts +213 -0
  52. package/dist/core/themes.d.ts.map +1 -0
  53. package/dist/core/thumbnails.d.ts +124 -0
  54. package/dist/core/thumbnails.d.ts.map +1 -0
  55. package/dist/core/tooltips.d.ts +100 -0
  56. package/dist/core/tooltips.d.ts.map +1 -0
  57. package/dist/elements/base.d.ts +3 -0
  58. package/dist/elements/base.d.ts.map +1 -0
  59. package/dist/elements/index.d.ts +33 -0
  60. package/dist/elements/index.d.ts.map +1 -0
  61. package/dist/elements/index.js +55 -0
  62. package/dist/elements/index.js.map +9 -0
  63. package/dist/elements/media-airplay-button.d.ts +13 -0
  64. package/dist/elements/media-airplay-button.d.ts.map +1 -0
  65. package/dist/elements/media-error-display.d.ts +13 -0
  66. package/dist/elements/media-error-display.d.ts.map +1 -0
  67. package/dist/elements/media-fullscreen-button.d.ts +13 -0
  68. package/dist/elements/media-fullscreen-button.d.ts.map +1 -0
  69. package/dist/elements/media-live-button.d.ts +13 -0
  70. package/dist/elements/media-live-button.d.ts.map +1 -0
  71. package/dist/elements/media-mute-button.d.ts +13 -0
  72. package/dist/elements/media-mute-button.d.ts.map +1 -0
  73. package/dist/elements/media-pip-button.d.ts +13 -0
  74. package/dist/elements/media-pip-button.d.ts.map +1 -0
  75. package/dist/elements/media-play-button.d.ts +14 -0
  76. package/dist/elements/media-play-button.d.ts.map +1 -0
  77. package/dist/elements/media-playback-rate-button.d.ts +10 -0
  78. package/dist/elements/media-playback-rate-button.d.ts.map +1 -0
  79. package/dist/elements/media-preview-time.d.ts +18 -0
  80. package/dist/elements/media-preview-time.d.ts.map +1 -0
  81. package/dist/elements/media-progress-bar.d.ts +16 -0
  82. package/dist/elements/media-progress-bar.d.ts.map +1 -0
  83. package/dist/elements/media-remote-playback-button.d.ts +16 -0
  84. package/dist/elements/media-remote-playback-button.d.ts.map +1 -0
  85. package/dist/elements/media-seek-button.d.ts +9 -0
  86. package/dist/elements/media-seek-button.d.ts.map +1 -0
  87. package/dist/elements/media-settings-menu.d.ts +32 -0
  88. package/dist/elements/media-settings-menu.d.ts.map +1 -0
  89. package/dist/elements/media-time-display.d.ts +21 -0
  90. package/dist/elements/media-time-display.d.ts.map +1 -0
  91. package/dist/elements/media-time-group.d.ts +22 -0
  92. package/dist/elements/media-time-group.d.ts.map +1 -0
  93. package/dist/elements/media-time-separator.d.ts +13 -0
  94. package/dist/elements/media-time-separator.d.ts.map +1 -0
  95. package/dist/elements/media-volume-slider.d.ts +16 -0
  96. package/dist/elements/media-volume-slider.d.ts.map +1 -0
  97. package/dist/elements/utils.d.ts +27 -0
  98. package/dist/elements/utils.d.ts.map +1 -0
  99. package/dist/elements/video-player.d.ts +23 -0
  100. package/dist/elements/video-player.d.ts.map +1 -0
  101. package/dist/elements/video-skin.d.ts +23 -0
  102. package/dist/elements/video-skin.d.ts.map +1 -0
  103. package/dist/features/captions.d.ts +8 -0
  104. package/dist/features/captions.d.ts.map +1 -0
  105. package/dist/features/create-player.d.ts +45 -0
  106. package/dist/features/create-player.d.ts.map +1 -0
  107. package/dist/features/fullscreen.d.ts +8 -0
  108. package/dist/features/fullscreen.d.ts.map +1 -0
  109. package/dist/features/gestures.d.ts +8 -0
  110. package/dist/features/gestures.d.ts.map +1 -0
  111. package/dist/features/index.d.ts +26 -0
  112. package/dist/features/index.d.ts.map +1 -0
  113. package/dist/features/index.js +31 -0
  114. package/dist/features/index.js.map +9 -0
  115. package/dist/features/keyboard.d.ts +8 -0
  116. package/dist/features/keyboard.d.ts.map +1 -0
  117. package/dist/features/pip.d.ts +8 -0
  118. package/dist/features/pip.d.ts.map +1 -0
  119. package/dist/features/playback.d.ts +8 -0
  120. package/dist/features/playback.d.ts.map +1 -0
  121. package/dist/features/quality.d.ts +8 -0
  122. package/dist/features/quality.d.ts.map +1 -0
  123. package/dist/features/types.d.ts +26 -0
  124. package/dist/features/types.d.ts.map +1 -0
  125. package/dist/features/volume.d.ts +8 -0
  126. package/dist/features/volume.d.ts.map +1 -0
  127. package/dist/index.d.ts +84 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +8149 -0
  130. package/dist/index.js.map +31 -0
  131. package/dist/layouts/index.d.ts +58 -0
  132. package/dist/layouts/index.d.ts.map +1 -0
  133. package/dist/player.d.ts +90 -0
  134. package/dist/player.d.ts.map +1 -0
  135. package/dist/plugins/ads.d.ts +311 -0
  136. package/dist/plugins/ads.d.ts.map +1 -0
  137. package/dist/plugins/analytics.d.ts +203 -0
  138. package/dist/plugins/analytics.d.ts.map +1 -0
  139. package/dist/plugins/end-screen.d.ts +184 -0
  140. package/dist/plugins/end-screen.d.ts.map +1 -0
  141. package/dist/plugins/index.d.ts +13 -0
  142. package/dist/plugins/index.d.ts.map +1 -0
  143. package/dist/plugins/index.js +20 -0
  144. package/dist/plugins/index.js.map +9 -0
  145. package/dist/plugins/skip-segments.d.ts +159 -0
  146. package/dist/plugins/skip-segments.d.ts.map +1 -0
  147. package/dist/plugins/watermark.d.ts +157 -0
  148. package/dist/plugins/watermark.d.ts.map +1 -0
  149. package/dist/providers/base.d.ts +56 -0
  150. package/dist/providers/base.d.ts.map +1 -0
  151. package/dist/providers/dash.d.ts +94 -0
  152. package/dist/providers/dash.d.ts.map +1 -0
  153. package/dist/providers/hls.d.ts +182 -0
  154. package/dist/providers/hls.d.ts.map +1 -0
  155. package/dist/providers/html5.d.ts +64 -0
  156. package/dist/providers/html5.d.ts.map +1 -0
  157. package/dist/providers/index.d.ts +41 -0
  158. package/dist/providers/index.d.ts.map +1 -0
  159. package/dist/providers/vimeo.d.ts +157 -0
  160. package/dist/providers/vimeo.d.ts.map +1 -0
  161. package/dist/providers/youtube.d.ts +134 -0
  162. package/dist/providers/youtube.d.ts.map +1 -0
  163. package/dist/stx/index.d.ts +59 -0
  164. package/dist/stx/index.d.ts.map +1 -0
  165. package/dist/stx/index.js +480 -0
  166. package/dist/stx/index.js.map +11 -0
  167. package/dist/types.d.ts +724 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/ui/components.d.ts +121 -0
  170. package/dist/ui/components.d.ts.map +1 -0
  171. package/dist/ui/index.d.ts +9 -0
  172. package/dist/ui/index.d.ts.map +1 -0
  173. package/dist/ui/index.js +16 -0
  174. package/dist/ui/index.js.map +9 -0
  175. package/package.json +109 -0
  176. package/src/cdn.test.ts +10 -0
  177. package/src/cdn.ts +60 -0
  178. package/src/config.ts +25 -0
  179. package/src/core/audio-gain.ts +171 -0
  180. package/src/core/caption-styles.ts +568 -0
  181. package/src/core/chapters.ts +551 -0
  182. package/src/core/error-overlay.ts +354 -0
  183. package/src/core/events.ts +552 -0
  184. package/src/core/features.ts +287 -0
  185. package/src/core/i18n.ts +590 -0
  186. package/src/core/index.ts +249 -0
  187. package/src/core/live.ts +472 -0
  188. package/src/core/media-session.ts +137 -0
  189. package/src/core/orientation.ts +57 -0
  190. package/src/core/popover.ts +143 -0
  191. package/src/core/remote-playback.ts +661 -0
  192. package/src/core/settings-menu.ts +630 -0
  193. package/src/core/spinner.ts +310 -0
  194. package/src/core/state.ts +476 -0
  195. package/src/core/text-tracks.ts +599 -0
  196. package/src/core/themes.ts +607 -0
  197. package/src/core/thumbnails.ts +457 -0
  198. package/src/core/tooltips.ts +370 -0
  199. package/src/elements/base.ts +2 -0
  200. package/src/elements/index.ts +82 -0
  201. package/src/elements/media-airplay-button.ts +78 -0
  202. package/src/elements/media-error-display.ts +47 -0
  203. package/src/elements/media-fullscreen-button.ts +72 -0
  204. package/src/elements/media-live-button.ts +65 -0
  205. package/src/elements/media-mute-button.ts +69 -0
  206. package/src/elements/media-pip-button.ts +66 -0
  207. package/src/elements/media-play-button.ts +69 -0
  208. package/src/elements/media-playback-rate-button.ts +59 -0
  209. package/src/elements/media-preview-time.ts +75 -0
  210. package/src/elements/media-progress-bar.ts +200 -0
  211. package/src/elements/media-remote-playback-button.ts +108 -0
  212. package/src/elements/media-seek-button.ts +50 -0
  213. package/src/elements/media-settings-menu.ts +302 -0
  214. package/src/elements/media-time-display.ts +99 -0
  215. package/src/elements/media-time-group.ts +41 -0
  216. package/src/elements/media-time-separator.ts +35 -0
  217. package/src/elements/media-volume-slider.ts +140 -0
  218. package/src/elements/utils.ts +82 -0
  219. package/src/elements/video-player.ts +175 -0
  220. package/src/elements/video-skin.ts +127 -0
  221. package/src/features/captions.ts +56 -0
  222. package/src/features/create-player.ts +91 -0
  223. package/src/features/fullscreen.ts +44 -0
  224. package/src/features/gestures.ts +44 -0
  225. package/src/features/index.ts +44 -0
  226. package/src/features/keyboard.ts +89 -0
  227. package/src/features/pip.ts +47 -0
  228. package/src/features/playback.ts +68 -0
  229. package/src/features/quality.ts +19 -0
  230. package/src/features/types.ts +28 -0
  231. package/src/features/volume.ts +46 -0
  232. package/src/index.ts +635 -0
  233. package/src/layouts/index.ts +672 -0
  234. package/src/player.ts +821 -0
  235. package/src/plugins/ads.ts +1045 -0
  236. package/src/plugins/analytics.ts +673 -0
  237. package/src/plugins/end-screen.ts +793 -0
  238. package/src/plugins/index.ts +61 -0
  239. package/src/plugins/skip-segments.ts +537 -0
  240. package/src/plugins/watermark.ts +537 -0
  241. package/src/providers/base.ts +197 -0
  242. package/src/providers/dash.ts +588 -0
  243. package/src/providers/hls.ts +624 -0
  244. package/src/providers/html5.ts +473 -0
  245. package/src/providers/index.ts +97 -0
  246. package/src/providers/vimeo.ts +533 -0
  247. package/src/providers/youtube.ts +542 -0
  248. package/src/stx/index.ts +606 -0
  249. package/src/types.ts +874 -0
  250. package/src/ui/components.ts +910 -0
  251. package/src/ui/index.ts +29 -0
@@ -0,0 +1,588 @@
1
+ /**
2
+ * DASH Provider
3
+ *
4
+ * Media provider for MPEG-DASH streaming using dash.js.
5
+ *
6
+ * @module providers/dash
7
+ */
8
+
9
+ import { BaseProvider } from './base'
10
+ import type {
11
+ Src,
12
+ ProviderLoader,
13
+ VideoQuality,
14
+ AudioTrack,
15
+ TextTrack,
16
+ PlayerOptions,
17
+ DRMConfig,
18
+ } from '../types'
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ export interface DASHSource {
25
+ src: string
26
+ type: 'dash'
27
+ /** DRM configuration */
28
+ drm?: DRMConfig
29
+ }
30
+
31
+ export interface DASHProviderConfig {
32
+ /** Custom dash.js settings */
33
+ dashConfig?: Record<string, any>
34
+ /** Enable low latency mode */
35
+ lowLatency?: boolean
36
+ /** Retry parameters */
37
+ retry?: {
38
+ maxAttempts: number
39
+ delay: number
40
+ }
41
+ }
42
+
43
+ // =============================================================================
44
+ // DASH.js Loading
45
+ // =============================================================================
46
+
47
+ let dashjs: any = null
48
+ let dashLoading: Promise<any> | null = null
49
+
50
+ const DASH_CDN_DEFAULT = 'https://cdn.dashjs.org/latest/dash.all.min.js'
51
+
52
+ /**
53
+ * Load dash.js from CDN
54
+ */
55
+ async function loadDashJS(cdnUrl?: string): Promise<any> {
56
+ if (dashjs) return dashjs
57
+
58
+ if (dashLoading) return dashLoading
59
+
60
+ dashLoading = new Promise((resolve, reject) => {
61
+ // Check if already loaded globally
62
+ if (typeof window !== 'undefined' && (window as any).dashjs) {
63
+ dashjs = (window as any).dashjs
64
+ resolve(dashjs)
65
+ return
66
+ }
67
+
68
+ const script = document.createElement('script')
69
+ script.src = cdnUrl || DASH_CDN_DEFAULT
70
+ script.async = true
71
+
72
+ script.onload = () => {
73
+ dashjs = (window as any).dashjs
74
+ resolve(dashjs)
75
+ }
76
+
77
+ script.onerror = () => {
78
+ reject(new Error('Failed to load dash.js'))
79
+ }
80
+
81
+ document.head.appendChild(script)
82
+ })
83
+
84
+ return dashLoading
85
+ }
86
+
87
+ // =============================================================================
88
+ // Source Detection
89
+ // =============================================================================
90
+
91
+ /**
92
+ * Check if source is a DASH manifest
93
+ */
94
+ export function isDASHSource(src: Src): src is DASHSource | string {
95
+ if (typeof src === 'object' && 'type' in src && src.type === 'dash') {
96
+ return true
97
+ }
98
+
99
+ if (typeof src === 'string') {
100
+ return src.includes('.mpd') || src.includes('manifest(format=mpd')
101
+ }
102
+
103
+ return false
104
+ }
105
+
106
+ // =============================================================================
107
+ // DASH Provider
108
+ // =============================================================================
109
+
110
+ export class DASHProvider extends BaseProvider {
111
+ readonly name = 'dash'
112
+ readonly type = 'dash' as const
113
+
114
+ private dash: any = null
115
+ private video: HTMLVideoElement | null = null
116
+ private source: DASHSource | null = null
117
+ private config: DASHProviderConfig = {}
118
+ private _volume = 1
119
+ private _muted = false
120
+ private _playbackRate = 1
121
+
122
+ get mediaElement(): HTMLVideoElement | null {
123
+ return this.video
124
+ }
125
+
126
+ /**
127
+ * Initialize the provider
128
+ */
129
+ protected async initialize(): Promise<void> {
130
+ // Create video element
131
+ this.video = document.createElement('video')
132
+ this.video.style.width = '100%'
133
+ this.video.style.height = '100%'
134
+ this.video.style.display = 'block'
135
+
136
+ // Load dash.js
137
+ await loadDashJS(this.options.dashCdnUrl)
138
+
139
+ // Create player instance
140
+ this.dash = dashjs.MediaPlayer().create()
141
+
142
+ // Apply custom config from options
143
+ if (this.options.dashConfig) {
144
+ this.config.dashConfig = this.options.dashConfig as Record<string, any>
145
+ }
146
+
147
+ if (this.config.dashConfig) {
148
+ Object.entries(this.config.dashConfig).forEach(([key, value]) => {
149
+ this.dash.updateSettings({ [key]: value })
150
+ })
151
+ }
152
+
153
+ // Enable low latency mode
154
+ if (this.config.lowLatency) {
155
+ this.dash.updateSettings({
156
+ streaming: {
157
+ lowLatencyEnabled: true,
158
+ liveDelay: 3,
159
+ liveCatchUpMinDrift: 0.05,
160
+ liveCatchUpPlaybackRate: 0.5,
161
+ },
162
+ })
163
+ }
164
+
165
+ // Attach dash.js events
166
+ this.attachDashEvents()
167
+
168
+ // Append to container
169
+ if (this.container) {
170
+ this.container.appendChild(this.video)
171
+ }
172
+
173
+ // Add video element events
174
+ this.attachVideoEvents()
175
+ }
176
+
177
+ /**
178
+ * Attach video element event listeners
179
+ */
180
+ private attachVideoEvents(): void {
181
+ if (!this.video) return
182
+
183
+ this.video.addEventListener('play', () => this.events.emit('play'))
184
+ this.video.addEventListener('pause', () => this.events.emit('pause'))
185
+ this.video.addEventListener('ended', () => this.events.emit('ended'))
186
+ this.video.addEventListener('timeupdate', () => this.events.emit('timeupdate', this.getCurrentTime()))
187
+ this.video.addEventListener('durationchange', () => this.events.emit('durationchange', this.getDuration()))
188
+ this.video.addEventListener('volumechange', () => {
189
+ this.events.emit('volumechange', this.getVolume(), this.getMuted())
190
+ })
191
+ this.video.addEventListener('waiting', () => this.events.emit('waiting'))
192
+ this.video.addEventListener('playing', () => this.events.emit('playing'))
193
+ this.video.addEventListener('seeking', () => this.events.emit('seeking'))
194
+ this.video.addEventListener('seeked', () => this.events.emit('seeked'))
195
+ this.video.addEventListener('progress', () => {
196
+ const ranges: Array<{ start: number, end: number }> = []
197
+ for (let i = 0; i < this.video!.buffered.length; i++) {
198
+ ranges.push({
199
+ start: this.video!.buffered.start(i),
200
+ end: this.video!.buffered.end(i),
201
+ })
202
+ }
203
+ this.events.emit('progress', ranges)
204
+ })
205
+ this.video.addEventListener('loadedmetadata', () => {
206
+ this.events.emit('loadedmetadata')
207
+ const fullscreenAvailability = this.getFeatureAvailability('fullscreen')
208
+ const pipAvailability = this.getFeatureAvailability('pip')
209
+ const volumeAvailability = this.getFeatureAvailability('volume')
210
+ this.events.emit('statechange', {
211
+ videoWidth: this.video!.videoWidth,
212
+ videoHeight: this.video!.videoHeight,
213
+ aspectRatio: (this.video!.videoHeight > 0 ? this.video!.videoWidth / this.video!.videoHeight : 0) || 16 / 9,
214
+ canFullscreen: fullscreenAvailability === 'available',
215
+ canPictureInPicture: pipAvailability === 'available',
216
+ fullscreenAvailability,
217
+ pipAvailability,
218
+ volumeAvailability,
219
+ })
220
+ this.events.emit('availabilitychange', 'fullscreen', fullscreenAvailability)
221
+ this.events.emit('availabilitychange', 'pip', pipAvailability)
222
+ this.events.emit('availabilitychange', 'volume', volumeAvailability)
223
+ })
224
+ this.video.addEventListener('loadeddata', () => this.events.emit('loadeddata'))
225
+ this.video.addEventListener('canplay', () => this.events.emit('canplay'))
226
+ this.video.addEventListener('canplaythrough', () => this.events.emit('canplaythrough'))
227
+ this.video.addEventListener('ratechange', () => this.events.emit('ratechange', this.getPlaybackRate()))
228
+ }
229
+
230
+ /**
231
+ * Attach dash.js event listeners
232
+ */
233
+ private attachDashEvents(): void {
234
+ if (!dashjs) return
235
+
236
+ const MediaPlayer = dashjs.MediaPlayer
237
+
238
+ // Quality change
239
+ this.dash.on(MediaPlayer.events.QUALITY_CHANGE_RENDERED, () => {
240
+ this.events.emit('qualitychange', this.getQualities().find(q => q.selected) || null)
241
+ })
242
+
243
+ // Manifest loaded
244
+ this.dash.on(MediaPlayer.events.MANIFEST_LOADED, () => {
245
+ // Qualities and tracks are now available
246
+ })
247
+
248
+ // Error handling
249
+ this.dash.on(MediaPlayer.events.ERROR, (event: any) => {
250
+ this.emitError(
251
+ event.error?.code || 0,
252
+ event.error?.message || 'DASH playback error',
253
+ event.error
254
+ )
255
+ })
256
+ }
257
+
258
+ /**
259
+ * Configure DRM protection
260
+ */
261
+ private configureDRM(drm: DRMConfig): void {
262
+ const protectionData: Record<string, any> = {}
263
+
264
+ if (drm.widevine) {
265
+ protectionData['com.widevine.alpha'] = {
266
+ serverURL: drm.widevine.url,
267
+ httpRequestHeaders: drm.widevine.headers,
268
+ }
269
+ }
270
+
271
+ if (drm.playready) {
272
+ protectionData['com.microsoft.playready'] = {
273
+ serverURL: drm.playready.url,
274
+ httpRequestHeaders: drm.playready.headers,
275
+ }
276
+ }
277
+
278
+ if (drm.fairplay) {
279
+ protectionData['com.apple.fps.1_0'] = {
280
+ serverURL: drm.fairplay.url,
281
+ serverCertificateURL: drm.fairplay.certificateUrl,
282
+ httpRequestHeaders: drm.fairplay.headers,
283
+ }
284
+ }
285
+
286
+ this.dash.setProtectionData(protectionData)
287
+ }
288
+
289
+ /**
290
+ * Load a source
291
+ */
292
+ async load(src: Src): Promise<void> {
293
+ if (!this.video || !this.dash) return
294
+
295
+ // Parse source
296
+ if (typeof src === 'string') {
297
+ this.source = { src, type: 'dash' }
298
+ }
299
+ else if (isDASHSource(src)) {
300
+ this.source = src as DASHSource
301
+ }
302
+ else {
303
+ this.emitError(4, 'Invalid DASH source')
304
+ return
305
+ }
306
+
307
+ // Configure DRM if provided
308
+ if (this.source.drm) {
309
+ this.configureDRM(this.source.drm)
310
+ }
311
+
312
+ // Initialize with video element and source
313
+ this.dash.initialize(this.video, this.source.src, false)
314
+ }
315
+
316
+ /**
317
+ * Check if source can be played
318
+ */
319
+ canPlay(src: Src): boolean {
320
+ return isDASHSource(src)
321
+ }
322
+
323
+ /**
324
+ * Cleanup the provider
325
+ */
326
+ protected cleanup(): void {
327
+ if (this.dash) {
328
+ this.dash.reset()
329
+ this.dash = null
330
+ }
331
+
332
+ if (this.video) {
333
+ this.video.remove()
334
+ this.video = null
335
+ }
336
+
337
+ this.source = null
338
+ }
339
+
340
+ // ==========================================================================
341
+ // Playback
342
+ // ==========================================================================
343
+
344
+ async play(): Promise<void> {
345
+ if (!this.video) return
346
+ try {
347
+ await this.video.play()
348
+ } catch (error) {
349
+ if ((error as Error).name === 'NotAllowedError') {
350
+ this.emitError(1, 'Playback was blocked. User interaction required.', error)
351
+ } else {
352
+ throw error
353
+ }
354
+ }
355
+ }
356
+
357
+ pause(): void {
358
+ if (this.video) {
359
+ this.video.pause()
360
+ }
361
+ }
362
+
363
+ // ==========================================================================
364
+ // Seeking
365
+ // ==========================================================================
366
+
367
+ seekTo(time: number): void {
368
+ if (this.video) {
369
+ this.video.currentTime = time
370
+ }
371
+ }
372
+
373
+ getCurrentTime(): number {
374
+ return this.video?.currentTime || 0
375
+ }
376
+
377
+ getDuration(): number {
378
+ return this.video?.duration || 0
379
+ }
380
+
381
+ // ==========================================================================
382
+ // Volume
383
+ // ==========================================================================
384
+
385
+ setVolume(volume: number): void {
386
+ this._volume = Math.max(0, Math.min(1, volume))
387
+ if (this.video) {
388
+ this.video.volume = this._volume
389
+ }
390
+ }
391
+
392
+ getVolume(): number {
393
+ return this.video?.volume ?? this._volume
394
+ }
395
+
396
+ setMuted(muted: boolean): void {
397
+ this._muted = muted
398
+ if (this.video) {
399
+ this.video.muted = muted
400
+ }
401
+ }
402
+
403
+ getMuted(): boolean {
404
+ return this.video?.muted ?? this._muted
405
+ }
406
+
407
+ // ==========================================================================
408
+ // Playback Rate
409
+ // ==========================================================================
410
+
411
+ setPlaybackRate(rate: number): void {
412
+ this._playbackRate = rate
413
+ if (this.video) {
414
+ this.video.playbackRate = rate
415
+ }
416
+ }
417
+
418
+ getPlaybackRate(): number {
419
+ return this.video?.playbackRate ?? this._playbackRate
420
+ }
421
+
422
+ // ==========================================================================
423
+ // Live
424
+ // ==========================================================================
425
+
426
+ get isLive(): boolean {
427
+ return this.dash?.isDynamic() ?? false
428
+ }
429
+
430
+ get liveLatency(): number {
431
+ return this.dash?.getCurrentLiveLatency() ?? 0
432
+ }
433
+
434
+ get canLiveSync(): boolean {
435
+ return this.isLive
436
+ }
437
+
438
+ syncToLive(): void {
439
+ if (this.dash && this.isLive) {
440
+ this.dash.seekToOriginalLive()
441
+ }
442
+ }
443
+
444
+ // ==========================================================================
445
+ // Quality
446
+ // ==========================================================================
447
+
448
+ getQualities(): VideoQuality[] {
449
+ if (!this.dash) return []
450
+
451
+ const bitrateInfoList = this.dash.getBitrateInfoListFor('video')
452
+ if (!bitrateInfoList) return []
453
+
454
+ const currentIndex = this.dash.getQualityFor('video')
455
+
456
+ return bitrateInfoList.map((info: any, index: number) => ({
457
+ id: `${index}`,
458
+ label: `${info.height}p`,
459
+ width: info.width,
460
+ height: info.height,
461
+ bitrate: info.bitrate,
462
+ selected: index === currentIndex,
463
+ }))
464
+ }
465
+
466
+ setQuality(quality: VideoQuality | 'auto'): void {
467
+ if (!this.dash) return
468
+
469
+ if (quality === 'auto') {
470
+ this.dash.updateSettings({
471
+ streaming: { abr: { autoSwitchBitrate: { video: true } } },
472
+ })
473
+ }
474
+ else {
475
+ const index = Number.parseInt(quality.id, 10)
476
+ if (!Number.isNaN(index)) {
477
+ this.dash.updateSettings({
478
+ streaming: { abr: { autoSwitchBitrate: { video: false } } },
479
+ })
480
+ this.dash.setQualityFor('video', index)
481
+ }
482
+ }
483
+ }
484
+
485
+ get isAutoQuality(): boolean {
486
+ if (!this.dash) return true
487
+ const settings = this.dash.getSettings()
488
+ return settings?.streaming?.abr?.autoSwitchBitrate?.video ?? true
489
+ }
490
+
491
+ // ==========================================================================
492
+ // Audio Tracks
493
+ // ==========================================================================
494
+
495
+ getAudioTracks(): AudioTrack[] {
496
+ if (!this.dash) return []
497
+
498
+ const tracks = this.dash.getTracksFor('audio')
499
+ if (!tracks) return []
500
+
501
+ const currentTrack = this.dash.getCurrentTrackFor('audio')
502
+
503
+ return tracks.map((track: any, index: number) => ({
504
+ id: track.id || `${index}`,
505
+ label: track.labels?.[0]?.text || track.lang || `Track ${index + 1}`,
506
+ language: track.lang || '',
507
+ kind: 'main' as const,
508
+ selected: track === currentTrack,
509
+ }))
510
+ }
511
+
512
+ setAudioTrack(trackId: string): void {
513
+ if (!this.dash) return
514
+
515
+ const tracks = this.dash.getTracksFor('audio')
516
+ const track = tracks.find((t: any, i: number) => (t.id || `${i}`) === trackId)
517
+
518
+ if (track) {
519
+ this.dash.setCurrentTrack(track)
520
+ }
521
+ }
522
+
523
+ // ==========================================================================
524
+ // Text Tracks
525
+ // ==========================================================================
526
+
527
+ getTextTracks(): TextTrack[] {
528
+ if (!this.dash) return []
529
+
530
+ const tracks = this.dash.getTracksFor('text')
531
+ if (!tracks) return []
532
+
533
+ const currentTrack = this.dash.getCurrentTrackFor('text')
534
+
535
+ return tracks.map((track: any, index: number) => ({
536
+ id: track.id || `${index}`,
537
+ label: track.labels?.[0]?.text || track.lang || `Subtitle ${index + 1}`,
538
+ language: track.lang || '',
539
+ kind: (track.kind || 'subtitles') as TextTrack['kind'],
540
+ mode: (track === currentTrack ? 'showing' : 'disabled') as TextTrack['mode'],
541
+ cues: [],
542
+ }))
543
+ }
544
+
545
+ setTextTrackMode(trackId: string, mode: 'disabled' | 'hidden' | 'showing'): void {
546
+ if (!this.dash) return
547
+
548
+ if (mode === 'disabled') {
549
+ this.dash.enableText(false)
550
+ return
551
+ }
552
+
553
+ const tracks = this.dash.getTracksFor('text')
554
+ const track = tracks.find((t: any, i: number) => (t.id || `${i}`) === trackId)
555
+
556
+ if (track) {
557
+ this.dash.setCurrentTrack(track)
558
+ this.dash.enableText(mode === 'showing')
559
+ }
560
+ }
561
+ }
562
+
563
+ // =============================================================================
564
+ // Provider Loader
565
+ // =============================================================================
566
+
567
+ export const dashLoader: ProviderLoader = {
568
+ name: 'dash',
569
+ type: 'dash',
570
+
571
+ canPlay(src: Src): boolean {
572
+ return isDASHSource(src)
573
+ },
574
+
575
+ mediaType(): 'video' {
576
+ return 'video'
577
+ },
578
+
579
+ async load(container: HTMLElement, options: PlayerOptions): Promise<DASHProvider> {
580
+ const provider = new DASHProvider()
581
+ await provider.setup(container, options)
582
+ return provider
583
+ },
584
+
585
+ preconnect(): string[] {
586
+ return ['https://cdn.dashjs.org']
587
+ },
588
+ }