@hopecloud/jetstream-player 1.4.0-rc.7 → 1.4.0-rc.8

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
@@ -1,8 +1,12 @@
1
- # @hopecloud/jetstream-player
1
+ # Jetstream Player SDK
2
2
 
3
- Control an embedded [Jetstream](https://docs.hope.cloud/libs/jetstream-player/) player
4
- from your page play, pause, seek, mute, read playback state, and subscribe to
5
- events across the iframe boundary, with no server glue.
3
+ Put the [Jetstream](https://docs.hope.cloud/libs/jetstream-player/) Player on
4
+ your website and drive it from your own code. The player itself lives in an
5
+ iframe served by Jetstream; this SDK is your remote control for it — play,
6
+ pause, seek, mute, ask what's happening, and react to events, without ever
7
+ thinking about the iframe boundary.
8
+
9
+ It works the same way for videos, audio, and whole playlists.
6
10
 
7
11
  **[Full documentation →](https://docs.hope.cloud/libs/jetstream-player/)**
8
12
 
@@ -14,6 +18,14 @@ npm i @hopecloud/jetstream-player
14
18
 
15
19
  ## Quick start
16
20
 
21
+ Give the player a place on your page — any element with an id:
22
+
23
+ ```html
24
+ <div id="player"></div>
25
+ ```
26
+
27
+ Then create the player and tell it what to play:
28
+
17
29
  ```ts
18
30
  import { JetstreamPlayer } from '@hopecloud/jetstream-player';
19
31
 
@@ -21,7 +33,7 @@ const player = new JetstreamPlayer('#player', {
21
33
  mediaId: 'jsv:xxxxxxxxxx',
22
34
  events: {
23
35
  ready: () => console.log('ready'),
24
- timeupdate: (time) => console.log('time', time),
36
+ timeupdate: (seconds) => console.log('now at', seconds),
25
37
  },
26
38
  });
27
39
 
@@ -29,49 +41,56 @@ player.play();
29
41
  const muted = await player.isMuted();
30
42
  ```
31
43
 
32
- `mediaId` is the only required option; the library creates and mounts the iframe
33
- at the given selector.
44
+ `mediaId` a video, an audio, or a playlist id from Jetstream — is the only
45
+ required option. The SDK creates the iframe and mounts it inside your element;
46
+ no other markup needed.
34
47
 
35
48
  ## What you can do
36
49
 
37
- - **Commands** — `play()`, `pause()`, `mute()`, `unmute()`, `seekTo(seconds)`,
38
- `playNext()`, `playPrev()`.
39
- - **Getters** (Promise-based) — `isMuted()`, `isPaused()`, `getCurrentTime()`,
40
- `getDuration()`. They reject after 5s if the player doesn't reply.
41
- - **Events** subscribe via `options.events`: `ready`, `timeupdate`, `play`,
42
- `pause`, `loadedmetadata`, `ended`, `volumechange`, `error`.
43
- - **Teardown** — `dispose()` removes listeners and tears down Picture-in-Picture.
50
+ - **Control playback** — `play()`, `pause()`, `seekTo(seconds)`, `mute()`,
51
+ `unmute()`, and `playNext()` / `playPrev()` for playlists.
52
+ - **Ask the player questions** — `isMuted()`, `isPaused()`, `getCurrentTime()`,
53
+ `getDuration()`. The answers come from the player itself, so these return
54
+ Promises (and reject after 5 seconds if the player doesn't reply).
55
+ - **React to events** — pass handlers in `options.events`, or subscribe later
56
+ with `on()` (it returns an unsubscribe function): `ready`, `play`, `pause`,
57
+ `timeupdate`, `loadedmetadata`, `ended`, `volumechange`, `error`.
58
+ - **Keep it on screen** — `sticky: true` pins the player to a corner of the
59
+ screen when the visitor scrolls past it.
60
+ - **Clean up** — `dispose()` when your page removes the player.
44
61
 
45
- See the [options](https://docs.hope.cloud/libs/jetstream-player/player-options)
46
- and [methods](https://docs.hope.cloud/libs/jetstream-player/player-methods)
47
- reference for the full list.
62
+ Start with [Getting Started](https://docs.hope.cloud/libs/jetstream-player/getting-started),
63
+ then see [Player Options](https://docs.hope.cloud/libs/jetstream-player/player-options)
64
+ and [Player Methods](https://docs.hope.cloud/libs/jetstream-player/player-methods)
65
+ for everything you can configure and call.
48
66
 
49
- ## Targeting a non-prod embed
67
+ ## Pointing at a staging environment
50
68
 
51
- By default the player loads the production embed. Point it at another host
52
- (staging/dev) with `embedOrigin`:
69
+ By default the player loads the production embed. For special setups a
70
+ staging or dev environment — point it elsewhere with `embedOrigin`:
53
71
 
54
72
  ```ts
55
- new JetstreamPlayer('#player', {
73
+ const player = new JetstreamPlayer('#player', {
56
74
  mediaId: 'jsv:xxxxxxxxxx',
57
75
  embedOrigin: 'https://embed-stage.jetstream.studio',
58
76
  });
59
77
  ```
60
78
 
61
- Communication is cross-origin `postMessage`, and the SDK learns the iframe's
62
- real origin at runtime — so it keeps working even when the embed redirects.
79
+ Under the hood everything is cross-origin `postMessage`, and the SDK learns
80
+ the iframe's real origin at runtime — so it keeps working even when the embed
81
+ host redirects.
63
82
 
64
83
  ## Development
65
84
 
66
85
  ```bash
67
86
  pnpm install
68
- pnpm build # rolldown + .d.ts
69
- pnpm test # vitest
70
- pnpm lint
71
- pnpm type-check
87
+ pnpm build # vp pack — bundle + .d.ts
88
+ pnpm test # vp test (vitest)
89
+ pnpm check # format check + type-aware lint + type errors (the CI gate)
72
90
  ```
73
91
 
74
92
  Documentation lives in the monorepo's public docs site at
75
93
  `docs/cloud/libs/jetstream-player/`, published to docs.hope.cloud.
76
94
 
77
- Architecture decisions are recorded in [`adr/`](./adr).
95
+ Architecture decisions are recorded in the monorepo at
96
+ `docs/adr/jetstream-player/`.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,78 @@
1
- export { JetstreamPlayer } from './player';
2
- export type { JetstreamPlayerOptions, PlayerEventHandler, PlayerEventHandlers, } from './player.types';
3
- export type { EmbedFrameOptions } from './embed/embed-frame.types';
4
- export { playerListenEvents } from './bridge/protocol.types';
5
- export type { PlayerCommand, PlayerCommandEvent, PlayerEvent, PlayerEventMap, PlayerEvents, PlayerGetterEvents, } from './bridge/protocol.types';
1
+ //#region src/bridge/protocol.types.d.ts
2
+ interface PlayerEventMap {
3
+ ready: unknown;
4
+ timeupdate: number;
5
+ play: unknown;
6
+ pause: unknown;
7
+ loadedmetadata: undefined;
8
+ ended: boolean;
9
+ volumechange: number;
10
+ error: unknown;
11
+ }
12
+ type PlayerEvents = keyof PlayerEventMap;
13
+ declare const playerListenEvents: readonly PlayerEvents[];
14
+ type PlayerGetterEvents = "is-muted" | "get-current-time" | "get-video-current-time" | "get-duration" | "is-paused";
15
+ interface PlayerEvent {
16
+ type: PlayerEvents | PlayerGetterEvents;
17
+ value?: unknown;
18
+ }
19
+ type PlayerCommandEvent = "play" | "pause" | "mute" | "unmute" | "seek-to" | "seekTo" | "play-next" | "play-prev";
20
+ interface PlayerCommand {
21
+ name: PlayerCommandEvent | PlayerGetterEvents;
22
+ params?: unknown;
23
+ }
24
+ //#endregion
25
+ //#region src/embed/embed-frame.types.d.ts
26
+ interface EmbedFrameOptions {
27
+ mediaId: string;
28
+ playerId?: string;
29
+ region?: "eu";
30
+ audioLang?: string;
31
+ subtitleLang?: string;
32
+ embedOrigin?: string;
33
+ /** @deprecated Use {@link embedOrigin} instead. */
34
+ origin?: string;
35
+ width?: string;
36
+ height?: string;
37
+ }
38
+ //#endregion
39
+ //#region src/player.types.d.ts
40
+ type PlayerEventHandler<E extends PlayerEvents = PlayerEvents> = (value: PlayerEventMap[E]) => void;
41
+ type PlayerEventHandlers = { [E in PlayerEvents]?: PlayerEventHandler<E> };
42
+ type JetstreamPlayerOptions = {
43
+ sticky?: boolean;
44
+ events?: PlayerEventHandlers;
45
+ } & EmbedFrameOptions;
46
+ //#endregion
47
+ //#region src/player.d.ts
48
+ declare class JetstreamPlayer {
49
+ private options;
50
+ private readonly listeners;
51
+ private iFramePiP;
52
+ private bridge;
53
+ constructor(el: string, options: JetstreamPlayerOptions);
54
+ private registerEvents;
55
+ private emit;
56
+ on<E extends PlayerEvents>(event: E, handler: PlayerEventHandler<E>): () => void;
57
+ off<E extends PlayerEvents>(event: E, handler?: PlayerEventHandler<E>): void;
58
+ private request;
59
+ play(): void;
60
+ pause(): void;
61
+ mute(): void;
62
+ unmute(): void;
63
+ seekTo(seconds: number): void;
64
+ playNext(): void;
65
+ playPrev(): void;
66
+ isMuted(): Promise<boolean>;
67
+ isPaused(): Promise<boolean>;
68
+ getCurrentTime(): Promise<number>;
69
+ /** @deprecated Use {@link getCurrentTime} instead. */
70
+ getVideoCurrentTime(): Promise<number>;
71
+ getDuration(): Promise<number>;
72
+ dispose(): void;
73
+ }
74
+ //#endregion
75
+ //#region src/bridge/protocol.d.ts
76
+ declare const decodeCommand: (raw: string) => PlayerCommand | null;
77
+ //#endregion
78
+ export { type EmbedFrameOptions, JetstreamPlayer, type JetstreamPlayerOptions, type PlayerCommand, type PlayerCommandEvent, type PlayerEvent, type PlayerEventHandler, type PlayerEventHandlers, type PlayerEventMap, type PlayerEvents, type PlayerGetterEvents, decodeCommand, playerListenEvents };
package/dist/index.js CHANGED
@@ -1,15 +1,17 @@
1
1
  //#region src/bridge/protocol.ts
2
2
  const encode = (command) => JSON.stringify(command);
3
- const decode = (raw) => {
3
+ const parseObjectWithStringKey = (raw, key) => {
4
4
  if (typeof raw !== "string") return null;
5
5
  try {
6
6
  const parsed = JSON.parse(raw);
7
- if (typeof parsed === "object" && parsed !== null && typeof parsed.type === "string") return parsed;
7
+ if (typeof parsed === "object" && parsed !== null && typeof parsed[key] === "string") return parsed;
8
8
  return null;
9
9
  } catch {
10
10
  return null;
11
11
  }
12
12
  };
13
+ const decode = (raw) => parseObjectWithStringKey(raw, "type");
14
+ const decodeCommand = (raw) => parseObjectWithStringKey(raw, "name");
13
15
  //#endregion
14
16
  //#region src/bridge/player-bridge.ts
15
17
  const REQUEST_TIMEOUT_MS = 5e3;
@@ -103,7 +105,7 @@ const mountEmbedFrame = (selector, options) => {
103
105
  const iframe = document.createElement("iframe");
104
106
  iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
105
107
  iframe.allowFullscreen = true;
106
- iframe.frameBorder = "0";
108
+ iframe.style.border = "0";
107
109
  iframe.title = "Jetstream Player";
108
110
  iframe.src = src;
109
111
  iframe.width = options.width || "480px";
@@ -134,8 +136,6 @@ var IFramePiPPlugin = class {
134
136
  originalIframeStyle = null;
135
137
  parent = null;
136
138
  constructor(containerSelector, iframe) {
137
- this.containerSelector = containerSelector;
138
- this.iframe = iframe;
139
139
  this.containerSelector = containerSelector;
140
140
  this.iframe = iframe;
141
141
  this.init();
@@ -147,8 +147,10 @@ var IFramePiPPlugin = class {
147
147
  this.parent = parent;
148
148
  this.originalParentHeight = parent.style.height || null;
149
149
  this.originalIframeStyle = this.iframe.getAttribute("style");
150
- this.observer = new IntersectionObserver(([{ isIntersecting }]) => {
151
- if (!isIntersecting) {
150
+ this.observer = new IntersectionObserver((entries) => {
151
+ const entry = entries[0];
152
+ if (!entry) return;
153
+ if (!entry.isIntersecting) {
152
154
  const height = parent.getBoundingClientRect().height;
153
155
  parent.style.height = `${height}px`;
154
156
  this.iframe.style.position = PiP_STYLES.position;
@@ -253,7 +255,7 @@ var JetstreamPlayer = class {
253
255
  }
254
256
  seekTo(seconds) {
255
257
  this.bridge?.send({
256
- name: "seekTo",
258
+ name: "seek-to",
257
259
  params: seconds
258
260
  });
259
261
  }
@@ -270,7 +272,7 @@ var JetstreamPlayer = class {
270
272
  return this.request({ name: "is-paused" });
271
273
  }
272
274
  getCurrentTime() {
273
- return this.request({ name: "get-video-current-time" });
275
+ return this.request({ name: "get-current-time" });
274
276
  }
275
277
  /** @deprecated Use {@link getCurrentTime} instead. */
276
278
  getVideoCurrentTime() {
@@ -298,4 +300,4 @@ const playerListenEvents = [
298
300
  "error"
299
301
  ];
300
302
  //#endregion
301
- export { JetstreamPlayer, playerListenEvents };
303
+ export { JetstreamPlayer, decodeCommand, playerListenEvents };
package/package.json CHANGED
@@ -1,45 +1,39 @@
1
1
  {
2
2
  "name": "@hopecloud/jetstream-player",
3
- "version": "1.4.0-rc.7",
3
+ "version": "1.4.0-rc.8",
4
4
  "description": "Jetstream Embed Player SDK",
5
- "type": "module",
6
- "sideEffects": false,
7
5
  "homepage": "https://docs.hope.cloud/libs/jetstream-player/",
8
- "engines": {
9
- "node": "24"
10
- },
11
- "publishConfig": {
12
- "access": "public"
6
+ "author": {
7
+ "name": "DV Software",
8
+ "email": "code@deepvision.software"
13
9
  },
14
- "main": "dist/index.js",
15
10
  "files": [
16
11
  "dist"
17
12
  ],
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "main": "dist/index.js",
18
16
  "exports": {
19
17
  ".": {
20
18
  "types": "./dist/index.d.ts",
21
19
  "import": "./dist/index.js"
22
20
  }
23
21
  },
24
- "author": {
25
- "email": "code@deepvision.software",
26
- "name": "DV Software"
22
+ "publishConfig": {
23
+ "access": "public"
27
24
  },
28
25
  "devDependencies": {
29
- "@deepvision/eslint-plugin": "2.0.3",
30
26
  "@types/node": "24.12.0",
31
- "eslint": "10.1.0",
32
27
  "happy-dom": "^20.4.0",
33
- "rolldown": "^1.1.4",
34
- "typescript": "6.0.3",
35
- "vitest": "^4.0.18"
28
+ "vite-plus": "^0.2.2"
29
+ },
30
+ "engines": {
31
+ "node": "24"
36
32
  },
37
33
  "scripts": {
38
- "build": "rolldown --config && tsc --emitDeclarationOnly",
39
- "lint": "eslint",
40
- "lint:fix": "eslint --fix",
41
- "type-check": "tsc --noEmit",
42
- "test": "vitest",
43
- "test:run": "vitest run"
34
+ "build": "vp pack",
35
+ "check": "vp check",
36
+ "check:fix": "vp check --fix",
37
+ "test": "vp test"
44
38
  }
45
39
  }
@@ -1,13 +0,0 @@
1
- import type { PlayerCommand, PlayerEvent } from './protocol.types';
2
- import type { PlayerTransport } from './player-transport';
3
- export declare class PlayerBridge {
4
- private readonly transport;
5
- private targetOrigin;
6
- private readonly listeners;
7
- private readonly detach;
8
- constructor(transport: PlayerTransport, initialTargetOrigin: string);
9
- send(command: PlayerCommand): void;
10
- subscribe(listener: (event: PlayerEvent) => void): () => void;
11
- request<T>(command: PlayerCommand, timeoutMs?: number): Promise<T>;
12
- dispose(): void;
13
- }
@@ -1,5 +0,0 @@
1
- export interface PlayerTransport {
2
- send(message: string, targetOrigin: string): void;
3
- subscribe(handler: (message: string, origin: string) => void): () => void;
4
- }
5
- export declare const windowTransport: (iframe: HTMLIFrameElement) => PlayerTransport;
@@ -1,3 +0,0 @@
1
- import type { PlayerCommand, PlayerEvent } from './protocol.types';
2
- export declare const encode: (command: PlayerCommand) => string;
3
- export declare const decode: (raw: string) => PlayerEvent | null;
@@ -1,22 +0,0 @@
1
- export interface PlayerEventMap {
2
- ready: unknown;
3
- timeupdate: number;
4
- play: unknown;
5
- pause: unknown;
6
- loadedmetadata: undefined;
7
- ended: boolean;
8
- volumechange: number;
9
- error: unknown;
10
- }
11
- export declare const playerListenEvents: readonly ["ready", "timeupdate", "play", "pause", "loadedmetadata", "ended", "volumechange", "error"];
12
- export type PlayerEvents = (typeof playerListenEvents)[number];
13
- export type PlayerGetterEvents = 'is-muted' | 'get-video-current-time' | 'get-duration' | 'is-paused';
14
- export interface PlayerEvent {
15
- type: PlayerEvents | PlayerGetterEvents;
16
- value?: unknown;
17
- }
18
- export type PlayerCommandEvent = 'play' | 'pause' | 'mute' | 'unmute' | 'seekTo' | 'play-next' | 'play-prev';
19
- export interface PlayerCommand {
20
- name: PlayerCommandEvent | PlayerGetterEvents;
21
- params?: unknown;
22
- }
@@ -1,7 +0,0 @@
1
- import type { EmbedFrameOptions } from './embed-frame.types';
2
- export declare const buildEmbedSrc: (options: EmbedFrameOptions) => string;
3
- export interface MountedFrame {
4
- iframe: HTMLIFrameElement;
5
- src: string;
6
- }
7
- export declare const mountEmbedFrame: (selector: string, options: EmbedFrameOptions) => MountedFrame;
@@ -1,12 +0,0 @@
1
- export interface EmbedFrameOptions {
2
- mediaId: string;
3
- playerId?: string;
4
- region?: 'eu';
5
- audioLang?: string;
6
- subtitleLang?: string;
7
- embedOrigin?: string;
8
- /** @deprecated Use {@link embedOrigin} instead. */
9
- origin?: string;
10
- width?: string;
11
- height?: string;
12
- }
@@ -1,11 +0,0 @@
1
- export declare class IFramePiPPlugin {
2
- containerSelector: string;
3
- iframe: HTMLIFrameElement;
4
- private observer;
5
- private originalParentHeight;
6
- private originalIframeStyle;
7
- private parent;
8
- constructor(containerSelector: string, iframe: HTMLIFrameElement);
9
- init(): void;
10
- destroy(): void;
11
- }
package/dist/player.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import type { PlayerEvents } from './bridge/protocol.types';
2
- import type { JetstreamPlayerOptions, PlayerEventHandler } from './player.types';
3
- export declare class JetstreamPlayer {
4
- private options;
5
- private readonly listeners;
6
- private iFramePiP;
7
- private bridge;
8
- constructor(el: string, options: JetstreamPlayerOptions);
9
- private registerEvents;
10
- private emit;
11
- on<E extends PlayerEvents>(event: E, handler: PlayerEventHandler<E>): () => void;
12
- off<E extends PlayerEvents>(event: E, handler?: PlayerEventHandler<E>): void;
13
- private request;
14
- play(): void;
15
- pause(): void;
16
- mute(): void;
17
- unmute(): void;
18
- seekTo(seconds: number): void;
19
- playNext(): void;
20
- playPrev(): void;
21
- isMuted(): Promise<boolean>;
22
- isPaused(): Promise<boolean>;
23
- getCurrentTime(): Promise<number>;
24
- /** @deprecated Use {@link getCurrentTime} instead. */
25
- getVideoCurrentTime(): Promise<number>;
26
- getDuration(): Promise<number>;
27
- dispose(): void;
28
- }
@@ -1,10 +0,0 @@
1
- import type { PlayerEventMap, PlayerEvents } from './bridge/protocol.types';
2
- import type { EmbedFrameOptions } from './embed/embed-frame.types';
3
- export type PlayerEventHandler<E extends PlayerEvents = PlayerEvents> = (value: PlayerEventMap[E]) => void;
4
- export type PlayerEventHandlers = {
5
- [E in PlayerEvents]?: PlayerEventHandler<E>;
6
- };
7
- export type JetstreamPlayerOptions = {
8
- sticky?: boolean;
9
- events?: PlayerEventHandlers;
10
- } & EmbedFrameOptions;