@growth-labs/video 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 (66) hide show
  1. package/dist/access-registry.d.ts +16 -0
  2. package/dist/access-registry.d.ts.map +1 -0
  3. package/dist/access-registry.js +15 -0
  4. package/dist/access-registry.js.map +1 -0
  5. package/dist/index.d.ts +19 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +44 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/options.d.ts +136 -0
  10. package/dist/options.d.ts.map +1 -0
  11. package/dist/options.js +53 -0
  12. package/dist/options.js.map +1 -0
  13. package/dist/routes/playback.d.ts +3 -0
  14. package/dist/routes/playback.d.ts.map +1 -0
  15. package/dist/routes/playback.js +83 -0
  16. package/dist/routes/playback.js.map +1 -0
  17. package/dist/routes/session.d.ts +3 -0
  18. package/dist/routes/session.d.ts.map +1 -0
  19. package/dist/routes/session.js +69 -0
  20. package/dist/routes/session.js.map +1 -0
  21. package/dist/types.d.ts +31 -0
  22. package/dist/types.d.ts.map +1 -0
  23. package/dist/types.js +2 -0
  24. package/dist/types.js.map +1 -0
  25. package/dist/utils/captions.d.ts +7 -0
  26. package/dist/utils/captions.d.ts.map +1 -0
  27. package/dist/utils/captions.js +66 -0
  28. package/dist/utils/captions.js.map +1 -0
  29. package/dist/utils/chapters.d.ts +7 -0
  30. package/dist/utils/chapters.d.ts.map +1 -0
  31. package/dist/utils/chapters.js +40 -0
  32. package/dist/utils/chapters.js.map +1 -0
  33. package/dist/utils/index.d.ts +8 -0
  34. package/dist/utils/index.d.ts.map +1 -0
  35. package/dist/utils/index.js +8 -0
  36. package/dist/utils/index.js.map +1 -0
  37. package/dist/utils/jsonld.d.ts +3 -0
  38. package/dist/utils/jsonld.d.ts.map +1 -0
  39. package/dist/utils/jsonld.js +42 -0
  40. package/dist/utils/jsonld.js.map +1 -0
  41. package/dist/utils/keys.d.ts +6 -0
  42. package/dist/utils/keys.d.ts.map +1 -0
  43. package/dist/utils/keys.js +16 -0
  44. package/dist/utils/keys.js.map +1 -0
  45. package/dist/utils/session.d.ts +10 -0
  46. package/dist/utils/session.d.ts.map +1 -0
  47. package/dist/utils/session.js +70 -0
  48. package/dist/utils/session.js.map +1 -0
  49. package/dist/utils/url.d.ts +7 -0
  50. package/dist/utils/url.d.ts.map +1 -0
  51. package/dist/utils/url.js +25 -0
  52. package/dist/utils/url.js.map +1 -0
  53. package/dist/utils/youtube.d.ts +12 -0
  54. package/dist/utils/youtube.d.ts.map +1 -0
  55. package/dist/utils/youtube.js +21 -0
  56. package/dist/utils/youtube.js.map +1 -0
  57. package/dist/vite-plugin.d.ts +4 -0
  58. package/dist/vite-plugin.d.ts.map +1 -0
  59. package/dist/vite-plugin.js +18 -0
  60. package/dist/vite-plugin.js.map +1 -0
  61. package/package.json +65 -0
  62. package/src/components/Video.astro +164 -0
  63. package/src/components/VideoEmbed.astro +109 -0
  64. package/src/components/VideoGallery.astro +36 -0
  65. package/src/components/VideoPreview.astro +70 -0
  66. package/src/components/index.ts +4 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chapters.d.ts","sourceRoot":"","sources":["../../src/utils/chapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,YAAY,EAAE,MAAM,aAAa,CAAA;AAExD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAwBnE;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;CACtB;AAED,wBAAsB,YAAY,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,mBAAwB,GAC5B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAU9B"}
