@hopecloud/jetstream-player 1.1.5 → 1.3.0-rc.3
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 +76 -1
- package/dist/embed-frame.d.ts +7 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +171 -149
- package/dist/player-bridge.d.ts +13 -0
- package/dist/player-transport.d.ts +5 -0
- package/dist/player.d.ts +7 -9
- package/dist/player.type.d.ts +7 -9
- package/dist/protocol.d.ts +3 -0
- package/package.json +21 -14
- package/dist/iframe-builder-src.d.ts +0 -9
- package/dist/iframe-builder.d.ts +0 -8
- package/dist/utils.d.ts +0 -3
package/README.md
CHANGED
|
@@ -1 +1,76 @@
|
|
|
1
|
-
|
|
1
|
+
# @hopecloud/jetstream-player
|
|
2
|
+
|
|
3
|
+
Control an embedded [Jetstream](https://jetstream-player.docs.hope.cloud/) 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.
|
|
6
|
+
|
|
7
|
+
**[Full documentation →](https://jetstream-player.docs.hope.cloud/)**
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i @hopecloud/jetstream-player
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { JetstreamPlayer } from '@hopecloud/jetstream-player';
|
|
19
|
+
|
|
20
|
+
const player = new JetstreamPlayer('#player', {
|
|
21
|
+
mediaId: 'jsv:xxxxxxxxxx',
|
|
22
|
+
events: {
|
|
23
|
+
ready: () => console.log('ready'),
|
|
24
|
+
timeupdate: (time) => console.log('time', time),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
player.play();
|
|
29
|
+
const muted = await player.isMuted();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`mediaId` is the only required option; the library creates and mounts the iframe
|
|
33
|
+
at the given selector.
|
|
34
|
+
|
|
35
|
+
## What you can do
|
|
36
|
+
|
|
37
|
+
- **Commands** — `play()`, `pause()`, `mute()`, `unmute()`, `seekTo(seconds)`,
|
|
38
|
+
`playNext()`, `playPrev()`.
|
|
39
|
+
- **Getters** (Promise-based) — `isMuted()`, `isPaused()`,
|
|
40
|
+
`getVideoCurrentTime()`, `getDuration()`. They reject after 5s if the player
|
|
41
|
+
doesn't reply.
|
|
42
|
+
- **Events** — subscribe via `options.events`: `ready`, `timeupdate`, `play`,
|
|
43
|
+
`pause`, `loadedmetadata`, `ended`, `volumechange`, `error`.
|
|
44
|
+
- **Teardown** — `dispose()` removes listeners and tears down Picture-in-Picture.
|
|
45
|
+
|
|
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)
|
|
48
|
+
reference for the full list.
|
|
49
|
+
|
|
50
|
+
## Targeting a non-prod embed
|
|
51
|
+
|
|
52
|
+
By default the player loads the production embed. Point it at another host
|
|
53
|
+
(staging/dev) with `origin`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
new JetstreamPlayer('#player', {
|
|
57
|
+
mediaId: 'jsv:xxxxxxxxxx',
|
|
58
|
+
origin: 'https://embed-stage.jetstream.studio',
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Communication is cross-origin `postMessage`, and the SDK learns the iframe's
|
|
63
|
+
real origin at runtime — so it keeps working even when the embed redirects.
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pnpm install
|
|
69
|
+
pnpm build # rolldown + .d.ts
|
|
70
|
+
pnpm test # vitest
|
|
71
|
+
pnpm lint
|
|
72
|
+
pnpm typecheck
|
|
73
|
+
pnpm docs:dev # vitepress docs
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Architecture decisions are recorded in [`adr/`](./adr).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EmbedFrameOptions } from './player.type';
|
|
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;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,68 +1,43 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
generateIframe() {
|
|
10
|
-
const iframe = document.createElement("iframe");
|
|
11
|
-
iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
|
|
12
|
-
iframe.allowFullscreen = true;
|
|
13
|
-
iframe.frameBorder = "0";
|
|
14
|
-
iframe.title = "Jetstream player";
|
|
15
|
-
iframe.src = this.options.src;
|
|
16
|
-
iframe.width = this.options.width || "480px";
|
|
17
|
-
iframe.height = this.options.height || "270px";
|
|
18
|
-
iframe.classList.add("jet-stream-iframe-player");
|
|
19
|
-
return iframe;
|
|
20
|
-
}
|
|
21
|
-
insertIframe(iframe) {
|
|
22
|
-
const parent = document.querySelector(this.options.selector);
|
|
23
|
-
if (parent) parent.appendChild(iframe);
|
|
24
|
-
else console.error(`Element matching selector "${this.options.selector}" not found.`);
|
|
1
|
+
//#region src/embed-frame.ts
|
|
2
|
+
const DEFAULT_ORIGIN = "https://jstre.am";
|
|
3
|
+
const regionOrigin = (origin, region) => {
|
|
4
|
+
const url = new URL(origin);
|
|
5
|
+
if (region === "eu") {
|
|
6
|
+
const parts = url.hostname.split(".");
|
|
7
|
+
parts.splice(parts.length > 2 ? 1 : 0, 0, "eu");
|
|
8
|
+
url.hostname = parts.join(".");
|
|
25
9
|
}
|
|
10
|
+
return url.origin;
|
|
26
11
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
buildSrc() {
|
|
57
|
-
const url = new URL(`${this.getRegionBasedOrigin()}/embed/${this.options.mediaId}`);
|
|
58
|
-
if (this.options.playerId) url.href = this.appendPath(this.options.playerId, url.href);
|
|
59
|
-
if (this.options.region === "eu") url.searchParams.append("region", "eu");
|
|
60
|
-
if (this.options.audioLang) url.searchParams.append("audioLang", this.options.audioLang.toLowerCase());
|
|
61
|
-
if (this.options.subtitleLang) url.searchParams.append("subtitleLang", this.options.subtitleLang.toLowerCase());
|
|
62
|
-
return url.href;
|
|
63
|
-
}
|
|
12
|
+
const buildEmbedSrc = (options) => {
|
|
13
|
+
if (options.mediaId === "") throw new Error("mediaId not provided!");
|
|
14
|
+
const origin = regionOrigin(options.origin ?? DEFAULT_ORIGIN, options.region);
|
|
15
|
+
const path = options.playerId ? `/embed/${options.mediaId}/${options.playerId}` : `/embed/${options.mediaId}`;
|
|
16
|
+
const url = new URL(`${origin}${path}`);
|
|
17
|
+
if (options.region === "eu") url.searchParams.append("region", "eu");
|
|
18
|
+
if (options.audioLang) url.searchParams.append("audioLang", options.audioLang.toLowerCase());
|
|
19
|
+
if (options.subtitleLang) url.searchParams.append("subtitleLang", options.subtitleLang.toLowerCase());
|
|
20
|
+
return url.href;
|
|
21
|
+
};
|
|
22
|
+
const mountEmbedFrame = (selector, options) => {
|
|
23
|
+
const src = buildEmbedSrc(options);
|
|
24
|
+
const iframe = document.createElement("iframe");
|
|
25
|
+
iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
|
|
26
|
+
iframe.allowFullscreen = true;
|
|
27
|
+
iframe.frameBorder = "0";
|
|
28
|
+
iframe.title = "Jetstream player";
|
|
29
|
+
iframe.src = src;
|
|
30
|
+
iframe.width = options.width || "480px";
|
|
31
|
+
iframe.height = options.height || "270px";
|
|
32
|
+
iframe.classList.add("jet-stream-iframe-player");
|
|
33
|
+
const parent = document.querySelector(selector);
|
|
34
|
+
if (parent) parent.appendChild(iframe);
|
|
35
|
+
else console.error(`Element matching selector "${selector}" not found.`);
|
|
36
|
+
return {
|
|
37
|
+
iframe,
|
|
38
|
+
src
|
|
39
|
+
};
|
|
64
40
|
};
|
|
65
|
-
|
|
66
41
|
//#endregion
|
|
67
42
|
//#region src/iframe-pip-plugin.ts
|
|
68
43
|
const PiP_STYLES = {
|
|
@@ -73,6 +48,8 @@ const PiP_STYLES = {
|
|
|
73
48
|
height: "180px"
|
|
74
49
|
};
|
|
75
50
|
var IFramePiPPlugin = class {
|
|
51
|
+
containerSelector;
|
|
52
|
+
iframe;
|
|
76
53
|
observer = null;
|
|
77
54
|
originalParentHeight = null;
|
|
78
55
|
originalIframeStyle = null;
|
|
@@ -125,142 +102,188 @@ var IFramePiPPlugin = class {
|
|
|
125
102
|
this.destroy();
|
|
126
103
|
}
|
|
127
104
|
};
|
|
128
|
-
|
|
129
105
|
//#endregion
|
|
130
|
-
//#region src/
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const denormalizeEventData = (data) => {
|
|
135
|
-
return JSON.stringify(data);
|
|
136
|
-
};
|
|
137
|
-
const isValidEventData = (rawData) => {
|
|
138
|
-
if (typeof rawData !== "string") return false;
|
|
106
|
+
//#region src/protocol.ts
|
|
107
|
+
const encode = (command) => JSON.stringify(command);
|
|
108
|
+
const decode = (raw) => {
|
|
109
|
+
if (typeof raw !== "string") return null;
|
|
139
110
|
try {
|
|
140
|
-
JSON.parse(
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return
|
|
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();
|
|
145
165
|
}
|
|
146
166
|
};
|
|
147
|
-
|
|
167
|
+
//#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
|
+
});
|
|
148
184
|
//#endregion
|
|
149
185
|
//#region src/player.ts
|
|
150
186
|
var JetstreamPlayer = class {
|
|
151
|
-
iframe = null;
|
|
152
187
|
options;
|
|
153
|
-
|
|
188
|
+
listeners = /* @__PURE__ */ new Map();
|
|
154
189
|
iFramePiP = null;
|
|
155
|
-
|
|
156
|
-
boundSubscribeEventHandler;
|
|
190
|
+
bridge = null;
|
|
157
191
|
constructor(el, options) {
|
|
158
192
|
this.options = options;
|
|
159
|
-
const src =
|
|
160
|
-
|
|
161
|
-
playerId: options.playerId,
|
|
162
|
-
region: options.region
|
|
163
|
-
}).buildSrc();
|
|
193
|
+
const { iframe, src } = mountEmbedFrame(el, options);
|
|
194
|
+
let initialTargetOrigin;
|
|
164
195
|
try {
|
|
165
|
-
|
|
196
|
+
initialTargetOrigin = new URL(src).origin;
|
|
166
197
|
} catch {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
const iframeBuilder = new IFrameBuilder({
|
|
170
|
-
selector: el,
|
|
171
|
-
src,
|
|
172
|
-
width: this.options.width,
|
|
173
|
-
height: this.options.height
|
|
174
|
-
});
|
|
175
|
-
this.iframe = iframeBuilder.iframe;
|
|
176
|
-
this.boundSubscribeEventHandler = this.subscribeEventHandler.bind(this);
|
|
177
|
-
if (this.iframe) {
|
|
178
|
-
iframeBuilder.insertIframe(this.iframe);
|
|
179
|
-
this.iframe.addEventListener("load", () => {
|
|
180
|
-
this.registerEvents();
|
|
181
|
-
this.subscribeToRegisteredEvents();
|
|
182
|
-
}, { once: true });
|
|
183
|
-
if (typeof this.options.sticky !== "undefined" && this.options.sticky) this.iFramePiP = new IFramePiPPlugin(el, this.iframe);
|
|
198
|
+
initialTargetOrigin = "*";
|
|
184
199
|
}
|
|
200
|
+
this.bridge = new PlayerBridge(windowTransport(iframe), initialTargetOrigin);
|
|
201
|
+
iframe.addEventListener("load", () => {
|
|
202
|
+
this.registerEvents();
|
|
203
|
+
this.bridge?.subscribe((event) => {
|
|
204
|
+
this.emit(event.type, event.value);
|
|
205
|
+
});
|
|
206
|
+
}, { once: true });
|
|
207
|
+
if (typeof this.options.sticky !== "undefined" && this.options.sticky) this.iFramePiP = new IFramePiPPlugin(el, iframe);
|
|
185
208
|
}
|
|
186
209
|
registerEvents() {
|
|
187
|
-
if (!this.options.events
|
|
188
|
-
Object.entries(this.options.events).forEach(([eventName,
|
|
189
|
-
if (
|
|
210
|
+
if (!this.options.events) return;
|
|
211
|
+
Object.entries(this.options.events).forEach(([eventName, handler]) => {
|
|
212
|
+
if (handler) this.on(eventName, handler);
|
|
190
213
|
});
|
|
191
214
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
if (!isValidEventData(event.data)) return;
|
|
197
|
-
const data = normalizeEventData(event);
|
|
198
|
-
const eventType = data.type;
|
|
199
|
-
const handler = this.registeredEvents[eventType];
|
|
200
|
-
if (handler) handler(data.value);
|
|
215
|
+
emit(type, value) {
|
|
216
|
+
const handlers = this.listeners.get(type);
|
|
217
|
+
if (!handlers) return;
|
|
218
|
+
[...handlers].forEach((handler) => handler(value));
|
|
201
219
|
}
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
on(event, handler) {
|
|
221
|
+
let handlers = this.listeners.get(event);
|
|
222
|
+
if (!handlers) {
|
|
223
|
+
handlers = /* @__PURE__ */ new Set();
|
|
224
|
+
this.listeners.set(event, handlers);
|
|
225
|
+
}
|
|
226
|
+
handlers.add(handler);
|
|
227
|
+
return () => {
|
|
228
|
+
this.off(event, handler);
|
|
229
|
+
};
|
|
204
230
|
}
|
|
205
|
-
|
|
206
|
-
if (!
|
|
207
|
-
|
|
231
|
+
off(event, handler) {
|
|
232
|
+
if (!handler) {
|
|
233
|
+
this.listeners.delete(event);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const handlers = this.listeners.get(event);
|
|
237
|
+
if (!handlers) return;
|
|
238
|
+
handlers.delete(handler);
|
|
239
|
+
if (handlers.size === 0) this.listeners.delete(event);
|
|
208
240
|
}
|
|
209
|
-
|
|
210
|
-
return new
|
|
211
|
-
|
|
212
|
-
window.addEventListener("message", (event) => {
|
|
213
|
-
if (event.source !== this.iframe?.contentWindow) return;
|
|
214
|
-
if (this.targetOrigin !== "*" && event.origin !== this.targetOrigin) return;
|
|
215
|
-
if (!isValidEventData(event.data)) return;
|
|
216
|
-
const data = normalizeEventData(event);
|
|
217
|
-
if (data.type === getterEvent.name) resolve(data.value);
|
|
218
|
-
}, { once: true });
|
|
219
|
-
});
|
|
241
|
+
request(command) {
|
|
242
|
+
if (!this.bridge) return Promise.reject(/* @__PURE__ */ new Error("iframe is not provided or parent window not found!"));
|
|
243
|
+
return this.bridge.request(command);
|
|
220
244
|
}
|
|
221
245
|
play() {
|
|
222
|
-
this.
|
|
246
|
+
this.bridge?.send({ name: "play" });
|
|
223
247
|
}
|
|
224
248
|
pause() {
|
|
225
|
-
this.
|
|
249
|
+
this.bridge?.send({ name: "pause" });
|
|
226
250
|
}
|
|
227
251
|
mute() {
|
|
228
|
-
this.
|
|
252
|
+
this.bridge?.send({ name: "mute" });
|
|
229
253
|
}
|
|
230
254
|
unmute() {
|
|
231
|
-
this.
|
|
255
|
+
this.bridge?.send({ name: "unmute" });
|
|
232
256
|
}
|
|
233
257
|
seekTo(seconds) {
|
|
234
|
-
this.
|
|
258
|
+
this.bridge?.send({
|
|
235
259
|
name: "seekTo",
|
|
236
260
|
params: seconds
|
|
237
261
|
});
|
|
238
262
|
}
|
|
239
263
|
playNext() {
|
|
240
|
-
this.
|
|
264
|
+
this.bridge?.send({ name: "play-next" });
|
|
241
265
|
}
|
|
242
266
|
playPrev() {
|
|
243
|
-
this.
|
|
267
|
+
this.bridge?.send({ name: "play-prev" });
|
|
244
268
|
}
|
|
245
269
|
isMuted() {
|
|
246
|
-
return this.
|
|
270
|
+
return this.request({ name: "is-muted" });
|
|
247
271
|
}
|
|
248
272
|
isPaused() {
|
|
249
|
-
return this.
|
|
273
|
+
return this.request({ name: "is-paused" });
|
|
250
274
|
}
|
|
251
275
|
getVideoCurrentTime() {
|
|
252
|
-
return this.
|
|
276
|
+
return this.request({ name: "get-video-current-time" });
|
|
253
277
|
}
|
|
254
278
|
getDuration() {
|
|
255
|
-
return this.
|
|
279
|
+
return this.request({ name: "get-duration" });
|
|
256
280
|
}
|
|
257
281
|
dispose() {
|
|
258
|
-
|
|
259
|
-
this.
|
|
282
|
+
this.bridge?.dispose();
|
|
283
|
+
this.listeners.clear();
|
|
260
284
|
this.iFramePiP?.destroy();
|
|
261
285
|
}
|
|
262
286
|
};
|
|
263
|
-
|
|
264
287
|
//#endregion
|
|
265
288
|
//#region src/player.type.ts
|
|
266
289
|
const playerListenEvents = [
|
|
@@ -273,6 +296,5 @@ const playerListenEvents = [
|
|
|
273
296
|
"volumechange",
|
|
274
297
|
"error"
|
|
275
298
|
];
|
|
276
|
-
|
|
277
299
|
//#endregion
|
|
278
|
-
export { JetstreamPlayer,
|
|
300
|
+
export { JetstreamPlayer, playerListenEvents };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PlayerCommand, PlayerEvent } from './player.type';
|
|
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
|
+
}
|
package/dist/player.d.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import type { JetstreamPlayerOptions } from './player.type';
|
|
1
|
+
import type { PlayerEvents, JetstreamPlayerOptions, PlayerEventHandler } from './player.type';
|
|
2
2
|
export declare class JetstreamPlayer {
|
|
3
|
-
private iframe;
|
|
4
3
|
private options;
|
|
5
|
-
private
|
|
4
|
+
private readonly listeners;
|
|
6
5
|
private iFramePiP;
|
|
7
|
-
private
|
|
8
|
-
private boundSubscribeEventHandler;
|
|
6
|
+
private bridge;
|
|
9
7
|
constructor(el: string, options: JetstreamPlayerOptions);
|
|
10
8
|
private registerEvents;
|
|
11
|
-
private
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
private
|
|
9
|
+
private emit;
|
|
10
|
+
on(event: PlayerEvents, handler: PlayerEventHandler): () => void;
|
|
11
|
+
off(event: PlayerEvents, handler?: PlayerEventHandler): void;
|
|
12
|
+
private request;
|
|
15
13
|
play(): void;
|
|
16
14
|
pause(): void;
|
|
17
15
|
mute(): void;
|
package/dist/player.type.d.ts
CHANGED
|
@@ -5,25 +5,23 @@ export interface PlayerEvent {
|
|
|
5
5
|
type: PlayerEvents | PlayerGetterEvents;
|
|
6
6
|
value?: unknown;
|
|
7
7
|
}
|
|
8
|
+
export type PlayerEventHandler = (value?: unknown) => void;
|
|
8
9
|
export type PlayerCommandEvent = 'play' | 'pause' | 'mute' | 'unmute' | 'seekTo' | 'play-next' | 'play-prev';
|
|
9
10
|
export interface PlayerCommand {
|
|
10
11
|
name: PlayerCommandEvent | PlayerGetterEvents;
|
|
11
12
|
params?: unknown;
|
|
12
13
|
}
|
|
13
|
-
export interface
|
|
14
|
-
selector: string;
|
|
15
|
-
src: string;
|
|
16
|
-
width?: string;
|
|
17
|
-
height?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface IFrameBuilderSrcOpt {
|
|
14
|
+
export interface EmbedFrameOptions {
|
|
20
15
|
mediaId: string;
|
|
21
16
|
playerId?: string;
|
|
22
17
|
region?: 'eu';
|
|
23
18
|
audioLang?: string;
|
|
24
19
|
subtitleLang?: string;
|
|
20
|
+
origin?: string;
|
|
21
|
+
width?: string;
|
|
22
|
+
height?: string;
|
|
25
23
|
}
|
|
26
24
|
export type JetstreamPlayerOptions = {
|
|
27
25
|
sticky?: boolean;
|
|
28
|
-
events?: Partial<Record<PlayerEvents,
|
|
29
|
-
} &
|
|
26
|
+
events?: Partial<Record<PlayerEvents, PlayerEventHandler>>;
|
|
27
|
+
} & EmbedFrameOptions;
|
package/package.json
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hopecloud/jetstream-player",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0-rc.3",
|
|
4
4
|
"description": "Jetstream Embed Player SDK",
|
|
5
5
|
"type": "module",
|
|
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/",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "24"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
6
19
|
"main": "dist/index.js",
|
|
7
20
|
"files": [
|
|
8
21
|
"dist"
|
|
9
22
|
],
|
|
10
|
-
"engines": {
|
|
11
|
-
"node": "24"
|
|
12
|
-
},
|
|
13
23
|
"exports": {
|
|
14
24
|
".": {
|
|
15
25
|
"types": "./dist/index.d.ts",
|
|
@@ -20,17 +30,14 @@
|
|
|
20
30
|
"email": "code@deepvision.software",
|
|
21
31
|
"name": "DV Software"
|
|
22
32
|
},
|
|
23
|
-
"repository": {
|
|
24
|
-
"type": "git",
|
|
25
|
-
"url": "https://github.com/hopechannel/jetstream-player"
|
|
26
|
-
},
|
|
27
33
|
"devDependencies": {
|
|
28
|
-
"@deepvision/eslint-plugin": "2.0.
|
|
29
|
-
"@types/node": "
|
|
34
|
+
"@deepvision/eslint-plugin": "2.0.3",
|
|
35
|
+
"@types/node": "24.12.0",
|
|
36
|
+
"eslint": "10.1.0",
|
|
30
37
|
"happy-dom": "^20.4.0",
|
|
31
|
-
"rolldown": "^1.
|
|
32
|
-
"typescript": "
|
|
33
|
-
"vitepress": "1.6.4",
|
|
38
|
+
"rolldown": "^1.1.4",
|
|
39
|
+
"typescript": "6.0.3",
|
|
40
|
+
"vitepress": "^1.6.4",
|
|
34
41
|
"vitest": "^4.0.18"
|
|
35
42
|
},
|
|
36
43
|
"scripts": {
|
|
@@ -40,7 +47,7 @@
|
|
|
40
47
|
"docs:preview": "vitepress preview docs",
|
|
41
48
|
"lint": "eslint",
|
|
42
49
|
"lint:fix": "eslint --fix",
|
|
43
|
-
"
|
|
50
|
+
"type-check": "tsc --noEmit",
|
|
44
51
|
"test": "vitest",
|
|
45
52
|
"test:run": "vitest run"
|
|
46
53
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type IFrameBuilderSrcOpt } from './player.type';
|
|
2
|
-
export declare class IFrameBuilderSrc {
|
|
3
|
-
private options;
|
|
4
|
-
origin: string;
|
|
5
|
-
constructor(options: IFrameBuilderSrcOpt);
|
|
6
|
-
getRegionBasedOrigin(): string;
|
|
7
|
-
appendPath(path: string, builtUrl: string): string;
|
|
8
|
-
buildSrc(): string;
|
|
9
|
-
}
|
package/dist/iframe-builder.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type IFrameBuilderOpt } from './player.type';
|
|
2
|
-
export declare class IFrameBuilder {
|
|
3
|
-
options: IFrameBuilderOpt;
|
|
4
|
-
iframe: HTMLIFrameElement | null;
|
|
5
|
-
constructor(options: IFrameBuilderOpt);
|
|
6
|
-
private generateIframe;
|
|
7
|
-
insertIframe(iframe: HTMLIFrameElement): void;
|
|
8
|
-
}
|
package/dist/utils.d.ts
DELETED