@growth-labs/video 0.3.8 → 0.3.9
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/package.json
CHANGED
|
@@ -94,17 +94,22 @@ const playerBoolAttrs = buildPlayerBooleanAttrs({
|
|
|
94
94
|
<script>
|
|
95
95
|
import { bootstrapPremiumPlayer } from './premium-bootstrap'
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
// Vidstack registration is the consumer's responsibility — they choose
|
|
98
|
+
// which player bundle to ship (`vidstack/player` vs. `vidstack/elements`
|
|
99
|
+
// vs. a per-component define) and which layouts to register. The
|
|
100
|
+
// bootstrap below uses `customElements.whenDefined('media-player')`
|
|
101
|
+
// internally to wait for whatever registration path the consumer picked,
|
|
102
|
+
// so we no longer fire-and-forget a load of `vidstack/elements` here
|
|
103
|
+
// (it has no `defineCustomElements` export anyway — that was dead code).
|
|
102
104
|
|
|
103
105
|
const VIDEO_ACCESS_DENIED = 'gl:video-access-denied'
|
|
104
106
|
|
|
105
107
|
// Premium players ship without `src` and with `data-pending-src` carrying
|
|
106
108
|
// the manifest URL. Establish the session cookie *before* Vidstack loads
|
|
107
|
-
// the manifest — otherwise preload="metadata" 401s on mount.
|
|
109
|
+
// the manifest — otherwise preload="metadata" 401s on mount. The
|
|
110
|
+
// bootstrap is single-flight (consumes `data-pending-src` on entry) so
|
|
111
|
+
// re-invocation (e.g. from a view-transition `astro:after-swap` re-running
|
|
112
|
+
// page scripts) is a no-op and never tears down an in-flight HLS load.
|
|
108
113
|
document.querySelectorAll<HTMLElement>('media-player[data-premium="true"]').forEach((player) => {
|
|
109
114
|
bootstrapPremiumPlayer(player as unknown as Parameters<typeof bootstrapPremiumPlayer>[0], {
|
|
110
115
|
emitAccessDenied: (videoId) =>
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
export interface PremiumPlayerLike {
|
|
2
2
|
dataset: { videoId?: string; sessionRoute?: string; pendingSrc?: string }
|
|
3
3
|
src?: string
|
|
4
|
+
tagName?: string
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
export interface BootstrapOptions {
|
|
7
8
|
fetch?: typeof fetch
|
|
8
9
|
emitAccessDenied?: (videoId: string) => void
|
|
10
|
+
/**
|
|
11
|
+
* Hook used by tests to bypass `customElements.whenDefined('media-player')`.
|
|
12
|
+
* In production the bootstrap awaits the upgrade so `player.src` (a Vidstack
|
|
13
|
+
* property setter installed during upgrade) is in place before assignment.
|
|
14
|
+
*/
|
|
15
|
+
waitForUpgrade?: (tagName: string) => Promise<void>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default `waitForUpgrade` — resolves once `customElements.whenDefined()` for
|
|
20
|
+
* the player tag has run. Resolves immediately if `customElements` is missing
|
|
21
|
+
* (SSR / non-browser tests) so the bootstrap is still callable.
|
|
22
|
+
*/
|
|
23
|
+
function defaultWaitForUpgrade(tagName: string): Promise<void> {
|
|
24
|
+
if (typeof globalThis === 'undefined') return Promise.resolve()
|
|
25
|
+
const ce = (globalThis as { customElements?: CustomElementRegistry }).customElements
|
|
26
|
+
if (!ce || typeof ce.whenDefined !== 'function') return Promise.resolve()
|
|
27
|
+
return ce.whenDefined(tagName).then(() => undefined)
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
export async function bootstrapPremiumPlayer(
|
|
@@ -15,6 +34,19 @@ export async function bootstrapPremiumPlayer(
|
|
|
15
34
|
const { videoId, sessionRoute, pendingSrc } = player.dataset
|
|
16
35
|
if (!videoId || !sessionRoute) return 'skipped'
|
|
17
36
|
|
|
37
|
+
// Single-flight guard. The bootstrap is supposed to run exactly once per
|
|
38
|
+
// premium <media-player>, but consumers can re-run page scripts on
|
|
39
|
+
// view-transition swaps (`astro:after-swap`), or a defensive consumer can
|
|
40
|
+
// call into the bootstrap from multiple lifecycle hooks. A second
|
|
41
|
+
// `player.src = pendingSrc` after Vidstack has attached the HLS provider
|
|
42
|
+
// tears the provider down — the in-flight master.m3u8 / playlist.m3u8 XHRs
|
|
43
|
+
// abort, the player is left without a source, and `<media-quality-radio-
|
|
44
|
+
// group>` reports zero qualities so the picker disappears. Consuming
|
|
45
|
+
// `data-pending-src` makes the function a no-op on every call after the
|
|
46
|
+
// first authorized one, and prevents the race regardless of how many times
|
|
47
|
+
// the host script invokes it.
|
|
48
|
+
delete player.dataset.pendingSrc
|
|
49
|
+
|
|
18
50
|
const f = opts.fetch ?? fetch
|
|
19
51
|
let ok = false
|
|
20
52
|
try {
|
|
@@ -30,7 +62,21 @@ export async function bootstrapPremiumPlayer(
|
|
|
30
62
|
}
|
|
31
63
|
|
|
32
64
|
if (ok) {
|
|
33
|
-
if (pendingSrc)
|
|
65
|
+
if (pendingSrc) {
|
|
66
|
+
// Wait for Vidstack to upgrade <media-player>. The `src` getter/setter
|
|
67
|
+
// is installed during element upgrade; assigning to `player.src`
|
|
68
|
+
// before then writes a plain JS property that the Vidstack provider
|
|
69
|
+
// never reads, so the manifest URL is silently lost and the player
|
|
70
|
+
// stays inert. `customElements.whenDefined('media-player')` resolves
|
|
71
|
+
// the moment `vidstack/player`'s `defineCustomElement(MediaPlayer
|
|
72
|
+
// Element)` has run; in practice this is the same microtask as the
|
|
73
|
+
// session POST returns. The await is virtually free on a warm page
|
|
74
|
+
// and prevents the lost-source race on a cold one.
|
|
75
|
+
const tagName = (player.tagName ?? 'media-player').toLowerCase()
|
|
76
|
+
const waitForUpgrade = opts.waitForUpgrade ?? defaultWaitForUpgrade
|
|
77
|
+
await waitForUpgrade(tagName)
|
|
78
|
+
player.src = pendingSrc
|
|
79
|
+
}
|
|
34
80
|
return 'authorized'
|
|
35
81
|
}
|
|
36
82
|
opts.emitAccessDenied?.(videoId)
|