@@ -0,0 +1,40 @@
1
+ export function parseChaptersJson(raw) {
2
+ if (!raw || typeof raw !== 'object')
3
+ return null;
4
+ const obj = raw;
5
+ if (!Array.isArray(obj.chapters))
6
+ return null;
7
+ const out = [];
8
+ for (const item of obj.chapters) {
9
+ if (!item || typeof item !== 'object')
10
+ return null;
11
+ const ch = item;
12
+ if (typeof ch.index !== 'number' ||
13
+ typeof ch.title !== 'string' ||
14
+ typeof ch.startSeconds !== 'number' ||
15
+ typeof ch.endSeconds !== 'number') {
16
+ return null;
17
+ }
18
+ out.push({
19
+ index: ch.index,
20
+ title: ch.title,
21
+ startSeconds: ch.startSeconds,
22
+ endSeconds: ch.endSeconds,
23
+ });
24
+ }
25
+ return { chapters: out };
26
+ }
27
+ export async function loadChapters(url, opts = {}) {
28
+ const fetcher = opts.fetcher ?? fetch;
29
+ try {
30
+ const res = await fetcher(url);
31
+ if (!res.ok)
32
+ return null;
33
+ const raw = (await res.json());
34
+ return parseChaptersJson(raw);
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ //# sourceMappingURL=chapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chapters.js","sourceRoot":"","sources":["../../src/utils/chapters.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC7C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAChD,MAAM,GAAG,GAAG,GAA8B,CAAA;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7C,MAAM,GAAG,GAAc,EAAE,CAAA;IACzB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QAClD,MAAM,EAAE,GAAG,IAA+B,CAAA;QAC1C,IACC,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ;YAC5B,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ;YAC5B,OAAO,EAAE,CAAC,YAAY,KAAK,QAAQ;YACnC,OAAO,EAAE,CAAC,UAAU,KAAK,QAAQ,EAChC,CAAC;YACF,OAAO,IAAI,CAAA;QACZ,CAAC;QACD,GAAG,CAAC,IAAI,CAAC;YACR,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,YAAY,EAAE,EAAE,CAAC,YAAY;YAC7B,UAAU,EAAE,EAAE,CAAC,UAAU;SACzB,CAAC,CAAA;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAA;AACzB,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,GAAW,EACX,OAA4B,EAAE;IAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAA;IACrC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QACxB,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAY,CAAA;QACzC,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;AACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ export * from './captions.js';
2
+ export * from './chapters.js';
3
+ export * from './jsonld.js';
4
+ export * from './keys.js';
5
+ export * from './session.js';
6
+ export * from './url.js';
7
+ export * from './youtube.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,cAAc,CAAA"}
@@ -0,0 +1,8 @@
1
+ export * from './captions.js';
2
+ export * from './chapters.js';
3
+ export * from './jsonld.js';
4
+ export * from './keys.js';
5
+ export * from './session.js';
6
+ export * from './url.js';
7
+ export * from './youtube.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,cAAc,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { VideoObjectInput } from '../types.js';
2
+ export declare function generateVideoObjectJsonLd(input: VideoObjectInput): object;
3
+ //# sourceMappingURL=jsonld.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld.d.ts","sourceRoot":"","sources":["../../src/utils/jsonld.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAenD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CA0BzE"}
@@ -0,0 +1,42 @@
1
+ import { posterUrl } from './url.js';
2
+ function toIsoDuration(seconds) {
3
+ const h = Math.floor(seconds / 3600);
4
+ const m = Math.floor((seconds % 3600) / 60);
5
+ const s = seconds % 60;
6
+ let out = 'PT';
7
+ if (h)
8
+ out += `${h}H`;
9
+ if (m)
10
+ out += `${m}M`;
11
+ if (s)
12
+ out += `${s}S`;
13
+ if (out === 'PT')
14
+ out = 'PT0S';
15
+ return out;
16
+ }
17
+ export function generateVideoObjectJsonLd(input) {
18
+ const thumbnailUrl = input.thumbnailUrls ?? [posterUrl(input.publicDomain, input.videoId)];
19
+ const ld = {
20
+ '@context': 'https://schema.org',
21
+ '@type': 'VideoObject',
22
+ name: input.title,
23
+ description: input.description,
24
+ uploadDate: input.uploadDate,
25
+ duration: toIsoDuration(input.durationSeconds),
26
+ thumbnailUrl,
27
+ embedUrl: input.embedUrl,
28
+ };
29
+ if (input.contentUrl)
30
+ ld.contentUrl = input.contentUrl;
31
+ if (input.transcript)
32
+ ld.transcript = input.transcript;
33
+ if (input.chapters && input.chapters.length > 0) {
34
+ ld.potentialAction = {
35
+ '@type': 'SeekToAction',
36
+ target: `${input.embedUrl}#t={seek_to_second_number}`,
37
+ 'startOffset-input': 'required name=seek_to_second_number',
38
+ };
39
+ }
40
+ return ld;
41
+ }
42
+ //# sourceMappingURL=jsonld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld.js","sourceRoot":"","sources":["../../src/utils/jsonld.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEpC,SAAS,aAAa,CAAC,OAAe;IACrC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IACpC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAC3C,MAAM,CAAC,GAAG,OAAO,GAAG,EAAE,CAAA;IACtB,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,IAAI,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAA;IACrB,IAAI,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAA;IACrB,IAAI,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAA;IACrB,IAAI,GAAG,KAAK,IAAI;QAAE,GAAG,GAAG,MAAM,CAAA;IAC9B,OAAO,GAAG,CAAA;AACX,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAAuB;IAChE,MAAM,YAAY,GAAG,KAAK,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IAE1F,MAAM,EAAE,GAA4B;QACnC,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,KAAK,CAAC,KAAK;QACjB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC;QAC9C,YAAY;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACxB,CAAA;IAED,IAAI,KAAK,CAAC,UAAU;QAAE,EAAE,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAA;IACtD,IAAI,KAAK,CAAC,UAAU;QAAE,EAAE,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAA;IAEtD,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjD,EAAE,CAAC,eAAe,GAAG;YACpB,OAAO,EAAE,cAAc;YACvB,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,4BAA4B;YACrD,mBAAmB,EAAE,qCAAqC;SAC1D,CAAA;IACF,CAAC;IAED,OAAO,EAAE,CAAA;AACV,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function videoManifestKey(videoId: string): string;
2
+ export declare function posterKey(videoId: string): string;
3
+ export declare function captionsKey(videoId: string, lang?: string): string;
4
+ export declare function chaptersKey(videoId: string): string;
5
+ export declare function hlsBundlePrefix(videoId: string): string;
6
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/utils/keys.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAa,GAAG,MAAM,CAExE;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -0,0 +1,16 @@
1
+ export function videoManifestKey(videoId) {
2
+ return `video/${videoId}/hls/master.m3u8`;
3
+ }
4
+ export function posterKey(videoId) {
5
+ return `video/${videoId}/poster.jpg`;
6
+ }
7
+ export function captionsKey(videoId, lang = 'en') {
8
+ return lang === 'en' ? `video/${videoId}/captions.vtt` : `video/${videoId}/captions-${lang}.vtt`;
9
+ }
10
+ export function chaptersKey(videoId) {
11
+ return `video/${videoId}/chapters.json`;
12
+ }
13
+ export function hlsBundlePrefix(videoId) {
14
+ return `video/${videoId}/hls/`;
15
+ }
16
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/utils/keys.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC/C,OAAO,SAAS,OAAO,kBAAkB,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACxC,OAAO,SAAS,OAAO,aAAa,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,OAAe,IAAI;IAC/D,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,OAAO,eAAe,CAAC,CAAC,CAAC,SAAS,OAAO,aAAa,IAAI,MAAM,CAAA;AACjG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IAC1C,OAAO,SAAS,OAAO,gBAAgB,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC9C,OAAO,SAAS,OAAO,OAAO,CAAA;AAC/B,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface SessionPayload {
2
+ videoIds: string[] | '*';
3
+ subjectId: string;
4
+ issuedAt: number;
5
+ expiresAt: number;
6
+ nonce: string;
7
+ }
8
+ export declare function issueSession(payload: Omit<SessionPayload, 'nonce'>, secret: string): Promise<string>;
9
+ export declare function verifySession(cookie: string, secret: string, requestedVideoId: string): Promise<SessionPayload | null>;
10
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/utils/session.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAG,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACb;AAiDD,wBAAsB,YAAY,CACjC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,EACtC,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,wBAAsB,aAAa,CAClC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0BhC"}
@@ -0,0 +1,70 @@
1
+ const encoder = new TextEncoder();
2
+ const decoder = new TextDecoder();
3
+ function toBase64Url(bytes) {
4
+ let bin = '';
5
+ for (let i = 0; i < bytes.length; i++)
6
+ bin += String.fromCharCode(bytes[i]);
7
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
8
+ }
9
+ function fromBase64Url(str) {
10
+ const padded = str.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (str.length % 4)) % 4);
11
+ const bin = atob(padded);
12
+ const bytes = new Uint8Array(bin.length);
13
+ for (let i = 0; i < bin.length; i++)
14
+ bytes[i] = bin.charCodeAt(i);
15
+ return bytes;
16
+ }
17
+ async function importKey(secret) {
18
+ return crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
19
+ }
20
+ async function sign(secret, data) {
21
+ const key = await importKey(secret);
22
+ const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
23
+ return toBase64Url(new Uint8Array(sig));
24
+ }
25
+ async function verify(secret, data, signatureB64) {
26
+ const key = await importKey(secret);
27
+ const sig = fromBase64Url(signatureB64);
28
+ return crypto.subtle.verify('HMAC', key, sig.buffer, encoder.encode(data));
29
+ }
30
+ function randomNonce() {
31
+ const bytes = new Uint8Array(16);
32
+ crypto.getRandomValues(bytes);
33
+ let hex = '';
34
+ for (let i = 0; i < bytes.length; i++)
35
+ hex += bytes[i].toString(16).padStart(2, '0');
36
+ return hex;
37
+ }
38
+ export async function issueSession(payload, secret) {
39
+ const full = { ...payload, nonce: randomNonce() };
40
+ const payloadB64 = toBase64Url(encoder.encode(JSON.stringify(full)));
41
+ const sig = await sign(secret, payloadB64);
42
+ return `${payloadB64}.${sig}`;
43
+ }
44
+ export async function verifySession(cookie, secret, requestedVideoId) {
45
+ const dot = cookie.indexOf('.');
46
+ if (dot === -1)
47
+ return null;
48
+ const payloadB64 = cookie.slice(0, dot);
49
+ const sig = cookie.slice(dot + 1);
50
+ if (!payloadB64 || !sig)
51
+ return null;
52
+ if (!(await verify(secret, payloadB64, sig)))
53
+ return null;
54
+ let payload;
55
+ try {
56
+ payload = JSON.parse(decoder.decode(fromBase64Url(payloadB64)));
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ const now = Math.floor(Date.now() / 1000);
62
+ if (payload.expiresAt <= now)
63
+ return null;
64
+ if (payload.videoIds !== '*' &&
65
+ (!Array.isArray(payload.videoIds) || !payload.videoIds.includes(requestedVideoId))) {
66
+ return null;
67
+ }
68
+ return payload;
69
+ }
70
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/utils/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function manifestUrl(publicDomain: string, videoId: string): string;
2
+ export declare function posterUrl(publicDomain: string, videoId: string): string;
3
+ export declare function captionsUrl(publicDomain: string, videoId: string, lang?: string): string;
4
+ export declare function chaptersUrl(publicDomain: string, videoId: string): string;
5
+ export declare function premiumManifestUrl(playbackRoute: string, videoId: string): string;
6
+ export declare function premiumPlaybackUrl(playbackRoute: string, videoId: string, subpath: string): string;
7
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAa,GAAG,MAAM,CAI9F;AAED,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAMD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,wBAAgB,kBAAkB,CACjC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACb,MAAM,CAGR"}
@@ -0,0 +1,25 @@
1
+ export function manifestUrl(publicDomain, videoId) {
2
+ return `https://${publicDomain}/video/${videoId}/hls/master.m3u8`;
3
+ }
4
+ export function posterUrl(publicDomain, videoId) {
5
+ return `https://${publicDomain}/cdn-cgi/image/width=1200,height=675,fit=cover,quality=80,format=auto/video/${videoId}/poster.jpg`;
6
+ }
7
+ export function captionsUrl(publicDomain, videoId, lang = 'en') {
8
+ return lang === 'en'
9
+ ? `https://${publicDomain}/video/${videoId}/captions.vtt`
10
+ : `https://${publicDomain}/video/${videoId}/captions-${lang}.vtt`;
11
+ }
12
+ export function chaptersUrl(publicDomain, videoId) {
13
+ return `https://${publicDomain}/video/${videoId}/chapters.json`;
14
+ }
15
+ function normalizeRoute(route) {
16
+ return route.endsWith('/') ? route.slice(0, -1) : route;
17
+ }
18
+ export function premiumManifestUrl(playbackRoute, videoId) {
19
+ return `${normalizeRoute(playbackRoute)}/${videoId}/hls/master.m3u8`;
20
+ }
21
+ export function premiumPlaybackUrl(playbackRoute, videoId, subpath) {
22
+ const trimmed = subpath.startsWith('/') ? subpath.slice(1) : subpath;
23
+ return `${normalizeRoute(playbackRoute)}/${videoId}/${trimmed}`;
24
+ }
25
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,YAAoB,EAAE,OAAe;IAChE,OAAO,WAAW,YAAY,UAAU,OAAO,kBAAkB,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,YAAoB,EAAE,OAAe;IAC9D,OAAO,WAAW,YAAY,+EAA+E,OAAO,aAAa,CAAA;AAClI,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,YAAoB,EAAE,OAAe,EAAE,OAAe,IAAI;IACrF,OAAO,IAAI,KAAK,IAAI;QACnB,CAAC,CAAC,WAAW,YAAY,UAAU,OAAO,eAAe;QACzD,CAAC,CAAC,WAAW,YAAY,UAAU,OAAO,aAAa,IAAI,MAAM,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,YAAoB,EAAE,OAAe;IAChE,OAAO,WAAW,YAAY,UAAU,OAAO,gBAAgB,CAAA;AAChE,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACpC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,aAAqB,EAAE,OAAe;IACxE,OAAO,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,OAAO,kBAAkB,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,kBAAkB,CACjC,aAAqB,EACrB,OAAe,EACf,OAAe;IAEf,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IACpE,OAAO,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,OAAO,IAAI,OAAO,EAAE,CAAA;AAChE,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface YouTubeEmbedOptions {
2
+ autoplay?: boolean;
3
+ start?: number;
4
+ end?: number;
5
+ muted?: boolean;
6
+ controls?: boolean;
7
+ privacyMode?: boolean;
8
+ }
9
+ export declare function youtubeEmbedUrl(videoId: string, opts?: YouTubeEmbedOptions): string;
10
+ export type YouTubeThumbnailQuality = 'default' | 'hq' | 'maxres';
11
+ export declare function youtubeThumbnailUrl(videoId: string, quality?: YouTubeThumbnailQuality): string;
12
+ //# sourceMappingURL=youtube.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"youtube.d.ts","sourceRoot":"","sources":["../../src/utils/youtube.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB,GAAG,MAAM,CAUvF;AAED,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,IAAI,GAAG,QAAQ,CAAA;AAEjE,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,uBAAkC,GACzC,MAAM,CAGR"}
@@ -0,0 +1,21 @@
1
+ export function youtubeEmbedUrl(videoId, opts = {}) {
2
+ const host = opts.privacyMode === false ? 'www.youtube.com' : 'www.youtube-nocookie.com';
3
+ const params = new URLSearchParams();
4
+ if (opts.autoplay)
5
+ params.set('autoplay', '1');
6
+ if (opts.muted)
7
+ params.set('mute', '1');
8
+ if (opts.controls === false)
9
+ params.set('controls', '0');
10
+ if (opts.start != null)
11
+ params.set('start', String(opts.start));
12
+ if (opts.end != null)
13
+ params.set('end', String(opts.end));
14
+ const qs = params.toString();
15
+ return `https://${host}/embed/${videoId}${qs ? `?${qs}` : ''}`;
16
+ }
17
+ export function youtubeThumbnailUrl(videoId, quality = 'maxres') {
18
+ const suffix = quality === 'maxres' ? 'maxresdefault' : quality === 'hq' ? 'hqdefault' : 'default';
19
+ return `https://i.ytimg.com/vi/${videoId}/${suffix}.jpg`;
20
+ }
21
+ //# sourceMappingURL=youtube.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"youtube.js","sourceRoot":"","sources":["../../src/utils/youtube.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,OAA4B,EAAE;IAC9E,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,0BAA0B,CAAA;IACxF,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;IACpC,IAAI,IAAI,CAAC,QAAQ;QAAE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;IAC9C,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;IACxD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAC/D,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACzD,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAA;IAC5B,OAAO,WAAW,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;AAC/D,CAAC;AAID,MAAM,UAAU,mBAAmB,CAClC,OAAe,EACf,UAAmC,QAAQ;IAE3C,MAAM,MAAM,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAA;IAClG,OAAO,0BAA0B,OAAO,IAAI,MAAM,MAAM,CAAA;AACzD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { ResolvedVideoOptions } from './options.js';
3
+ export declare function growthLabsVideoPlugin(config: ResolvedVideoOptions): Plugin;
4
+ //# sourceMappingURL=vite-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAKxD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAc1E"}
@@ -0,0 +1,18 @@
1
+ const VIRTUAL_MODULE_ID = 'virtual:growth-labs/video/config';
2
+ const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
3
+ export function growthLabsVideoPlugin(config) {
4
+ return {
5
+ name: 'growth-labs-video-config',
6
+ resolveId(id) {
7
+ if (id === VIRTUAL_MODULE_ID) {
8
+ return RESOLVED_VIRTUAL_MODULE_ID;
9
+ }
10
+ },
11
+ load(id) {
12
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
13
+ return `export const config = ${JSON.stringify(config)};`;
14
+ }
15
+ },
16
+ };
17
+ }
18
+ //# sourceMappingURL=vite-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAGA,MAAM,iBAAiB,GAAG,kCAAkC,CAAA;AAC5D,MAAM,0BAA0B,GAAG,KAAK,iBAAiB,EAAE,CAAA;AAE3D,MAAM,UAAU,qBAAqB,CAAC,MAA4B;IACjE,OAAO;QACN,IAAI,EAAE,0BAA0B;QAChC,SAAS,CAAC,EAAE;YACX,IAAI,EAAE,KAAK,iBAAiB,EAAE,CAAC;gBAC9B,OAAO,0BAA0B,CAAA;YAClC,CAAC;QACF,CAAC;QACD,IAAI,CAAC,EAAE;YACN,IAAI,EAAE,KAAK,0BAA0B,EAAE,CAAC;gBACvC,OAAO,yBAAyB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAA;YAC1D,CAAC;QACF,CAAC;KACD,CAAA;AACF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@growth-labs/video",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js"
9
+ },
10
+ "./components": {
11
+ "types": "./src/components/index.ts",
12
+ "import": "./src/components/index.ts"
13
+ },
14
+ "./components/*": {
15
+ "types": "./src/components/*.astro",
16
+ "import": "./src/components/*.astro"
17
+ },
18
+ "./utils": {
19
+ "types": "./dist/utils/index.d.ts",
20
+ "import": "./dist/utils/index.js"
21
+ },
22
+ "./utils/*": {
23
+ "types": "./dist/utils/*.d.ts",
24
+ "import": "./dist/utils/*.js"
25
+ },
26
+ "./routes/*": {
27
+ "types": "./dist/routes/*.d.ts",
28
+ "import": "./dist/routes/*.js",
29
+ "default": "./dist/routes/*.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "src/components"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "peerDependencies": {
41
+ "astro": "^6.0.0",
42
+ "vidstack": "^1.12.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "vidstack": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "zod": "^3.23.0"
51
+ },
52
+ "devDependencies": {
53
+ "@cloudflare/workers-types": "^4.20260402.1",
54
+ "astro": "^6.0.0",
55
+ "typescript": "^5.7.0",
56
+ "vite": "^7.0.0",
57
+ "vitest": "^3.0.0",
58
+ "vidstack": "^1.12.0"
59
+ },
60
+ "scripts": {
61
+ "build": "tsc",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest"
64
+ }
65
+ }
@@ -0,0 +1,164 @@
1
+ ---
2
+ import { config } from 'virtual:growth-labs/video/config'
3
+ import {
4
+ captionsUrl,
5
+ chaptersUrl,
6
+ manifestUrl,
7
+ posterUrl,
8
+ premiumManifestUrl,
9
+ } from '@growth-labs/video/utils'
10
+
11
+ interface Props {
12
+ videoId: string
13
+ title: string
14
+ premium?: boolean
15
+ poster?: string
16
+ captions?: boolean
17
+ chapters?: boolean
18
+ captionsLang?: string
19
+ autoplay?: boolean
20
+ muted?: boolean
21
+ class?: string
22
+ }
23
+
24
+ const {
25
+ videoId,
26
+ title,
27
+ premium = false,
28
+ captionsLang = 'en',
29
+ class: className,
30
+ } = Astro.props
31
+
32
+ const showCaptions = Astro.props.captions ?? config.player.showCaptions
33
+ const showChapters = Astro.props.chapters ?? config.player.showChapters
34
+
35
+ const src = premium
36
+ ? premiumManifestUrl(config.premium!.playbackRoute, videoId)
37
+ : manifestUrl(config.publicDomain, videoId)
38
+
39
+ const poster = Astro.props.poster ?? posterUrl(config.publicDomain, videoId)
40
+ const chaptersSrc = showChapters ? chaptersUrl(config.publicDomain, videoId) : null
41
+ const captionsSrc = showCaptions
42
+ ? captionsUrl(config.publicDomain, videoId, captionsLang)
43
+ : null
44
+ ---
45
+
46
+ <media-player
47
+ data-video-id={videoId}
48
+ data-premium={premium ? 'true' : 'false'}
49
+ data-session-route={premium ? config.premium?.sessionRoute : undefined}
50
+ data-chapters-src={chaptersSrc}
51
+ data-captions-src={captionsSrc}
52
+ data-captions-lang={captionsLang}
53
+ data-progress-marks={JSON.stringify(config.analytics.progressMarks)}
54
+ title={title}
55
+ src={src}
56
+ poster={poster}
57
+ preload={config.player.preload}
58
+ autoplay={Astro.props.autoplay ?? config.player.autoplay}
59
+ muted={Astro.props.muted ?? config.player.muted}
60
+ class={`gl-video ${className ?? ''}`.trim()}
61
+ >
62
+ <media-provider></media-provider>
63
+ <media-video-layout></media-video-layout>
64
+ </media-player>
65
+
66
+ <script>
67
+ import('vidstack/elements').then((mod: any) => {
68
+ if (typeof mod.defineCustomElements === 'function') {
69
+ mod.defineCustomElements()
70
+ }
71
+ })
72
+
73
+ const VIDEO_ACCESS_DENIED = 'gl:video-access-denied'
74
+
75
+ document.querySelectorAll<HTMLElement>('media-player[data-premium="true"]').forEach((player) => {
76
+ const videoId = player.dataset.videoId
77
+ const sessionRoute = player.dataset.sessionRoute
78
+ if (!videoId || !sessionRoute) return
79
+
80
+ let sessionChecked = false
81
+ const ensure = async () => {
82
+ if (sessionChecked) return
83
+ sessionChecked = true
84
+ try {
85
+ const res = await fetch(sessionRoute, {
86
+ method: 'POST',
87
+ credentials: 'same-origin',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ videoId }),
90
+ })
91
+ if (!res.ok) {
92
+ window.dispatchEvent(
93
+ new CustomEvent(VIDEO_ACCESS_DENIED, { detail: { videoId } }),
94
+ )
95
+ }
96
+ } catch {
97
+ window.dispatchEvent(
98
+ new CustomEvent(VIDEO_ACCESS_DENIED, { detail: { videoId } }),
99
+ )
100
+ }
101
+ }
102
+
103
+ player.addEventListener('pointerenter', ensure, { once: true })
104
+ player.addEventListener('play', ensure)
105
+ })
106
+
107
+ document.querySelectorAll<HTMLElement>('media-player[data-chapters-src]').forEach(async (player) => {
108
+ const src = player.dataset.chaptersSrc
109
+ if (!src) return
110
+ try {
111
+ const res = await fetch(src)
112
+ if (!res.ok) return
113
+ const data = (await res.json()) as { chapters: unknown }
114
+ ;(player as unknown as { chapters?: unknown }).chapters = data.chapters
115
+ } catch {
116
+ // silent
117
+ }
118
+ })
119
+
120
+ const emit = (name: string, detail: unknown) =>
121
+ window.dispatchEvent(new CustomEvent(name, { detail }))
122
+
123
+ document.querySelectorAll<HTMLElement>('media-player').forEach((player) => {
124
+ const videoId = player.dataset.videoId
125
+ if (!videoId) return
126
+ const marks: number[] = JSON.parse(player.dataset.progressMarks || '[]')
127
+ const fired = new Set<number>()
128
+
129
+ player.addEventListener('play', () => emit('gl:video-play', { videoId }))
130
+ player.addEventListener('ended', () =>
131
+ emit('gl:video-complete', {
132
+ videoId,
133
+ duration: (player as unknown as { duration?: number }).duration,
134
+ }),
135
+ )
136
+ player.addEventListener('error', (e: Event) =>
137
+ emit('gl:video-error', { videoId, error: String((e as ErrorEvent).message ?? '') }),
138
+ )
139
+ player.addEventListener('timeupdate', () => {
140
+ const p = player as unknown as { currentTime?: number; duration?: number }
141
+ if (!p.currentTime || !p.duration) return
142
+ const fraction = p.currentTime / p.duration
143
+ for (const mark of marks) {
144
+ if (fraction >= mark && !fired.has(mark)) {
145
+ fired.add(mark)
146
+ emit('gl:video-progress', {
147
+ videoId,
148
+ currentTime: p.currentTime,
149
+ duration: p.duration,
150
+ fraction: mark,
151
+ })
152
+ }
153
+ }
154
+ })
155
+ })
156
+ </script>
157
+
158
+ <style>
159
+ .gl-video {
160
+ width: 100%;
161
+ aspect-ratio: 16 / 9;
162
+ display: block;
163
+ }
164
+ </style>