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,287 @@
1
+ /**
2
+ * ts-video-player Feature Availability Detection
3
+ *
4
+ * Detects platform capabilities for volume, fullscreen, and PiP.
5
+ *
6
+ * @module core/features
7
+ */
8
+
9
+ import type { FeatureAvailability } from '../types'
10
+
11
+ // =============================================================================
12
+ // Platform Detection Helpers
13
+ // =============================================================================
14
+
15
+ /**
16
+ * Detect iOS Safari (including iPadOS Safari)
17
+ */
18
+ export function isIOSSafari(): boolean {
19
+ if (typeof navigator === 'undefined') return false
20
+ const ua = navigator.userAgent
21
+ // iPhone/iPad/iPod with Safari
22
+ if (/iPad|iPhone|iPod/.test(ua) && !(window as any).MSStream) return true
23
+ // iPadOS reports as Mac but has touch
24
+ if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) return true
25
+ return false
26
+ }
27
+
28
+ /**
29
+ * Detect Safari browser (desktop or mobile)
30
+ */
31
+ export function isSafari(): boolean {
32
+ if (typeof navigator === 'undefined') return false
33
+ return /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent)
34
+ }
35
+
36
+ /**
37
+ * Detect standalone PWA mode
38
+ */
39
+ export function isStandalonePWA(): boolean {
40
+ if (typeof window === 'undefined') return false
41
+ return (navigator as any).standalone === true
42
+ || window.matchMedia('(display-mode: standalone)').matches
43
+ }
44
+
45
+ // =============================================================================
46
+ // Volume Availability
47
+ // =============================================================================
48
+
49
+ /**
50
+ * Detect volume control availability.
51
+ * iOS Safari silently ignores programmatic volume changes on media elements.
52
+ */
53
+ export function detectVolumeAvailability(
54
+ media?: HTMLMediaElement | null,
55
+ ): FeatureAvailability {
56
+ if (isIOSSafari()) return 'unsupported'
57
+ if (!media) return 'unavailable'
58
+ return 'available'
59
+ }
60
+
61
+ /**
62
+ * Async volume probe: attempts to set volume and reads it back.
63
+ * This is the authoritative check - the sync check is a fast heuristic.
64
+ */
65
+ export async function probeVolumeAvailability(
66
+ media: HTMLMediaElement,
67
+ ): Promise<FeatureAvailability> {
68
+ const original = media.volume
69
+ try {
70
+ const testValue = original === 1 ? 0.5 : 1
71
+ media.volume = testValue
72
+ // Some browsers apply volume asynchronously
73
+ await new Promise((r) => setTimeout(r, 0))
74
+ const changed = media.volume !== original
75
+ media.volume = original
76
+ return changed ? 'available' : 'unsupported'
77
+ } catch {
78
+ try { media.volume = original } catch { /* ignore */ }
79
+ return 'unsupported'
80
+ }
81
+ }
82
+
83
+ // =============================================================================
84
+ // Fullscreen Availability
85
+ // =============================================================================
86
+
87
+ interface WebKitDocument extends Document {
88
+ webkitFullscreenEnabled?: boolean
89
+ webkitFullscreenElement?: Element
90
+ webkitExitFullscreen?: () => Promise<void>
91
+ webkitCancelFullScreen?: () => Promise<void>
92
+ mozFullScreenEnabled?: boolean
93
+ msFullscreenEnabled?: boolean
94
+ }
95
+
96
+ interface WebKitVideoElement extends HTMLVideoElement {
97
+ webkitEnterFullscreen?: () => void
98
+ webkitExitFullscreen?: () => void
99
+ webkitSupportsFullscreen?: boolean
100
+ webkitDisplayingFullscreen?: boolean
101
+ webkitPresentationMode?: string
102
+ webkitSetPresentationMode?: (mode: string) => void
103
+ webkitSupportsPresentationMode?: (mode: string) => boolean
104
+ }
105
+
106
+ interface WebKitFullscreenElement extends HTMLElement {
107
+ webkitRequestFullscreen?: () => Promise<void>
108
+ webkitRequestFullScreen?: () => Promise<void>
109
+ }
110
+
111
+ /**
112
+ * Detect fullscreen API availability.
113
+ */
114
+ export function detectFullscreenAvailability(
115
+ container?: HTMLElement | null,
116
+ videoElement?: HTMLVideoElement | null,
117
+ ): FeatureAvailability {
118
+ const doc = typeof document !== 'undefined' ? document as WebKitDocument : null
119
+ if (!doc) return 'unsupported'
120
+
121
+ // Standard API or WebKit prefix
122
+ if (doc.fullscreenEnabled || doc.webkitFullscreenEnabled) return 'available'
123
+ // Moz/MS prefixes
124
+ if (doc.mozFullScreenEnabled || doc.msFullscreenEnabled) return 'available'
125
+
126
+ // iOS Safari: video-only fullscreen via webkitEnterFullscreen
127
+ if (videoElement) {
128
+ const video = videoElement as WebKitVideoElement
129
+ if (video.webkitSupportsFullscreen === true) return 'available'
130
+ if (typeof video.webkitEnterFullscreen === 'function') return 'available'
131
+ }
132
+
133
+ if (!container && !videoElement) return 'unavailable'
134
+ return 'unsupported'
135
+ }
136
+
137
+ /**
138
+ * Whether this environment only supports video-element fullscreen (iOS Safari),
139
+ * not container-level fullscreen.
140
+ */
141
+ export function isVideoOnlyFullscreen(): boolean {
142
+ if (typeof document === 'undefined') return false
143
+ const doc = document as WebKitDocument
144
+ return isIOSSafari()
145
+ && !doc.fullscreenEnabled
146
+ && !doc.webkitFullscreenEnabled
147
+ }
148
+
149
+ /**
150
+ * Enter fullscreen with cross-browser support including iOS Safari.
151
+ */
152
+ export async function enterFullscreen(
153
+ container: HTMLElement | null,
154
+ media: HTMLMediaElement | null,
155
+ ): Promise<void> {
156
+ const video = media as WebKitVideoElement | null
157
+
158
+ // Try container first (standard and WebKit APIs)
159
+ if (container) {
160
+ const el = container as WebKitFullscreenElement
161
+ if (typeof el.requestFullscreen === 'function') return el.requestFullscreen()
162
+ if (typeof el.webkitRequestFullscreen === 'function') return el.webkitRequestFullscreen()
163
+ if (typeof el.webkitRequestFullScreen === 'function') return el.webkitRequestFullScreen()
164
+ }
165
+
166
+ // Fall back to video element (iOS Safari)
167
+ if (video && typeof video.webkitEnterFullscreen === 'function') {
168
+ video.webkitEnterFullscreen()
169
+ return
170
+ }
171
+
172
+ // Last resort: try media element with standard API
173
+ if (media && typeof media.requestFullscreen === 'function') {
174
+ return media.requestFullscreen()
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Exit fullscreen with cross-browser support.
180
+ */
181
+ export async function exitFullscreen(media?: HTMLMediaElement | null): Promise<void> {
182
+ const doc = document as WebKitDocument
183
+ const video = media as WebKitVideoElement | null
184
+
185
+ if (typeof doc.exitFullscreen === 'function') return doc.exitFullscreen()
186
+ if (typeof doc.webkitExitFullscreen === 'function') return doc.webkitExitFullscreen()
187
+ if (typeof doc.webkitCancelFullScreen === 'function') return doc.webkitCancelFullScreen()
188
+
189
+ // iOS Safari video fullscreen
190
+ if (video && typeof video.webkitExitFullscreen === 'function') {
191
+ video.webkitExitFullscreen()
192
+ }
193
+ }
194
+
195
+ // =============================================================================
196
+ // Picture-in-Picture Availability
197
+ // =============================================================================
198
+
199
+ /**
200
+ * Detect Picture-in-Picture availability.
201
+ */
202
+ export function detectPipAvailability(
203
+ videoElement?: HTMLVideoElement | null,
204
+ ): FeatureAvailability {
205
+ if (!videoElement) return 'unavailable'
206
+
207
+ // Standard PiP API
208
+ if ('requestPictureInPicture' in videoElement) {
209
+ if (typeof document !== 'undefined' && document.pictureInPictureEnabled === false) {
210
+ return 'unavailable'
211
+ }
212
+ // Safari PWAs have the API but it doesn't work
213
+ if (isSafari() && isStandalonePWA()) return 'unavailable'
214
+ return 'available'
215
+ }
216
+
217
+ // iOS Safari WebKit presentation mode
218
+ const video = videoElement as WebKitVideoElement
219
+ if (typeof video.webkitSetPresentationMode === 'function') {
220
+ if (isStandalonePWA()) return 'unavailable'
221
+ if (typeof video.webkitSupportsPresentationMode === 'function') {
222
+ return video.webkitSupportsPresentationMode('picture-in-picture')
223
+ ? 'available'
224
+ : 'unsupported'
225
+ }
226
+ return 'available'
227
+ }
228
+
229
+ return 'unsupported'
230
+ }
231
+
232
+ /**
233
+ * Enter Picture-in-Picture with cross-browser support.
234
+ */
235
+ export async function enterPiP(media: HTMLMediaElement): Promise<void> {
236
+ const video = media as HTMLVideoElement & WebKitVideoElement
237
+
238
+ // Standard PiP API
239
+ if (typeof video.requestPictureInPicture === 'function') {
240
+ await video.requestPictureInPicture()
241
+ return
242
+ }
243
+
244
+ // iOS Safari WebKit presentation mode
245
+ if (typeof video.webkitSetPresentationMode === 'function') {
246
+ video.webkitSetPresentationMode('picture-in-picture')
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Exit Picture-in-Picture with cross-browser support.
252
+ */
253
+ export async function exitPiP(media?: HTMLMediaElement | null): Promise<void> {
254
+ // Standard PiP API
255
+ if (document.pictureInPictureElement && typeof document.exitPictureInPicture === 'function') {
256
+ await document.exitPictureInPicture()
257
+ return
258
+ }
259
+
260
+ // iOS Safari WebKit presentation mode
261
+ if (media) {
262
+ const video = media as WebKitVideoElement
263
+ if (video.webkitPresentationMode === 'picture-in-picture'
264
+ && typeof video.webkitSetPresentationMode === 'function') {
265
+ video.webkitSetPresentationMode('inline')
266
+ }
267
+ }
268
+ }
269
+
270
+ // =============================================================================
271
+ // Aggregate Detection
272
+ // =============================================================================
273
+
274
+ /**
275
+ * Detect all feature availabilities at once.
276
+ */
277
+ export function detectAllFeatures(
278
+ container?: HTMLElement | null,
279
+ videoElement?: HTMLVideoElement | null,
280
+ mediaElement?: HTMLMediaElement | null,
281
+ ): Record<'volume' | 'fullscreen' | 'pip', FeatureAvailability> {
282
+ return {
283
+ volume: detectVolumeAvailability(mediaElement),
284
+ fullscreen: detectFullscreenAvailability(container, videoElement),
285
+ pip: detectPipAvailability(videoElement),
286
+ }
287
+ }