@growth-labs/video 0.3.3 → 0.3.5

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/README.md CHANGED
@@ -54,6 +54,32 @@ Why a module spec instead of an inline function? The integration runs in the bui
54
54
 
55
55
  `per-video` scope (the 0.3.0 default) issues a cookie restricted to one `videoId`, so video status changes propagate within `sessionTtl`. `wildcard` issues a cookie that plays every premium video for the cookie's lifetime — cheaper per page but status changes do not propagate until the cookie expires.
56
56
 
57
+ ## Customizing the layout
58
+
59
+ Since **0.3.5**, `<Video>` exposes a named slot `layout` for replacing the Vidstack chrome (controls, sliders, menus) without losing anything the package wires up around the player — premium bootstrap, accessCheck, `gl:video-*` analytics events, captions/chapter `<track>` elements, progress marks.
60
+
61
+ ```astro
62
+ ---
63
+ import { Video } from '@growth-labs/video/components'
64
+ import MyVideoLayout from '../components/MyVideoLayout.astro'
65
+ ---
66
+
67
+ <Video videoId="..." title="..." premium>
68
+ <MyVideoLayout slot="layout" />
69
+ </Video>
70
+ ```
71
+
72
+ If you omit the slot, you get Vidstack's stock `<media-video-layout>` (the package's current default). If you provide it, your slotted content **replaces** the default entirely — it is rendered as a direct child of `<media-player>`, so any Vidstack layout primitive (`<media-controls>`, `<media-time-slider>`, `<media-quality-radio-group>`, etc.) works.
73
+
74
+ Consumers writing a custom layout typically need to import Vidstack's full UI element registry once at the top of their component (Vidstack's `vidstack/elements` only defines a minimal set; the full primitive set is under `vidstack/player/ui`):
75
+
76
+ ```ts
77
+ // inside MyVideoLayout.astro's <script>
78
+ import 'vidstack/player/ui'
79
+ ```
80
+
81
+ Captions and chapter `<track>` elements continue to be rendered by `<Video>` inside `<media-provider>` — do not duplicate them inside your slotted layout.
82
+
57
83
  ## R2 Layout
58
84
 
59
85
  ```text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-labs/video",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -7,6 +7,7 @@ import {
7
7
  posterUrl,
8
8
  premiumManifestUrl,
9
9
  } from '@growth-labs/video/utils'
10
+ import { buildPlayerBooleanAttrs } from './player-boolean-attrs'
10
11
 
11
12
  interface Props {
12
13
  videoId: string
@@ -48,6 +49,14 @@ const chaptersSrc = showChapters ? chaptersUrl(config.publicDomain, videoId) : n
48
49
  const captionsSrc = showCaptions
49
50
  ? captionsUrl(config.publicDomain, videoId, captionsLang)
50
51
  : null
52
+
53
+ // Custom elements treat attribute *presence* as truthy. Spread only the
54
+ // keys that resolve to true so SSR for `autoplay: false` / `muted: false`
55
+ // omits the attributes entirely — see player-boolean-attrs.ts.
56
+ const playerBoolAttrs = buildPlayerBooleanAttrs({
57
+ autoplay: Astro.props.autoplay ?? config.player.autoplay,
58
+ muted: Astro.props.muted ?? config.player.muted,
59
+ })
51
60
  ---
52
61
 
53
62
  <media-player
@@ -63,9 +72,8 @@ const captionsSrc = showCaptions
63
72
  src={premium ? undefined : src}
64
73
  poster={poster}
65
74
  preload={config.player.preload}
66
- autoplay={Astro.props.autoplay ?? config.player.autoplay}
67
- muted={Astro.props.muted ?? config.player.muted}
68
75
  class={`gl-video ${className ?? ''}`.trim()}
76
+ {...playerBoolAttrs}
69
77
  >
70
78
  <media-provider>
71
79
  {captionsSrc && (
@@ -79,7 +87,7 @@ const captionsSrc = showCaptions
79
87
  )}
80
88
  {chaptersSrc && <track src={chaptersSrc} kind="chapters" srclang="en" label="Chapters" default />}
81
89
  </media-provider>
82
- <media-video-layout></media-video-layout>
90
+ <slot name="layout"><media-video-layout></media-video-layout></slot>
83
91
  </media-player>
84
92
 
85
93
  <script>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Build the subset of boolean-presence attributes for a `<media-player>`
3
+ * element. Custom elements (including Vidstack's `media-player`) treat
4
+ * attribute *presence* as truthy regardless of the string value — so
5
+ * `autoplay="false"` is read as truthy by `hasAttribute('autoplay')` and
6
+ * triggers autoplay. Astro renders interpolated booleans as the literal
7
+ * strings `"true"` / `"false"` for custom elements (it has no allowlist
8
+ * of HTML-spec'd boolean attrs on non-HTMLElement tags), so we cannot
9
+ * write `autoplay={resolvedAutoplay}` directly. Instead we spread an
10
+ * attrs object that only contains the key when the value is truthy.
11
+ *
12
+ * The returned shape is `Record<string, true>` so a downstream
13
+ * `<media-player {...attrs}>` emits `autoplay` / `muted` as bare
14
+ * presence attributes when set, and emits nothing for them when not.
15
+ */
16
+ export function buildPlayerBooleanAttrs(input: {
17
+ autoplay: boolean
18
+ muted: boolean
19
+ }): Record<string, true> {
20
+ const attrs: Record<string, true> = {}
21
+ if (input.autoplay) attrs.autoplay = true
22
+ if (input.muted) attrs.muted = true
23
+ return attrs
24
+ }
@@ -1,16 +0,0 @@
1
- import type { APIContext } from 'astro';
2
- export interface AccessCheckParams {
3
- videoId: string;
4
- request: Request;
5
- env: Record<string, unknown>;
6
- }
7
- export interface AccessResult {
8
- allowed: boolean;
9
- reason?: string;
10
- subjectId?: string;
11
- }
12
- export type AccessCheck = (params: AccessCheckParams, context: APIContext) => Promise<AccessResult>;
13
- export declare function registerAccessCheck(check: AccessCheck): void;
14
- export declare function getAccessCheck(): AccessCheck | null;
15
- export declare function _resetAccessRegistry(): void;
16
- //# sourceMappingURL=access-registry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-registry.d.ts","sourceRoot":"","sources":["../src/access-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAEvC,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC5B;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;AAInG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAK5D;AAED,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAEnD;AAGD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
@@ -1,15 +0,0 @@
1
- let registered = null;
2
- export function registerAccessCheck(check) {
3
- if (registered) {
4
- console.warn('@growth-labs/video: access check replaced (previous registration overwritten)');
5
- }
6
- registered = check;
7
- }
8
- export function getAccessCheck() {
9
- return registered;
10
- }
11
- // Test-only reset. Not exported from the package's public surface.
12
- export function _resetAccessRegistry() {
13
- registered = null;
14
- }
15
- //# sourceMappingURL=access-registry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-registry.js","sourceRoot":"","sources":["../src/access-registry.ts"],"names":[],"mappings":"AAgBA,IAAI,UAAU,GAAuB,IAAI,CAAA;AAEzC,MAAM,UAAU,mBAAmB,CAAC,KAAkB;IACrD,IAAI,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,+EAA+E,CAAC,CAAA;IAC9F,CAAC;IACD,UAAU,GAAG,KAAK,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,cAAc;IAC7B,OAAO,UAAU,CAAA;AAClB,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,oBAAoB;IACnC,UAAU,GAAG,IAAI,CAAA;AAClB,CAAC"}