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.
- package/LICENSE.md +21 -0
- package/README.md +438 -0
- package/dist/cdn.d.ts +10 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.min.js +1046 -0
- package/dist/cdn.min.js.map +54 -0
- package/dist/cdn.test.d.ts +2 -0
- package/dist/cdn.test.d.ts.map +1 -0
- package/dist/chunk-36x4yhjn.js +765 -0
- package/dist/chunk-36x4yhjn.js.map +12 -0
- package/dist/chunk-eajts2jv.js +4243 -0
- package/dist/chunk-eajts2jv.js.map +42 -0
- package/dist/chunk-z1ehbz9f.js +380 -0
- package/dist/chunk-z1ehbz9f.js.map +19 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/core/audio-gain.d.ts +67 -0
- package/dist/core/audio-gain.d.ts.map +1 -0
- package/dist/core/caption-styles.d.ts +121 -0
- package/dist/core/caption-styles.d.ts.map +1 -0
- package/dist/core/chapters.d.ts +150 -0
- package/dist/core/chapters.d.ts.map +1 -0
- package/dist/core/error-overlay.d.ts +71 -0
- package/dist/core/error-overlay.d.ts.map +1 -0
- package/dist/core/events.d.ts +119 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/features.d.ts +64 -0
- package/dist/core/features.d.ts.map +1 -0
- package/dist/core/i18n.d.ts +132 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/index.d.ts +37 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/live.d.ts +130 -0
- package/dist/core/live.d.ts.map +1 -0
- package/dist/core/media-session.d.ts +38 -0
- package/dist/core/media-session.d.ts.map +1 -0
- package/dist/core/orientation.d.ts +22 -0
- package/dist/core/orientation.d.ts.map +1 -0
- package/dist/core/popover.d.ts +42 -0
- package/dist/core/popover.d.ts.map +1 -0
- package/dist/core/remote-playback.d.ts +195 -0
- package/dist/core/remote-playback.d.ts.map +1 -0
- package/dist/core/settings-menu.d.ts +132 -0
- package/dist/core/settings-menu.d.ts.map +1 -0
- package/dist/core/spinner.d.ts +67 -0
- package/dist/core/spinner.d.ts.map +1 -0
- package/dist/core/state.d.ts +142 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/text-tracks.d.ts +162 -0
- package/dist/core/text-tracks.d.ts.map +1 -0
- package/dist/core/themes.d.ts +213 -0
- package/dist/core/themes.d.ts.map +1 -0
- package/dist/core/thumbnails.d.ts +124 -0
- package/dist/core/thumbnails.d.ts.map +1 -0
- package/dist/core/tooltips.d.ts +100 -0
- package/dist/core/tooltips.d.ts.map +1 -0
- package/dist/elements/base.d.ts +3 -0
- package/dist/elements/base.d.ts.map +1 -0
- package/dist/elements/index.d.ts +33 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/elements/index.js +55 -0
- package/dist/elements/index.js.map +9 -0
- package/dist/elements/media-airplay-button.d.ts +13 -0
- package/dist/elements/media-airplay-button.d.ts.map +1 -0
- package/dist/elements/media-error-display.d.ts +13 -0
- package/dist/elements/media-error-display.d.ts.map +1 -0
- package/dist/elements/media-fullscreen-button.d.ts +13 -0
- package/dist/elements/media-fullscreen-button.d.ts.map +1 -0
- package/dist/elements/media-live-button.d.ts +13 -0
- package/dist/elements/media-live-button.d.ts.map +1 -0
- package/dist/elements/media-mute-button.d.ts +13 -0
- package/dist/elements/media-mute-button.d.ts.map +1 -0
- package/dist/elements/media-pip-button.d.ts +13 -0
- package/dist/elements/media-pip-button.d.ts.map +1 -0
- package/dist/elements/media-play-button.d.ts +14 -0
- package/dist/elements/media-play-button.d.ts.map +1 -0
- package/dist/elements/media-playback-rate-button.d.ts +10 -0
- package/dist/elements/media-playback-rate-button.d.ts.map +1 -0
- package/dist/elements/media-preview-time.d.ts +18 -0
- package/dist/elements/media-preview-time.d.ts.map +1 -0
- package/dist/elements/media-progress-bar.d.ts +16 -0
- package/dist/elements/media-progress-bar.d.ts.map +1 -0
- package/dist/elements/media-remote-playback-button.d.ts +16 -0
- package/dist/elements/media-remote-playback-button.d.ts.map +1 -0
- package/dist/elements/media-seek-button.d.ts +9 -0
- package/dist/elements/media-seek-button.d.ts.map +1 -0
- package/dist/elements/media-settings-menu.d.ts +32 -0
- package/dist/elements/media-settings-menu.d.ts.map +1 -0
- package/dist/elements/media-time-display.d.ts +21 -0
- package/dist/elements/media-time-display.d.ts.map +1 -0
- package/dist/elements/media-time-group.d.ts +22 -0
- package/dist/elements/media-time-group.d.ts.map +1 -0
- package/dist/elements/media-time-separator.d.ts +13 -0
- package/dist/elements/media-time-separator.d.ts.map +1 -0
- package/dist/elements/media-volume-slider.d.ts +16 -0
- package/dist/elements/media-volume-slider.d.ts.map +1 -0
- package/dist/elements/utils.d.ts +27 -0
- package/dist/elements/utils.d.ts.map +1 -0
- package/dist/elements/video-player.d.ts +23 -0
- package/dist/elements/video-player.d.ts.map +1 -0
- package/dist/elements/video-skin.d.ts +23 -0
- package/dist/elements/video-skin.d.ts.map +1 -0
- package/dist/features/captions.d.ts +8 -0
- package/dist/features/captions.d.ts.map +1 -0
- package/dist/features/create-player.d.ts +45 -0
- package/dist/features/create-player.d.ts.map +1 -0
- package/dist/features/fullscreen.d.ts +8 -0
- package/dist/features/fullscreen.d.ts.map +1 -0
- package/dist/features/gestures.d.ts +8 -0
- package/dist/features/gestures.d.ts.map +1 -0
- package/dist/features/index.d.ts +26 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +31 -0
- package/dist/features/index.js.map +9 -0
- package/dist/features/keyboard.d.ts +8 -0
- package/dist/features/keyboard.d.ts.map +1 -0
- package/dist/features/pip.d.ts +8 -0
- package/dist/features/pip.d.ts.map +1 -0
- package/dist/features/playback.d.ts +8 -0
- package/dist/features/playback.d.ts.map +1 -0
- package/dist/features/quality.d.ts +8 -0
- package/dist/features/quality.d.ts.map +1 -0
- package/dist/features/types.d.ts +26 -0
- package/dist/features/types.d.ts.map +1 -0
- package/dist/features/volume.d.ts +8 -0
- package/dist/features/volume.d.ts.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8149 -0
- package/dist/index.js.map +31 -0
- package/dist/layouts/index.d.ts +58 -0
- package/dist/layouts/index.d.ts.map +1 -0
- package/dist/player.d.ts +90 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/plugins/ads.d.ts +311 -0
- package/dist/plugins/ads.d.ts.map +1 -0
- package/dist/plugins/analytics.d.ts +203 -0
- package/dist/plugins/analytics.d.ts.map +1 -0
- package/dist/plugins/end-screen.d.ts +184 -0
- package/dist/plugins/end-screen.d.ts.map +1 -0
- package/dist/plugins/index.d.ts +13 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +20 -0
- package/dist/plugins/index.js.map +9 -0
- package/dist/plugins/skip-segments.d.ts +159 -0
- package/dist/plugins/skip-segments.d.ts.map +1 -0
- package/dist/plugins/watermark.d.ts +157 -0
- package/dist/plugins/watermark.d.ts.map +1 -0
- package/dist/providers/base.d.ts +56 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/dash.d.ts +94 -0
- package/dist/providers/dash.d.ts.map +1 -0
- package/dist/providers/hls.d.ts +182 -0
- package/dist/providers/hls.d.ts.map +1 -0
- package/dist/providers/html5.d.ts +64 -0
- package/dist/providers/html5.d.ts.map +1 -0
- package/dist/providers/index.d.ts +41 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/vimeo.d.ts +157 -0
- package/dist/providers/vimeo.d.ts.map +1 -0
- package/dist/providers/youtube.d.ts +134 -0
- package/dist/providers/youtube.d.ts.map +1 -0
- package/dist/stx/index.d.ts +59 -0
- package/dist/stx/index.d.ts.map +1 -0
- package/dist/stx/index.js +480 -0
- package/dist/stx/index.js.map +11 -0
- package/dist/types.d.ts +724 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/ui/components.d.ts +121 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +16 -0
- package/dist/ui/index.js.map +9 -0
- package/package.json +109 -0
- package/src/cdn.test.ts +10 -0
- package/src/cdn.ts +60 -0
- package/src/config.ts +25 -0
- package/src/core/audio-gain.ts +171 -0
- package/src/core/caption-styles.ts +568 -0
- package/src/core/chapters.ts +551 -0
- package/src/core/error-overlay.ts +354 -0
- package/src/core/events.ts +552 -0
- package/src/core/features.ts +287 -0
- package/src/core/i18n.ts +590 -0
- package/src/core/index.ts +249 -0
- package/src/core/live.ts +472 -0
- package/src/core/media-session.ts +137 -0
- package/src/core/orientation.ts +57 -0
- package/src/core/popover.ts +143 -0
- package/src/core/remote-playback.ts +661 -0
- package/src/core/settings-menu.ts +630 -0
- package/src/core/spinner.ts +310 -0
- package/src/core/state.ts +476 -0
- package/src/core/text-tracks.ts +599 -0
- package/src/core/themes.ts +607 -0
- package/src/core/thumbnails.ts +457 -0
- package/src/core/tooltips.ts +370 -0
- package/src/elements/base.ts +2 -0
- package/src/elements/index.ts +82 -0
- package/src/elements/media-airplay-button.ts +78 -0
- package/src/elements/media-error-display.ts +47 -0
- package/src/elements/media-fullscreen-button.ts +72 -0
- package/src/elements/media-live-button.ts +65 -0
- package/src/elements/media-mute-button.ts +69 -0
- package/src/elements/media-pip-button.ts +66 -0
- package/src/elements/media-play-button.ts +69 -0
- package/src/elements/media-playback-rate-button.ts +59 -0
- package/src/elements/media-preview-time.ts +75 -0
- package/src/elements/media-progress-bar.ts +200 -0
- package/src/elements/media-remote-playback-button.ts +108 -0
- package/src/elements/media-seek-button.ts +50 -0
- package/src/elements/media-settings-menu.ts +302 -0
- package/src/elements/media-time-display.ts +99 -0
- package/src/elements/media-time-group.ts +41 -0
- package/src/elements/media-time-separator.ts +35 -0
- package/src/elements/media-volume-slider.ts +140 -0
- package/src/elements/utils.ts +82 -0
- package/src/elements/video-player.ts +175 -0
- package/src/elements/video-skin.ts +127 -0
- package/src/features/captions.ts +56 -0
- package/src/features/create-player.ts +91 -0
- package/src/features/fullscreen.ts +44 -0
- package/src/features/gestures.ts +44 -0
- package/src/features/index.ts +44 -0
- package/src/features/keyboard.ts +89 -0
- package/src/features/pip.ts +47 -0
- package/src/features/playback.ts +68 -0
- package/src/features/quality.ts +19 -0
- package/src/features/types.ts +28 -0
- package/src/features/volume.ts +46 -0
- package/src/index.ts +635 -0
- package/src/layouts/index.ts +672 -0
- package/src/player.ts +821 -0
- package/src/plugins/ads.ts +1045 -0
- package/src/plugins/analytics.ts +673 -0
- package/src/plugins/end-screen.ts +793 -0
- package/src/plugins/index.ts +61 -0
- package/src/plugins/skip-segments.ts +537 -0
- package/src/plugins/watermark.ts +537 -0
- package/src/providers/base.ts +197 -0
- package/src/providers/dash.ts +588 -0
- package/src/providers/hls.ts +624 -0
- package/src/providers/html5.ts +473 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/vimeo.ts +533 -0
- package/src/providers/youtube.ts +542 -0
- package/src/stx/index.ts +606 -0
- package/src/types.ts +874 -0
- package/src/ui/components.ts +910 -0
- 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
|
+
}
|