@chunkify/analytics 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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -0
  3. package/dist/core.d.ts +144 -0
  4. package/dist/core.d.ts.map +1 -0
  5. package/dist/core.js +660 -0
  6. package/dist/core.js.map +1 -0
  7. package/dist/datadog.d.ts +8 -0
  8. package/dist/datadog.d.ts.map +1 -0
  9. package/dist/datadog.js +15 -0
  10. package/dist/datadog.js.map +1 -0
  11. package/dist/event-types.d.ts +3 -0
  12. package/dist/event-types.d.ts.map +1 -0
  13. package/dist/event-types.js +5 -0
  14. package/dist/event-types.js.map +1 -0
  15. package/dist/hlsjs.d.ts +10 -0
  16. package/dist/hlsjs.d.ts.map +1 -0
  17. package/dist/hlsjs.js +185 -0
  18. package/dist/hlsjs.js.map +1 -0
  19. package/dist/http.d.ts +4 -0
  20. package/dist/http.d.ts.map +1 -0
  21. package/dist/http.js +43 -0
  22. package/dist/http.js.map +1 -0
  23. package/dist/index.d.ts +8 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +7 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/mapper.d.ts +11 -0
  28. package/dist/mapper.d.ts.map +1 -0
  29. package/dist/mapper.js +11 -0
  30. package/dist/mapper.js.map +1 -0
  31. package/dist/monitor.d.ts +6 -0
  32. package/dist/monitor.d.ts.map +1 -0
  33. package/dist/monitor.js +14 -0
  34. package/dist/monitor.js.map +1 -0
  35. package/dist/options.d.ts +4 -0
  36. package/dist/options.d.ts.map +1 -0
  37. package/dist/options.js +26 -0
  38. package/dist/options.js.map +1 -0
  39. package/dist/otlp.d.ts +4 -0
  40. package/dist/otlp.d.ts.map +1 -0
  41. package/dist/otlp.js +56 -0
  42. package/dist/otlp.js.map +1 -0
  43. package/dist/player.d.ts +8 -0
  44. package/dist/player.d.ts.map +1 -0
  45. package/dist/player.js +93 -0
  46. package/dist/player.js.map +1 -0
  47. package/dist/posthog.d.ts +8 -0
  48. package/dist/posthog.d.ts.map +1 -0
  49. package/dist/posthog.js +15 -0
  50. package/dist/posthog.js.map +1 -0
  51. package/dist/shaka.d.ts +26 -0
  52. package/dist/shaka.d.ts.map +1 -0
  53. package/dist/shaka.js +164 -0
  54. package/dist/shaka.js.map +1 -0
  55. package/dist/videojs.d.ts +11 -0
  56. package/dist/videojs.d.ts.map +1 -0
  57. package/dist/videojs.js +77 -0
  58. package/dist/videojs.js.map +1 -0
  59. package/package.json +75 -0
  60. package/src/core.ts +774 -0
  61. package/src/datadog.ts +24 -0
  62. package/src/event-types.ts +6 -0
  63. package/src/hlsjs.ts +192 -0
  64. package/src/http.ts +43 -0
  65. package/src/index.ts +11 -0
  66. package/src/mapper.ts +22 -0
  67. package/src/monitor.ts +15 -0
  68. package/src/options.ts +28 -0
  69. package/src/otlp.ts +70 -0
  70. package/src/player.ts +96 -0
  71. package/src/posthog.ts +24 -0
  72. package/src/shaka.ts +181 -0
  73. package/src/videojs.ts +89 -0
