@hopecloud/jetstream-player 1.3.0 → 1.4.0-rc.6

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,10 +1,10 @@
1
1
  # @hopecloud/jetstream-player
2
2
 
3
- Control an embedded [Jetstream](https://jetstream-player.docs.hope.cloud/) player
3
+ Control an embedded [Jetstream](https://docs.hope.cloud/libs/jetstream-player/) player
4
4
  from your page — play, pause, seek, mute, read playback state, and subscribe to
5
5
  events — across the iframe boundary, with no server glue.
6
6
 
7
- **[Full documentation →](https://jetstream-player.docs.hope.cloud/)**
7
+ **[Full documentation →](https://docs.hope.cloud/libs/jetstream-player/)**
8
8
 
9
9
  ## Install
10
10
 
@@ -36,26 +36,25 @@ at the given selector.
36
36
 
37
37
  - **Commands** — `play()`, `pause()`, `mute()`, `unmute()`, `seekTo(seconds)`,
38
38
  `playNext()`, `playPrev()`.
39
- - **Getters** (Promise-based) — `isMuted()`, `isPaused()`,
40
- `getVideoCurrentTime()`, `getDuration()`. They reject after 5s if the player
41
- doesn't reply.
39
+ - **Getters** (Promise-based) — `isMuted()`, `isPaused()`, `getCurrentTime()`,
40
+ `getDuration()`. They reject after 5s if the player doesn't reply.
42
41
  - **Events** — subscribe via `options.events`: `ready`, `timeupdate`, `play`,
43
42
  `pause`, `loadedmetadata`, `ended`, `volumechange`, `error`.
44
43
  - **Teardown** — `dispose()` removes listeners and tears down Picture-in-Picture.
45
44
 
46
- See the [options](https://jetstream-player.docs.hope.cloud/api/player-options)
47
- and [methods](https://jetstream-player.docs.hope.cloud/api/player-methods)
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)
48
47
  reference for the full list.
49
48
 
50
49
  ## Targeting a non-prod embed
51
50
 
52
51
  By default the player loads the production embed. Point it at another host
53
- (staging/dev) with `origin`:
52
+ (staging/dev) with `embedOrigin`:
54
53
 
55
54
  ```ts
56
55
  new JetstreamPlayer('#player', {
57
56
  mediaId: 'jsv:xxxxxxxxxx',
58
- origin: 'https://embed-stage.jetstream.studio',
57
+ embedOrigin: 'https://embed-stage.jetstream.studio',
59
58
  });
60
59
  ```
61
60
 
@@ -69,8 +68,10 @@ pnpm install
69
68
  pnpm build # rolldown + .d.ts
70
69
  pnpm test # vitest
71
70
  pnpm lint
72
- pnpm typecheck
73
- pnpm docs:dev # vitepress docs
71
+ pnpm type-check
74
72
  ```
75
73
 
74
+ Documentation lives in the monorepo's public docs site at
75
+ `docs/cloud/libs/jetstream-player/`, published to docs.hope.cloud.
76
+
76
77
  Architecture decisions are recorded in [`adr/`](./adr).
@@ -1,4 +1,4 @@
1
- import type { PlayerCommand, PlayerEvent } from './player.type';
1
+ import type { PlayerCommand, PlayerEvent } from './protocol.types';
2
2
  import type { PlayerTransport } from './player-transport';
3
3
  export declare class PlayerBridge {
4
4
  private readonly transport;
@@ -1,3 +1,3 @@
1
- import type { PlayerCommand, PlayerEvent } from './player.type';
1
+ import type { PlayerCommand, PlayerEvent } from './protocol.types';
2
2
  export declare const encode: (command: PlayerCommand) => string;
3
3
  export declare const decode: (raw: string) => PlayerEvent | null;
@@ -1,27 +1,22 @@
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
+ }
1
11
  export declare const playerListenEvents: readonly ["ready", "timeupdate", "play", "pause", "loadedmetadata", "ended", "volumechange", "error"];
2
- export type PlayerEvents = 'ready' | 'timeupdate' | 'play' | 'pause' | 'loadedmetadata' | 'ended' | 'volumechange' | 'error';
12
+ export type PlayerEvents = (typeof playerListenEvents)[number];
3
13
  export type PlayerGetterEvents = 'is-muted' | 'get-video-current-time' | 'get-duration' | 'is-paused';
4
14
  export interface PlayerEvent {
5
15
  type: PlayerEvents | PlayerGetterEvents;
6
16
  value?: unknown;
7
17
  }
8
- export type PlayerEventHandler = (value?: unknown) => void;
9
18
  export type PlayerCommandEvent = 'play' | 'pause' | 'mute' | 'unmute' | 'seekTo' | 'play-next' | 'play-prev';
10
19
  export interface PlayerCommand {
11
20
  name: PlayerCommandEvent | PlayerGetterEvents;
12
21
  params?: unknown;
13
22
  }
14
- export interface EmbedFrameOptions {
15
- mediaId: string;
16
- playerId?: string;
17
- region?: 'eu';
18
- audioLang?: string;
19
- subtitleLang?: string;
20
- origin?: string;
21
- width?: string;
22
- height?: string;
23
- }
24
- export type JetstreamPlayerOptions = {
25
- sticky?: boolean;
26
- events?: Partial<Record<PlayerEvents, PlayerEventHandler>>;
27
- } & EmbedFrameOptions;
@@ -1,4 +1,4 @@
1
- import type { EmbedFrameOptions } from './player.type';
1
+ import type { EmbedFrameOptions } from './embed-frame.types';
2
2
  export declare const buildEmbedSrc: (options: EmbedFrameOptions) => string;
3
3
  export interface MountedFrame {
4
4
  iframe: HTMLIFrameElement;
@@ -0,0 +1,12 @@
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
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export { JetstreamPlayer } from './player';
2
- export * from './player.type';
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';
package/dist/index.js CHANGED
@@ -1,5 +1,84 @@
1
- //#region src/embed-frame.ts
2
- const DEFAULT_ORIGIN = "https://jstre.am";
1
+ //#region src/bridge/protocol.ts
2
+ const encode = (command) => JSON.stringify(command);
3
+ const decode = (raw) => {
4
+ if (typeof raw !== "string") return null;
5
+ try {
6
+ const parsed = JSON.parse(raw);
7
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.type === "string") return parsed;
8
+ return null;
9
+ } catch {
10
+ return null;
11
+ }
12
+ };
13
+ //#endregion
14
+ //#region src/bridge/player-bridge.ts
15
+ const REQUEST_TIMEOUT_MS = 5e3;
16
+ var PlayerBridge = class {
17
+ transport;
18
+ targetOrigin;
19
+ listeners = /* @__PURE__ */ new Set();
20
+ detach;
21
+ constructor(transport, initialTargetOrigin) {
22
+ this.transport = transport;
23
+ this.targetOrigin = initialTargetOrigin;
24
+ this.detach = this.transport.subscribe((message, origin) => {
25
+ const event = decode(message);
26
+ if (!event) return;
27
+ this.targetOrigin = origin;
28
+ this.listeners.forEach((listener) => listener(event));
29
+ });
30
+ }
31
+ send(command) {
32
+ this.transport.send(encode(command), this.targetOrigin);
33
+ }
34
+ subscribe(listener) {
35
+ this.listeners.add(listener);
36
+ return () => {
37
+ this.listeners.delete(listener);
38
+ };
39
+ }
40
+ request(command, timeoutMs = REQUEST_TIMEOUT_MS) {
41
+ return new Promise((resolve, reject) => {
42
+ let off = () => void 0;
43
+ const timer = setTimeout(() => {
44
+ off();
45
+ reject(/* @__PURE__ */ new Error(`Timed out waiting for "${command.name}" response`));
46
+ }, timeoutMs);
47
+ off = this.subscribe((event) => {
48
+ if (event.type === command.name) {
49
+ clearTimeout(timer);
50
+ off();
51
+ resolve(event.value);
52
+ }
53
+ });
54
+ this.send(command);
55
+ });
56
+ }
57
+ dispose() {
58
+ this.detach();
59
+ this.listeners.clear();
60
+ }
61
+ };
62
+ //#endregion
63
+ //#region src/bridge/player-transport.ts
64
+ const windowTransport = (iframe) => ({
65
+ send: (message, targetOrigin) => {
66
+ iframe.contentWindow?.postMessage(message, targetOrigin);
67
+ },
68
+ subscribe: (handler) => {
69
+ const listener = (event) => {
70
+ if (event.source !== iframe.contentWindow) return;
71
+ handler(event.data, event.origin);
72
+ };
73
+ window.addEventListener("message", listener);
74
+ return () => {
75
+ window.removeEventListener("message", listener);
76
+ };
77
+ }
78
+ });
79
+ //#endregion
80
+ //#region src/embed/embed-frame.ts
81
+ const DEFAULT_EMBED_ORIGIN = "https://jstre.am";
3
82
  const regionOrigin = (origin, region) => {
4
83
  const url = new URL(origin);
5
84
  if (region === "eu") {
@@ -11,9 +90,9 @@ const regionOrigin = (origin, region) => {
11
90
  };
12
91
  const buildEmbedSrc = (options) => {
13
92
  if (options.mediaId === "") throw new Error("mediaId not provided!");
14
- const origin = regionOrigin(options.origin ?? DEFAULT_ORIGIN, options.region);
93
+ const embedOrigin = regionOrigin(options.embedOrigin ?? options.origin ?? DEFAULT_EMBED_ORIGIN, options.region);
15
94
  const path = options.playerId ? `/embed/${options.mediaId}/${options.playerId}` : `/embed/${options.mediaId}`;
16
- const url = new URL(`${origin}${path}`);
95
+ const url = new URL(`${embedOrigin}${path}`);
17
96
  if (options.region === "eu") url.searchParams.append("region", "eu");
18
97
  if (options.audioLang) url.searchParams.append("audioLang", options.audioLang.toLowerCase());
19
98
  if (options.subtitleLang) url.searchParams.append("subtitleLang", options.subtitleLang.toLowerCase());
@@ -25,11 +104,11 @@ const mountEmbedFrame = (selector, options) => {
25
104
  iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
26
105
  iframe.allowFullscreen = true;
27
106
  iframe.frameBorder = "0";
28
- iframe.title = "Jetstream player";
107
+ iframe.title = "Jetstream Player";
29
108
  iframe.src = src;
30
109
  iframe.width = options.width || "480px";
31
110
  iframe.height = options.height || "270px";
32
- iframe.classList.add("jet-stream-iframe-player");
111
+ iframe.classList.add("jetstream-player-iframe");
33
112
  const parent = document.querySelector(selector);
34
113
  if (parent) parent.appendChild(iframe);
35
114
  else console.error(`Element matching selector "${selector}" not found.`);
@@ -39,7 +118,7 @@ const mountEmbedFrame = (selector, options) => {
39
118
  };
40
119
  };
41
120
  //#endregion
42
- //#region src/iframe-pip-plugin.ts
121
+ //#region src/pip/iframe-pip-plugin.ts
43
122
  const PiP_STYLES = {
44
123
  position: "fixed",
45
124
  bottom: "20px",
@@ -98,90 +177,8 @@ var IFramePiPPlugin = class {
98
177
  if (this.iframe) if (this.originalIframeStyle) this.iframe.setAttribute("style", this.originalIframeStyle);
99
178
  else this.iframe.removeAttribute("style");
100
179
  }
101
- destory() {
102
- this.destroy();
103
- }
104
- };
105
- //#endregion
106
- //#region src/protocol.ts
107
- const encode = (command) => JSON.stringify(command);
108
- const decode = (raw) => {
109
- if (typeof raw !== "string") return null;
110
- try {
111
- const parsed = JSON.parse(raw);
112
- if (typeof parsed === "object" && parsed !== null && typeof parsed.type === "string") return parsed;
113
- return null;
114
- } catch {
115
- return null;
116
- }
117
- };
118
- //#endregion
119
- //#region src/player-bridge.ts
120
- const REQUEST_TIMEOUT_MS = 5e3;
121
- var PlayerBridge = class {
122
- transport;
123
- targetOrigin;
124
- listeners = /* @__PURE__ */ new Set();
125
- detach;
126
- constructor(transport, initialTargetOrigin) {
127
- this.transport = transport;
128
- this.targetOrigin = initialTargetOrigin;
129
- this.detach = this.transport.subscribe((message, origin) => {
130
- const event = decode(message);
131
- if (!event) return;
132
- this.targetOrigin = origin;
133
- this.listeners.forEach((listener) => listener(event));
134
- });
135
- }
136
- send(command) {
137
- this.transport.send(encode(command), this.targetOrigin);
138
- }
139
- subscribe(listener) {
140
- this.listeners.add(listener);
141
- return () => {
142
- this.listeners.delete(listener);
143
- };
144
- }
145
- request(command, timeoutMs = REQUEST_TIMEOUT_MS) {
146
- return new Promise((resolve, reject) => {
147
- let off = () => void 0;
148
- const timer = setTimeout(() => {
149
- off();
150
- reject(/* @__PURE__ */ new Error(`Timed out waiting for "${command.name}" response`));
151
- }, timeoutMs);
152
- off = this.subscribe((event) => {
153
- if (event.type === command.name) {
154
- clearTimeout(timer);
155
- off();
156
- resolve(event.value);
157
- }
158
- });
159
- this.send(command);
160
- });
161
- }
162
- dispose() {
163
- this.detach();
164
- this.listeners.clear();
165
- }
166
180
  };
167
181
  //#endregion
168
- //#region src/player-transport.ts
169
- const windowTransport = (iframe) => ({
170
- send: (message, targetOrigin) => {
171
- iframe.contentWindow?.postMessage(message, targetOrigin);
172
- },
173
- subscribe: (handler) => {
174
- const listener = (event) => {
175
- if (event.source !== iframe.contentWindow) return;
176
- handler(event.data, event.origin);
177
- };
178
- window.addEventListener("message", listener);
179
- return () => {
180
- window.removeEventListener("message", listener);
181
- };
182
- }
183
- });
184
- //#endregion
185
182
  //#region src/player.ts
186
183
  var JetstreamPlayer = class {
187
184
  options;
@@ -272,9 +269,13 @@ var JetstreamPlayer = class {
272
269
  isPaused() {
273
270
  return this.request({ name: "is-paused" });
274
271
  }
275
- getVideoCurrentTime() {
272
+ getCurrentTime() {
276
273
  return this.request({ name: "get-video-current-time" });
277
274
  }
275
+ /** @deprecated Use {@link getCurrentTime} instead. */
276
+ getVideoCurrentTime() {
277
+ return this.getCurrentTime();
278
+ }
278
279
  getDuration() {
279
280
  return this.request({ name: "get-duration" });
280
281
  }
@@ -285,7 +286,7 @@ var JetstreamPlayer = class {
285
286
  }
286
287
  };
287
288
  //#endregion
288
- //#region src/player.type.ts
289
+ //#region src/bridge/protocol.types.ts
289
290
  const playerListenEvents = [
290
291
  "ready",
291
292
  "timeupdate",
@@ -8,5 +8,4 @@ export declare class IFramePiPPlugin {
8
8
  constructor(containerSelector: string, iframe: HTMLIFrameElement);
9
9
  init(): void;
10
10
  destroy(): void;
11
- destory(): void;
12
11
  }
package/dist/player.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { PlayerEvents, JetstreamPlayerOptions, PlayerEventHandler } from './player.type';
1
+ import type { PlayerEvents } from './bridge/protocol.types';
2
+ import type { JetstreamPlayerOptions, PlayerEventHandler } from './player.types';
2
3
  export declare class JetstreamPlayer {
3
4
  private options;
4
5
  private readonly listeners;
@@ -7,8 +8,8 @@ export declare class JetstreamPlayer {
7
8
  constructor(el: string, options: JetstreamPlayerOptions);
8
9
  private registerEvents;
9
10
  private emit;
10
- on(event: PlayerEvents, handler: PlayerEventHandler): () => void;
11
- off(event: PlayerEvents, handler?: PlayerEventHandler): void;
11
+ on<E extends PlayerEvents>(event: E, handler: PlayerEventHandler<E>): () => void;
12
+ off<E extends PlayerEvents>(event: E, handler?: PlayerEventHandler<E>): void;
12
13
  private request;
13
14
  play(): void;
14
15
  pause(): void;
@@ -19,6 +20,8 @@ export declare class JetstreamPlayer {
19
20
  playPrev(): void;
20
21
  isMuted(): Promise<boolean>;
21
22
  isPaused(): Promise<boolean>;
23
+ getCurrentTime(): Promise<number>;
24
+ /** @deprecated Use {@link getCurrentTime} instead. */
22
25
  getVideoCurrentTime(): Promise<number>;
23
26
  getDuration(): Promise<number>;
24
27
  dispose(): void;
@@ -0,0 +1,10 @@
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;
package/package.json CHANGED
@@ -1,15 +1,10 @@
1
1
  {
2
2
  "name": "@hopecloud/jetstream-player",
3
- "version": "1.3.0",
3
+ "version": "1.4.0-rc.6",
4
4
  "description": "Jetstream Embed Player SDK",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/hopechannel/hopecloud.git",
10
- "directory": "libs/jetstream-player"
11
- },
12
- "homepage": "https://jetstream-player.docs.hope.cloud/",
7
+ "homepage": "https://docs.hope.cloud/libs/jetstream-player/",
13
8
  "engines": {
14
9
  "node": "24"
15
10
  },
@@ -37,14 +32,10 @@
37
32
  "happy-dom": "^20.4.0",
38
33
  "rolldown": "^1.1.4",
39
34
  "typescript": "6.0.3",
40
- "vitepress": "^1.6.4",
41
35
  "vitest": "^4.0.18"
42
36
  },
43
37
  "scripts": {
44
38
  "build": "rolldown --config && tsc --emitDeclarationOnly",
45
- "docs:dev": "vitepress dev docs",
46
- "docs:build": "vitepress build docs",
47
- "docs:preview": "vitepress preview docs",
48
39
  "lint": "eslint",
49
40
  "lint:fix": "eslint --fix",
50
41
  "type-check": "tsc --noEmit",