@@ -0,0 +1,26 @@
1
+ import { analyticsEventTypes } from "./event-types.js";
2
+ const eventTypes = new Set(analyticsEventTypes);
3
+ /** Validate options before installing any media or engine listeners. */
4
+ export function assertAnalyticsOptions(options) {
5
+ if (typeof options.videoId !== "string" || !options.videoId.trim()) {
6
+ throw new TypeError("Analytics requires a non-empty videoId.");
7
+ }
8
+ if (!Array.isArray(options.destinations) || options.destinations.length === 0 ||
9
+ !Array.from(options.destinations).every((destination) => destination && typeof destination.send === "function")) {
10
+ throw new TypeError("Analytics requires a non-empty destinations array with send functions.");
11
+ }
12
+ if ((options.environment !== undefined && typeof options.environment !== "string") ||
13
+ (options.viewerId !== undefined && typeof options.viewerId !== "string")) {
14
+ throw new TypeError("Analytics environment and viewerId must be strings when provided.");
15
+ }
16
+ if (options.snapshotIntervalMs !== undefined &&
17
+ (!Number.isSafeInteger(options.snapshotIntervalMs) || options.snapshotIntervalMs < 1000 ||
18
+ options.snapshotIntervalMs > 2_147_483_647)) {
19
+ throw new TypeError("Analytics snapshotIntervalMs must be an integer from 1000 to 2147483647 ms.");
20
+ }
21
+ if (options.includeEvents !== undefined &&
22
+ (!Array.isArray(options.includeEvents) || !options.includeEvents.every((event) => eventTypes.has(event)))) {
23
+ throw new TypeError("Analytics includeEvents must be an array of supported event types.");
24
+ }
25
+ }
26
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS,mBAAmB,CAAC,CAAC;AAExD,wEAAwE;AACxE,MAAM,UAAU,sBAAsB,CAAC,OAAyB;IAC9D,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACnE,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;QACzE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,IAAI,OAAO,WAAW,CAAC,IAAI,KAAK,UAAU,CAAC,EAAE,CAAC;QACpH,MAAM,IAAI,SAAS,CAAC,wEAAwE,CAAC,CAAC;IAChG,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC;QAC9E,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,SAAS,CAAC,mEAAmE,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,OAAO,CAAC,kBAAkB,KAAK,SAAS;QACxC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI;YACtF,OAAO,CAAC,kBAAkB,GAAG,aAAa,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,SAAS,CAAC,6EAA6E,CAAC,CAAC;IACrG,CAAC;IACD,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS;QACnC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9G,MAAM,IAAI,SAAS,CAAC,oEAAoE,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC","sourcesContent":["import type { AnalyticsOptions } from \"./core.js\";\nimport { analyticsEventTypes } from \"./event-types.js\";\n\nconst eventTypes = new Set<string>(analyticsEventTypes);\n\n/** Validate options before installing any media or engine listeners. */\nexport function assertAnalyticsOptions(options: AnalyticsOptions): void {\n if (typeof options.videoId !== \"string\" || !options.videoId.trim()) {\n throw new TypeError(\"Analytics requires a non-empty videoId.\");\n }\n if (!Array.isArray(options.destinations) || options.destinations.length === 0 ||\n !Array.from(options.destinations).every((destination) => destination && typeof destination.send === \"function\")) {\n throw new TypeError(\"Analytics requires a non-empty destinations array with send functions.\");\n }\n if ((options.environment !== undefined && typeof options.environment !== \"string\") ||\n (options.viewerId !== undefined && typeof options.viewerId !== \"string\")) {\n throw new TypeError(\"Analytics environment and viewerId must be strings when provided.\");\n }\n if (options.snapshotIntervalMs !== undefined &&\n (!Number.isSafeInteger(options.snapshotIntervalMs) || options.snapshotIntervalMs < 1000 ||\n options.snapshotIntervalMs > 2_147_483_647)) {\n throw new TypeError(\"Analytics snapshotIntervalMs must be an integer from 1000 to 2147483647 ms.\");\n }\n if (options.includeEvents !== undefined &&\n (!Array.isArray(options.includeEvents) || !options.includeEvents.every((event) => eventTypes.has(event)))) {\n throw new TypeError(\"Analytics includeEvents must be an array of supported event types.\");\n }\n}\n"]}
package/dist/otlp.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { AnalyticsDestination, ChunkifyPlayerAnalyticsEvent } from "./core.js";
2
+ /** Send each analytics event as an OTLP/HTTP JSON log record to a customer gateway. */
3
+ export declare function createOtlpAdapter(endpoint: string, mapAttributes?: (event: ChunkifyPlayerAnalyticsEvent) => object): AnalyticsDestination;
4
+ //# sourceMappingURL=otlp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AAwDpF,uFAAuF;AACvF,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,aAAa,GAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,MAAyB,GAChF,oBAAoB,CAStB"}
package/dist/otlp.js ADDED
@@ -0,0 +1,56 @@
1
+ import { createHttpAdapter } from "./http.js";
2
+ function toOtlpValue(value) {
3
+ if (value == null)
4
+ return null;
5
+ if (typeof value === "string")
6
+ return { stringValue: value };
7
+ if (typeof value === "boolean")
8
+ return { boolValue: value };
9
+ if (typeof value === "number") {
10
+ if (!Number.isFinite(value))
11
+ return null;
12
+ return Number.isSafeInteger(value) ? { intValue: String(value) } : { doubleValue: value };
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return { arrayValue: { values: value.map(toOtlpValue).filter((item) => item !== null) } };
16
+ }
17
+ if (typeof value === "object")
18
+ return { kvlistValue: { values: toOtlpAttributes(value) } };
19
+ return null;
20
+ }
21
+ function toOtlpAttributes(record) {
22
+ return Object.entries(record).flatMap(([key, value]) => {
23
+ const encoded = toOtlpValue(value);
24
+ return encoded === null ? [] : [{ key, value: encoded }];
25
+ });
26
+ }
27
+ function toOtlpLogRequest(event, attributes) {
28
+ const occurredAtMs = Date.parse(event.occurred_at);
29
+ if (!Number.isFinite(occurredAtMs))
30
+ throw new TypeError("OTLP adapter requires a valid occurred_at timestamp.");
31
+ return {
32
+ resourceLogs: [{
33
+ scopeLogs: [{
34
+ scope: { name: "@chunkify/analytics", version: event.sdk_version },
35
+ logRecords: [{
36
+ timeUnixNano: String(BigInt(occurredAtMs) * 1000000n),
37
+ eventName: event.event_name,
38
+ attributes: toOtlpAttributes(attributes),
39
+ }],
40
+ }],
41
+ }],
42
+ };
43
+ }
44
+ /** Send each analytics event as an OTLP/HTTP JSON log record to a customer gateway. */
45
+ export function createOtlpAdapter(endpoint, mapAttributes = (event) => event) {
46
+ if (typeof mapAttributes !== "function")
47
+ throw new TypeError("OTLP attribute mapper must be a function.");
48
+ return createHttpAdapter(endpoint, (event) => {
49
+ const attributes = mapAttributes(event);
50
+ if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) {
51
+ throw new TypeError("OTLP attribute mapper must return an object.");
52
+ }
53
+ return toOtlpLogRequest(event, attributes);
54
+ });
55
+ }
56
+ //# sourceMappingURL=otlp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnalyticsDestination, ChunkifyPlayerAnalyticsEvent } from \"./core.js\";\nimport { createHttpAdapter } from \"./http.js\";\n\ntype OtlpValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { intValue: string }\n | { doubleValue: number }\n | { arrayValue: { values: OtlpValue[] } }\n | { kvlistValue: { values: OtlpAttribute[] } };\n\ninterface OtlpAttribute {\n key: string;\n value: OtlpValue;\n}\n\nfunction toOtlpValue(value: unknown): OtlpValue | null {\n if (value == null) return null;\n if (typeof value === \"string\") return { stringValue: value };\n if (typeof value === \"boolean\") return { boolValue: value };\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) return null;\n return Number.isSafeInteger(value) ? { intValue: String(value) } : { doubleValue: value };\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map(toOtlpValue).filter((item): item is OtlpValue => item !== null) } };\n }\n if (typeof value === \"object\") return { kvlistValue: { values: toOtlpAttributes(value) } };\n return null;\n}\n\nfunction toOtlpAttributes(record: object): OtlpAttribute[] {\n return Object.entries(record).flatMap(([key, value]) => {\n const encoded = toOtlpValue(value);\n return encoded === null ? [] : [{ key, value: encoded }];\n });\n}\n\nfunction toOtlpLogRequest(event: ChunkifyPlayerAnalyticsEvent, attributes: object) {\n const occurredAtMs = Date.parse(event.occurred_at);\n if (!Number.isFinite(occurredAtMs)) throw new TypeError(\"OTLP adapter requires a valid occurred_at timestamp.\");\n\n return {\n resourceLogs: [{\n scopeLogs: [{\n scope: { name: \"@chunkify/analytics\", version: event.sdk_version },\n logRecords: [{\n timeUnixNano: String(BigInt(occurredAtMs) * 1_000_000n),\n eventName: event.event_name,\n attributes: toOtlpAttributes(attributes),\n }],\n }],\n }],\n };\n}\n\n/** Send each analytics event as an OTLP/HTTP JSON log record to a customer gateway. */\nexport function createOtlpAdapter(\n endpoint: string,\n mapAttributes: (event: ChunkifyPlayerAnalyticsEvent) => object = (event) => event,\n): AnalyticsDestination {\n if (typeof mapAttributes !== \"function\") throw new TypeError(\"OTLP attribute mapper must be a function.\");\n return createHttpAdapter(endpoint, (event) => {\n const attributes = mapAttributes(event);\n if (!attributes || typeof attributes !== \"object\" || Array.isArray(attributes)) {\n throw new TypeError(\"OTLP attribute mapper must return an object.\");\n }\n return toOtlpLogRequest(event, attributes);\n });\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { type AnalyticsOptions, type AnalyticsSession } from "./core.js";
2
+ /** The public surface needed from a player that can replace its media element. */
3
+ export interface MediaPlayerHost extends HTMLElement {
4
+ readonly media: HTMLMediaElement;
5
+ }
6
+ /** Attach the standalone media collector to a Chunkify Player. */
7
+ export declare function attachPlayerAnalytics(player: MediaPlayerHost, options: AnalyticsOptions): Pick<AnalyticsSession, "startAttempt" | "sourceChanged" | "destroy">;
8
+ //# sourceMappingURL=player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAI1F,kFAAkF;AAClF,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;CAClC;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAAC,gBAAgB,EAAE,cAAc,GAAG,eAAe,GAAG,SAAS,CAAC,CAkFtE"}
package/dist/player.js ADDED
@@ -0,0 +1,93 @@
1
+ import { attachAnalytics } from "./core.js";
2
+ import { createHlsJsConnector } from "./hlsjs.js";
3
+ /** Attach the standalone media collector to a Chunkify Player. */
4
+ export function attachPlayerAnalytics(player, options) {
5
+ let media = player.media;
6
+ let session = attachAnalytics(media, options);
7
+ let destroyed = false;
8
+ let activeHls = null;
9
+ let connector = null;
10
+ let checkTimer = null;
11
+ const clearCheckTimer = () => {
12
+ if (checkTimer === null)
13
+ return;
14
+ clearTimeout(checkTimer);
15
+ checkTimer = null;
16
+ };
17
+ const detachConnector = () => {
18
+ session.setConnector(null);
19
+ connector?.destroy();
20
+ connector = null;
21
+ activeHls = null;
22
+ };
23
+ const syncConnector = () => {
24
+ if (destroyed)
25
+ return;
26
+ const hls = media.localName === "hls-video"
27
+ ? media.api ?? null : null;
28
+ if (hls === activeHls)
29
+ return;
30
+ detachConnector();
31
+ if (!hls)
32
+ return;
33
+ try {
34
+ const next = createHlsJsConnector(hls);
35
+ session.setConnector(next);
36
+ connector = next;
37
+ activeHls = hls;
38
+ }
39
+ catch {
40
+ // Analytics must not interrupt playback if an engine is unavailable.
41
+ }
42
+ };
43
+ const scheduleConnectorCheck = () => {
44
+ clearCheckTimer();
45
+ checkTimer = globalThis.setTimeout(() => {
46
+ checkTimer = null;
47
+ syncConnector();
48
+ }, 0);
49
+ };
50
+ const observer = new MutationObserver(scheduleConnectorCheck);
51
+ const bindMedia = () => {
52
+ media.addEventListener("loadstart", syncConnector);
53
+ observer.observe(media, { attributes: true, attributeFilter: ["src"] });
54
+ syncConnector();
55
+ // hls-video-element creates its hls.js instance after setting src.
56
+ scheduleConnectorCheck();
57
+ };
58
+ const unbindMedia = () => {
59
+ clearCheckTimer();
60
+ observer.disconnect();
61
+ media.removeEventListener("loadstart", syncConnector);
62
+ };
63
+ bindMedia();
64
+ const onMediaChange = () => {
65
+ const next = player.media;
66
+ if (destroyed || next === media)
67
+ return;
68
+ session.sourceChanged();
69
+ unbindMedia();
70
+ session.destroy();
71
+ detachConnector();
72
+ media = next;
73
+ session = attachAnalytics(media, options);
74
+ bindMedia();
75
+ };
76
+ player.addEventListener("mediachange", onMediaChange);
77
+ return {
78
+ startAttempt() { if (!destroyed)
79
+ session.startAttempt(); },
80
+ sourceChanged() { if (!destroyed)
81
+ session.sourceChanged(); },
82
+ destroy() {
83
+ if (destroyed)
84
+ return;
85
+ destroyed = true;
86
+ player.removeEventListener("mediachange", onMediaChange);
87
+ unbindMedia();
88
+ session.destroy();
89
+ detachConnector();
90
+ },
91
+ };
92
+ }
93
+ //# sourceMappingURL=player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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","sourcesContent":["import { attachAnalytics, type AnalyticsOptions, type AnalyticsSession } from \"./core.js\";\nimport { createHlsJsConnector, type HlsJsAnalyticsConnector } from \"./hlsjs.js\";\nimport type Hls from \"hls.js\";\n\n/** The public surface needed from a player that can replace its media element. */\nexport interface MediaPlayerHost extends HTMLElement {\n readonly media: HTMLMediaElement;\n}\n\n/** Attach the standalone media collector to a Chunkify Player. */\nexport function attachPlayerAnalytics(\n player: MediaPlayerHost,\n options: AnalyticsOptions,\n): Pick<AnalyticsSession, \"startAttempt\" | \"sourceChanged\" | \"destroy\"> {\n let media = player.media;\n let session = attachAnalytics(media, options);\n let destroyed = false;\n let activeHls: Hls | null = null;\n let connector: HlsJsAnalyticsConnector | null = null;\n let checkTimer: ReturnType<typeof setTimeout> | null = null;\n\n const clearCheckTimer = () => {\n if (checkTimer === null) return;\n clearTimeout(checkTimer);\n checkTimer = null;\n };\n const detachConnector = () => {\n session.setConnector(null);\n connector?.destroy();\n connector = null;\n activeHls = null;\n };\n const syncConnector = () => {\n if (destroyed) return;\n const hls = media.localName === \"hls-video\"\n ? (media as HTMLMediaElement & { api?: Hls | null }).api ?? null : null;\n if (hls === activeHls) return;\n detachConnector();\n if (!hls) return;\n try {\n const next = createHlsJsConnector(hls);\n session.setConnector(next);\n connector = next;\n activeHls = hls;\n } catch {\n // Analytics must not interrupt playback if an engine is unavailable.\n }\n };\n const scheduleConnectorCheck = () => {\n clearCheckTimer();\n checkTimer = globalThis.setTimeout(() => {\n checkTimer = null;\n syncConnector();\n }, 0);\n };\n const observer = new MutationObserver(scheduleConnectorCheck);\n const bindMedia = () => {\n media.addEventListener(\"loadstart\", syncConnector);\n observer.observe(media, { attributes: true, attributeFilter: [\"src\"] });\n syncConnector();\n // hls-video-element creates its hls.js instance after setting src.\n scheduleConnectorCheck();\n };\n const unbindMedia = () => {\n clearCheckTimer();\n observer.disconnect();\n media.removeEventListener(\"loadstart\", syncConnector);\n };\n bindMedia();\n\n const onMediaChange = () => {\n const next = player.media;\n if (destroyed || next === media) return;\n session.sourceChanged();\n unbindMedia();\n session.destroy();\n detachConnector();\n media = next;\n session = attachAnalytics(media, options);\n bindMedia();\n };\n player.addEventListener(\"mediachange\", onMediaChange);\n\n return {\n startAttempt() { if (!destroyed) session.startAttempt(); },\n sourceChanged() { if (!destroyed) session.sourceChanged(); },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n player.removeEventListener(\"mediachange\", onMediaChange);\n unbindMedia();\n session.destroy();\n detachConnector();\n },\n };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { AnalyticsDestination, ChunkifyPlayerAnalyticsEvent } from "./core.js";
2
+ /** The small part of an initialized PostHog browser client needed by this adapter. */
3
+ export interface PostHogCompatibleClient {
4
+ capture(name: string, properties: Record<string, unknown>): unknown;
5
+ }
6
+ /** Send one PostHog event for each analytics event. */
7
+ export declare function createPostHogAdapter(client: PostHogCompatibleClient, mapEvent?: (event: ChunkifyPlayerAnalyticsEvent) => Record<string, unknown>): AnalyticsDestination;
8
+ //# sourceMappingURL=posthog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posthog.d.ts","sourceRoot":"","sources":["../src/posthog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AAEpF,sFAAsF;AACtF,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CACrE;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,GAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAA6B,GACrG,oBAAoB,CAYtB"}
@@ -0,0 +1,15 @@
1
+ /** Send one PostHog event for each analytics event. */
2
+ export function createPostHogAdapter(client, mapEvent = (event) => ({ ...event })) {
3
+ if (!client || typeof client.capture !== "function") {
4
+ throw new TypeError("PostHog adapter requires an initialized client with capture().");
5
+ }
6
+ if (typeof mapEvent !== "function") {
7
+ throw new TypeError("PostHog event mapper must be a function.");
8
+ }
9
+ return {
10
+ send(event) {
11
+ client.capture(event.event_name, mapEvent(event));
12
+ },
13
+ };
14
+ }
15
+ //# sourceMappingURL=posthog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posthog.js","sourceRoot":"","sources":["../src/posthog.ts"],"names":[],"mappings":"AAOA,uDAAuD;AACvD,MAAM,UAAU,oBAAoB,CAClC,MAA+B,EAC/B,QAAQ,GAAqE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;IAEtG,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACpD,MAAM,IAAI,SAAS,CAAC,gEAAgE,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CAAC,0CAA0C,CAAC,CAAC;IAClE,CAAC;IACD,OAAO;QACL,IAAI,CAAC,KAAmC;YACtC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { AnalyticsDestination, ChunkifyPlayerAnalyticsEvent } from \"./core.js\";\n\n/** The small part of an initialized PostHog browser client needed by this adapter. */\nexport interface PostHogCompatibleClient {\n capture(name: string, properties: Record<string, unknown>): unknown;\n}\n\n/** Send one PostHog event for each analytics event. */\nexport function createPostHogAdapter(\n client: PostHogCompatibleClient,\n mapEvent: (event: ChunkifyPlayerAnalyticsEvent) => Record<string, unknown> = (event) => ({ ...event }),\n): AnalyticsDestination {\n if (!client || typeof client.capture !== \"function\") {\n throw new TypeError(\"PostHog adapter requires an initialized client with capture().\");\n }\n if (typeof mapEvent !== \"function\") {\n throw new TypeError(\"PostHog event mapper must be a function.\");\n }\n return {\n send(event: ChunkifyPlayerAnalyticsEvent) {\n client.capture(event.event_name, mapEvent(event));\n },\n };\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { type AnalyticsConnector, type AnalyticsOptions, type AnalyticsSession } from "./core.js";
2
+ /** The public Shaka Player APIs used by analytics. */
3
+ export interface ShakaPlayer {
4
+ getMediaElement(): HTMLMediaElement | null;
5
+ getConfiguration(): {
6
+ streaming: {
7
+ observeQualityChanges: boolean;
8
+ };
9
+ };
10
+ configure(config: {
11
+ streaming: {
12
+ observeQualityChanges: boolean;
13
+ };
14
+ }): boolean;
15
+ addEventListener(type: string, listener: (event: Event) => void): void;
16
+ removeEventListener(type: string, listener: (event: Event) => void): void;
17
+ }
18
+ export interface ShakaAnalyticsConnector extends AnalyticsConnector {
19
+ reset(): void;
20
+ destroy(): void;
21
+ }
22
+ /** Observe Shaka's quality at the playhead, completed downloads, and critical errors. */
23
+ export declare function createShakaConnector(player: ShakaPlayer): ShakaAnalyticsConnector;
24
+ /** Monitor an attached Shaka Player before loading and playing content. */
25
+ export declare function monitorShaka(player: ShakaPlayer, options: AnalyticsOptions): Pick<AnalyticsSession, "startAttempt" | "sourceChanged" | "destroy">;
26
+ //# sourceMappingURL=shaka.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shaka.d.ts","sourceRoot":"","sources":["../src/shaka.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,kBAAkB,EAC/B,KAAK,gBAAgB,EACtD,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAkB3C,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,eAAe,IAAI,gBAAgB,GAAG,IAAI,CAAC;IAC3C,gBAAgB,IAAI;QAAE,SAAS,EAAE;YAAE,qBAAqB,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC;IACtE,SAAS,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE;YAAE,qBAAqB,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,GAAG,OAAO,CAAC;IAC9E,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IACvE,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,uBAAuB,CAgGjF;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,GACzE,IAAI,CAAC,gBAAgB,EAAE,cAAc,GAAG,eAAe,GAAG,SAAS,CAAC,CAyCrE"}
package/dist/shaka.js ADDED
@@ -0,0 +1,164 @@
1
+ import { attachAnalyticsWithManagedSource } from "./core.js";
2
+ import { assertAnalyticsOptions } from "./options.js";
3
+ function positive(value) {
4
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
5
+ }
6
+ /** Observe Shaka's quality at the playhead, completed downloads, and critical errors. */
7
+ export function createShakaConnector(player) {
8
+ const errorListeners = new Set();
9
+ const renditionListeners = new Set();
10
+ let rendition = null;
11
+ let switches = 0;
12
+ let requests = 0;
13
+ let bytes = 0;
14
+ let durationMs = 0;
15
+ let bytesKnown = true;
16
+ let durationKnown = true;
17
+ let destroyed = false;
18
+ const version = player.constructor.version;
19
+ const playerVersion = typeof version === "string" ? version : null;
20
+ const onQuality = (event) => {
21
+ const quality = event.mediaQuality;
22
+ if (quality?.contentType !== "video")
23
+ return;
24
+ const next = {
25
+ width: positive(quality.width), height: positive(quality.height),
26
+ codec: quality.codecs || null, bitrate_bps: positive(quality.bandwidth),
27
+ frame_rate: positive(quality.frameRate),
28
+ };
29
+ if (rendition && (next.width !== rendition.width || next.height !== rendition.height ||
30
+ next.codec !== rendition.codec || next.bitrate_bps !== rendition.bitrate_bps ||
31
+ next.frame_rate !== rendition.frame_rate))
32
+ switches++;
33
+ rendition = next;
34
+ for (const listener of renditionListeners)
35
+ listener(rendition);
36
+ };
37
+ const onError = (event) => {
38
+ const detail = event.detail;
39
+ if (detail?.severity !== 2)
40
+ return;
41
+ const category = detail.category === 1 ? "network"
42
+ : detail.category === 3 ? "decode" : "unknown";
43
+ for (const listener of errorListeners)
44
+ listener({
45
+ category, code: Number.isFinite(detail.code) ? detail.code : null, fatal: true,
46
+ });
47
+ };
48
+ const onDownload = (event) => {
49
+ const response = event.response;
50
+ if (response?.fromCache)
51
+ return;
52
+ requests++;
53
+ const size = response?.data?.byteLength;
54
+ if (typeof size === "number" && Number.isFinite(size) && size >= 0)
55
+ bytes += size;
56
+ else
57
+ bytesKnown = false;
58
+ const duration = response?.timeMs;
59
+ if (typeof duration === "number" && Number.isFinite(duration) && duration >= 0)
60
+ durationMs += duration;
61
+ else
62
+ durationKnown = false;
63
+ };
64
+ player.addEventListener("mediaqualitychanged", onQuality);
65
+ player.addEventListener("error", onError);
66
+ player.addEventListener("downloadcompleted", onDownload);
67
+ return {
68
+ name: "shaka-player", version: playerVersion,
69
+ playerName: "shaka-player", playerVersion,
70
+ snapshot() {
71
+ return {
72
+ renditionWidth: rendition?.width ?? null,
73
+ renditionHeight: rendition?.height ?? null,
74
+ renditionCodec: rendition?.codec ?? null,
75
+ renditionBitrate: rendition?.bitrate_bps ?? null,
76
+ renditionSwitchCount: switches,
77
+ deliveryRequestCount: requests,
78
+ deliveryTransferredBytes: bytesKnown ? bytes : null,
79
+ deliveryDurationMs: durationKnown ? durationMs : null,
80
+ };
81
+ },
82
+ subscribeToErrors(listener) {
83
+ errorListeners.add(listener);
84
+ return () => { errorListeners.delete(listener); };
85
+ },
86
+ subscribeToRenditions(listener) {
87
+ renditionListeners.add(listener);
88
+ listener(rendition);
89
+ return () => { renditionListeners.delete(listener); };
90
+ },
91
+ reset() {
92
+ rendition = null;
93
+ switches = 0;
94
+ requests = 0;
95
+ bytes = 0;
96
+ durationMs = 0;
97
+ bytesKnown = true;
98
+ durationKnown = true;
99
+ for (const listener of renditionListeners)
100
+ listener(null);
101
+ },
102
+ destroy() {
103
+ if (destroyed)
104
+ return;
105
+ destroyed = true;
106
+ player.removeEventListener("mediaqualitychanged", onQuality);
107
+ player.removeEventListener("error", onError);
108
+ player.removeEventListener("downloadcompleted", onDownload);
109
+ errorListeners.clear();
110
+ renditionListeners.clear();
111
+ },
112
+ };
113
+ }
114
+ /** Monitor an attached Shaka Player before loading and playing content. */
115
+ export function monitorShaka(player, options) {
116
+ assertAnalyticsOptions(options);
117
+ const media = player.getMediaElement();
118
+ const Media = media?.ownerDocument?.defaultView?.HTMLMediaElement;
119
+ if (!Media || !(media instanceof Media)) {
120
+ throw new TypeError("Shaka analytics requires a player attached to an HTML media element.");
121
+ }
122
+ // Shaka only emits quality changes at the playhead when this observer is enabled.
123
+ const observerWasEnabled = player.getConfiguration().streaming.observeQualityChanges;
124
+ if (!observerWasEnabled) {
125
+ player.configure({ streaming: { observeQualityChanges: true } });
126
+ }
127
+ const restoreObserver = !observerWasEnabled && player.getConfiguration().streaming.observeQualityChanges;
128
+ const session = attachAnalyticsWithManagedSource(media, options);
129
+ const connector = createShakaConnector(player);
130
+ session.setConnector(connector);
131
+ let destroyed = false;
132
+ const sourceChanged = () => {
133
+ if (destroyed)
134
+ return;
135
+ session.sourceChanged();
136
+ connector.reset();
137
+ };
138
+ const onLoading = () => { if (!destroyed)
139
+ session.startAttempt(); };
140
+ const onUnloading = () => sourceChanged();
141
+ player.addEventListener("loading", onLoading);
142
+ player.addEventListener("unloading", onUnloading);
143
+ return {
144
+ startAttempt() { if (!destroyed)
145
+ session.startAttempt(); },
146
+ sourceChanged,
147
+ destroy() {
148
+ if (destroyed)
149
+ return;
150
+ destroyed = true;
151
+ player.removeEventListener("loading", onLoading);
152
+ player.removeEventListener("unloading", onUnloading);
153
+ session.destroy();
154
+ connector.destroy();
155
+ if (restoreObserver) {
156
+ try {
157
+ player.configure({ streaming: { observeQualityChanges: false } });
158
+ }
159
+ catch { }
160
+ }
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=shaka.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shaka.js","sourceRoot":"","sources":["../src/shaka.ts"],"names":[],"mappings":"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","sourcesContent":["import { attachAnalyticsWithManagedSource, type AnalyticsConnector, type AnalyticsConnectorError,\n type AnalyticsConnectorSnapshot, type AnalyticsOptions, type AnalyticsRendition,\n type AnalyticsSession } from \"./core.js\";\nimport { assertAnalyticsOptions } from \"./options.js\";\n\ninterface ShakaMediaQuality {\n contentType?: string;\n width?: number | null;\n height?: number | null;\n codecs?: string | null;\n bandwidth?: number | null;\n frameRate?: number | null;\n}\n\ninterface ShakaEvent extends Event {\n mediaQuality?: ShakaMediaQuality;\n detail?: { severity?: number; category?: number; code?: number };\n response?: { data?: ArrayBuffer | ArrayBufferView; timeMs?: number; fromCache?: boolean };\n}\n\n/** The public Shaka Player APIs used by analytics. */\nexport interface ShakaPlayer {\n getMediaElement(): HTMLMediaElement | null;\n getConfiguration(): { streaming: { observeQualityChanges: boolean } };\n configure(config: { streaming: { observeQualityChanges: boolean } }): boolean;\n addEventListener(type: string, listener: (event: Event) => void): void;\n removeEventListener(type: string, listener: (event: Event) => void): void;\n}\n\nexport interface ShakaAnalyticsConnector extends AnalyticsConnector {\n reset(): void;\n destroy(): void;\n}\n\nfunction positive(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : null;\n}\n\n/** Observe Shaka's quality at the playhead, completed downloads, and critical errors. */\nexport function createShakaConnector(player: ShakaPlayer): ShakaAnalyticsConnector {\n const errorListeners = new Set<(error: AnalyticsConnectorError) => void>();\n const renditionListeners = new Set<(rendition: AnalyticsRendition | null) => void>();\n let rendition: AnalyticsRendition | null = null;\n let switches = 0;\n let requests = 0;\n let bytes = 0;\n let durationMs = 0;\n let bytesKnown = true;\n let durationKnown = true;\n let destroyed = false;\n const version = (player.constructor as { version?: unknown }).version;\n const playerVersion = typeof version === \"string\" ? version : null;\n\n const onQuality = (event: Event) => {\n const quality = (event as ShakaEvent).mediaQuality;\n if (quality?.contentType !== \"video\") return;\n const next: AnalyticsRendition = {\n width: positive(quality.width), height: positive(quality.height),\n codec: quality.codecs || null, bitrate_bps: positive(quality.bandwidth),\n frame_rate: positive(quality.frameRate),\n };\n if (rendition && (next.width !== rendition.width || next.height !== rendition.height ||\n next.codec !== rendition.codec || next.bitrate_bps !== rendition.bitrate_bps ||\n next.frame_rate !== rendition.frame_rate)) switches++;\n rendition = next;\n for (const listener of renditionListeners) listener(rendition);\n };\n const onError = (event: Event) => {\n const detail = (event as ShakaEvent).detail;\n if (detail?.severity !== 2) return;\n const category = detail.category === 1 ? \"network\"\n : detail.category === 3 ? \"decode\" : \"unknown\";\n for (const listener of errorListeners) listener({\n category, code: Number.isFinite(detail.code) ? detail.code! : null, fatal: true,\n });\n };\n const onDownload = (event: Event) => {\n const response = (event as ShakaEvent).response;\n if (response?.fromCache) return;\n requests++;\n const size = response?.data?.byteLength;\n if (typeof size === \"number\" && Number.isFinite(size) && size >= 0) bytes += size;\n else bytesKnown = false;\n const duration = response?.timeMs;\n if (typeof duration === \"number\" && Number.isFinite(duration) && duration >= 0) durationMs += duration;\n else durationKnown = false;\n };\n player.addEventListener(\"mediaqualitychanged\", onQuality);\n player.addEventListener(\"error\", onError);\n player.addEventListener(\"downloadcompleted\", onDownload);\n\n return {\n name: \"shaka-player\", version: playerVersion,\n playerName: \"shaka-player\", playerVersion,\n snapshot(): AnalyticsConnectorSnapshot {\n return {\n renditionWidth: rendition?.width ?? null,\n renditionHeight: rendition?.height ?? null,\n renditionCodec: rendition?.codec ?? null,\n renditionBitrate: rendition?.bitrate_bps ?? null,\n renditionSwitchCount: switches,\n deliveryRequestCount: requests,\n deliveryTransferredBytes: bytesKnown ? bytes : null,\n deliveryDurationMs: durationKnown ? durationMs : null,\n };\n },\n subscribeToErrors(listener) {\n errorListeners.add(listener);\n return () => { errorListeners.delete(listener); };\n },\n subscribeToRenditions(listener) {\n renditionListeners.add(listener);\n listener(rendition);\n return () => { renditionListeners.delete(listener); };\n },\n reset() {\n rendition = null;\n switches = 0;\n requests = 0;\n bytes = 0;\n durationMs = 0;\n bytesKnown = true;\n durationKnown = true;\n for (const listener of renditionListeners) listener(null);\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n player.removeEventListener(\"mediaqualitychanged\", onQuality);\n player.removeEventListener(\"error\", onError);\n player.removeEventListener(\"downloadcompleted\", onDownload);\n errorListeners.clear();\n renditionListeners.clear();\n },\n };\n}\n\n/** Monitor an attached Shaka Player before loading and playing content. */\nexport function monitorShaka(player: ShakaPlayer, options: AnalyticsOptions):\n Pick<AnalyticsSession, \"startAttempt\" | \"sourceChanged\" | \"destroy\"> {\n assertAnalyticsOptions(options);\n const media = player.getMediaElement();\n const Media = media?.ownerDocument?.defaultView?.HTMLMediaElement;\n if (!Media || !(media instanceof Media)) {\n throw new TypeError(\"Shaka analytics requires a player attached to an HTML media element.\");\n }\n // Shaka only emits quality changes at the playhead when this observer is enabled.\n const observerWasEnabled = player.getConfiguration().streaming.observeQualityChanges;\n if (!observerWasEnabled) {\n player.configure({ streaming: { observeQualityChanges: true } });\n }\n const restoreObserver = !observerWasEnabled && player.getConfiguration().streaming.observeQualityChanges;\n const session = attachAnalyticsWithManagedSource(media, options);\n const connector = createShakaConnector(player);\n session.setConnector(connector);\n let destroyed = false;\n const sourceChanged = () => {\n if (destroyed) return;\n session.sourceChanged();\n connector.reset();\n };\n const onLoading = () => { if (!destroyed) session.startAttempt(); };\n const onUnloading = () => sourceChanged();\n player.addEventListener(\"loading\", onLoading);\n player.addEventListener(\"unloading\", onUnloading);\n return {\n startAttempt() { if (!destroyed) session.startAttempt(); },\n sourceChanged,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n player.removeEventListener(\"loading\", onLoading);\n player.removeEventListener(\"unloading\", onUnloading);\n session.destroy();\n connector.destroy();\n if (restoreObserver) {\n try { player.configure({ streaming: { observeQualityChanges: false } }); } catch {}\n }\n },\n };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type Hls from "hls.js";
2
+ import { type AnalyticsOptions, type AnalyticsSession } from "./core.js";
3
+ /** Video.js 10 HTML media components expose the media API on the custom element. */
4
+ export type VideoJsMediaElement = HTMLElement & {
5
+ readonly src: string;
6
+ readonly engine?: Hls | object | null;
7
+ readonly adapter?: object;
8
+ };
9
+ /** Monitor a Video.js 10 HTML media component, including its hls.js engine when active. */
10
+ export declare function monitorVideoJs(media: VideoJsMediaElement, options: AnalyticsOptions): Pick<AnalyticsSession, "startAttempt" | "sourceChanged" | "destroy">;
11
+ //# sourceMappingURL=videojs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videojs.d.ts","sourceRoot":"","sources":["../src/videojs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,QAAQ,CAAC;AAC9B,OAAO,EACL,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAIlE,oFAAoF;AACpF,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAyBF,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAClF,IAAI,CAAC,gBAAgB,EAAE,cAAc,GAAG,eAAe,GAAG,SAAS,CAAC,CAkDrE"}
@@ -0,0 +1,77 @@
1
+ import { attachAnalyticsWithManagedSource } from "./core.js";
2
+ import { createHlsJsConnector } from "./hlsjs.js";
3
+ import { assertAnalyticsOptions } from "./options.js";
4
+ const videoJsMediaTags = new Set(["hls-video", "hlsjs-video", "native-hls-video"]);
5
+ function emptySnapshot() {
6
+ return {
7
+ renditionWidth: null, renditionHeight: null, renditionCodec: null, renditionBitrate: null,
8
+ renditionSwitchCount: null, deliveryRequestCount: null,
9
+ deliveryTransferredBytes: null, deliveryDurationMs: null,
10
+ };
11
+ }
12
+ function videoJsConnector(engine) {
13
+ if (engine) {
14
+ const hls = createHlsJsConnector(engine);
15
+ return { ...hls, name: "video.js/hls.js", playerName: "video.js", playerVersion: null };
16
+ }
17
+ return {
18
+ name: "video.js", version: null, playerName: "video.js", playerVersion: null,
19
+ snapshot: emptySnapshot,
20
+ subscribeToErrors: () => () => { },
21
+ destroy() { },
22
+ };
23
+ }
24
+ /** Monitor a Video.js 10 HTML media component, including its hls.js engine when active. */
25
+ export function monitorVideoJs(media, options) {
26
+ assertAnalyticsOptions(options);
27
+ if (!media || !videoJsMediaTags.has(media.localName)
28
+ || typeof media.play !== "function"
29
+ || typeof media.adapter !== "object" || media.adapter === null) {
30
+ throw new TypeError("Video.js 10 analytics requires a registered hls-video, hlsjs-video, or native-hls-video element from @videojs/html.");
31
+ }
32
+ const session = attachAnalyticsWithManagedSource(media, options);
33
+ let currentSource = media.src;
34
+ let currentEngine = null;
35
+ let connector = null;
36
+ let destroyed = false;
37
+ const syncConnector = () => {
38
+ const engine = media.localName === "hlsjs-video" ? media.engine ?? null : null;
39
+ if (connector && engine === currentEngine)
40
+ return;
41
+ connector?.destroy();
42
+ currentEngine = engine;
43
+ connector = videoJsConnector(engine);
44
+ session.setConnector(connector);
45
+ };
46
+ const onSourceChange = () => {
47
+ if (media.src === currentSource)
48
+ return;
49
+ currentSource = media.src;
50
+ session.sourceChanged();
51
+ connector?.destroy();
52
+ connector = null;
53
+ currentEngine = null;
54
+ session.setConnector(null);
55
+ };
56
+ const onLoadStart = () => syncConnector();
57
+ media.addEventListener("sourcechange", onSourceChange);
58
+ media.addEventListener("loadstart", onLoadStart);
59
+ syncConnector();
60
+ return {
61
+ startAttempt() { if (!destroyed)
62
+ session.startAttempt(); },
63
+ sourceChanged() { if (!destroyed)
64
+ session.sourceChanged(); },
65
+ destroy() {
66
+ if (destroyed)
67
+ return;
68
+ destroyed = true;
69
+ media.removeEventListener("sourcechange", onSourceChange);
70
+ media.removeEventListener("loadstart", onLoadStart);
71
+ session.destroy();
72
+ connector?.destroy();
73
+ connector = null;
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=videojs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videojs.js","sourceRoot":"","sources":["../src/videojs.ts"],"names":[],"mappings":"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","sourcesContent":["import type Hls from \"hls.js\";\nimport { attachAnalyticsWithManagedSource, type AnalyticsConnector, type AnalyticsConnectorSnapshot,\n type AnalyticsOptions, type AnalyticsSession } from \"./core.js\";\nimport { createHlsJsConnector, type HlsJsAnalyticsConnector } from \"./hlsjs.js\";\nimport { assertAnalyticsOptions } from \"./options.js\";\n\n/** Video.js 10 HTML media components expose the media API on the custom element. */\nexport type VideoJsMediaElement = HTMLElement & {\n readonly src: string;\n readonly engine?: Hls | object | null;\n readonly adapter?: object;\n};\n\nconst videoJsMediaTags = new Set([\"hls-video\", \"hlsjs-video\", \"native-hls-video\"]);\n\nfunction emptySnapshot(): AnalyticsConnectorSnapshot {\n return {\n renditionWidth: null, renditionHeight: null, renditionCodec: null, renditionBitrate: null,\n renditionSwitchCount: null, deliveryRequestCount: null,\n deliveryTransferredBytes: null, deliveryDurationMs: null,\n };\n}\n\nfunction videoJsConnector(engine: Hls | null): AnalyticsConnector & { destroy(): void } {\n if (engine) {\n const hls: HlsJsAnalyticsConnector = createHlsJsConnector(engine);\n return { ...hls, name: \"video.js/hls.js\", playerName: \"video.js\", playerVersion: null };\n }\n return {\n name: \"video.js\", version: null, playerName: \"video.js\", playerVersion: null,\n snapshot: emptySnapshot,\n subscribeToErrors: () => () => {},\n destroy() {},\n };\n}\n\n/** Monitor a Video.js 10 HTML media component, including its hls.js engine when active. */\nexport function monitorVideoJs(media: VideoJsMediaElement, options: AnalyticsOptions):\n Pick<AnalyticsSession, \"startAttempt\" | \"sourceChanged\" | \"destroy\"> {\n assertAnalyticsOptions(options);\n if (!media || !videoJsMediaTags.has(media.localName)\n || typeof (media as { play?: unknown }).play !== \"function\"\n || typeof media.adapter !== \"object\" || media.adapter === null) {\n throw new TypeError(\"Video.js 10 analytics requires a registered hls-video, hlsjs-video, or native-hls-video element from @videojs/html.\");\n }\n\n const session = attachAnalyticsWithManagedSource(media as unknown as HTMLMediaElement, options);\n let currentSource = media.src;\n let currentEngine: Hls | null = null;\n let connector: (AnalyticsConnector & { destroy(): void }) | null = null;\n let destroyed = false;\n\n const syncConnector = () => {\n const engine = media.localName === \"hlsjs-video\" ? (media.engine as Hls | null | undefined) ?? null : null;\n if (connector && engine === currentEngine) return;\n connector?.destroy();\n currentEngine = engine;\n connector = videoJsConnector(engine);\n session.setConnector(connector);\n };\n const onSourceChange = () => {\n if (media.src === currentSource) return;\n currentSource = media.src;\n session.sourceChanged();\n connector?.destroy();\n connector = null;\n currentEngine = null;\n session.setConnector(null);\n };\n const onLoadStart = () => syncConnector();\n\n media.addEventListener(\"sourcechange\", onSourceChange);\n media.addEventListener(\"loadstart\", onLoadStart);\n syncConnector();\n\n return {\n startAttempt() { if (!destroyed) session.startAttempt(); },\n sourceChanged() { if (!destroyed) session.sourceChanged(); },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n media.removeEventListener(\"sourcechange\", onSourceChange);\n media.removeEventListener(\"loadstart\", onLoadStart);\n session.destroy();\n connector?.destroy();\n connector = null;\n },\n };\n}\n"]}