@hopecloud/jetstream-player 1.1.4 → 1.1.5

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.
@@ -2,7 +2,6 @@ import { type IFrameBuilderSrcOpt } from './player.type';
2
2
  export declare class IFrameBuilderSrc {
3
3
  private options;
4
4
  origin: string;
5
- baseURL: URL;
6
5
  constructor(options: IFrameBuilderSrcOpt);
7
6
  getRegionBasedOrigin(): string;
8
7
  appendPath(path: string, builtUrl: string): string;
@@ -2,7 +2,11 @@ export declare class IFramePiPPlugin {
2
2
  containerSelector: string;
3
3
  iframe: HTMLIFrameElement;
4
4
  private observer;
5
+ private originalParentHeight;
6
+ private originalIframeStyle;
7
+ private parent;
5
8
  constructor(containerSelector: string, iframe: HTMLIFrameElement);
6
9
  init(): void;
10
+ destroy(): void;
7
11
  destory(): void;
8
12
  }
package/dist/index.js CHANGED
@@ -1,2 +1,278 @@
1
- const e=["ready","timeupdate","play","pause","loadedmetadata","ended","volumechange","error"];class t{options;iframe=null;constructor(e){this.options=e,this.iframe=this.generateIframe()}generateIframe(){const e=document.createElement("iframe");return e.allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",e.allowFullscreen=!0,e.frameBorder="0",e.src=this.options.src,e.width=this.options.width||"480px",e.height=this.options.height||"270px",e.classList.add("jet-stream-iframe-player"),e}insertIframe(e){const t=document.querySelector(this.options.selector);t?t.appendChild(e):console.error(`Div with id "${this.options.selector}" not found.`)}}class i{options;origin="https://jstre.am";baseURL=new URL(this.origin);constructor(e){if(""===e.mediaId)throw new Error("mediaId not provided!");this.options=e}getRegionBasedOrigin(){const e=this.baseURL,t=e.hostname.split(".");return this.options.region&&"eu"===this.options.region&&(t.length>2?t.splice(1,0,"eu"):t.splice(0,0,"eu")),e.hostname=t.join("."),e.origin}appendPath(e,t){const i=String(e).trim();return 0===i.indexOf("/")?t+=i:t+=`/${i}`,t}buildSrc(){const e=new URL(`${this.getRegionBasedOrigin()}/embed/${this.options.mediaId}`);return this.options.playerId&&(e.href=this.appendPath(this.options.playerId,e.href)),"eu"===this.options.region&&e.searchParams.append("region","eu"),this.options.audioLang&&e.searchParams.append("audioLang",this.options.audioLang.toLowerCase()),this.options.subtitleLang&&e.searchParams.append("subtitleLang",this.options.subtitleLang.toLowerCase()),e.href}}const s="fixed",r="20px",n="20px",o="330px",a="180px";class h{containerSelector;iframe;observer=null;constructor(e,t){this.containerSelector=e,this.iframe=t,this.containerSelector=e,this.iframe=t,this.init()}init(){if(!this.iframe||!this.containerSelector)return;const e=document.querySelector(this.containerSelector);if(!e)throw new Error("Selector of iframe not found");this.observer=new IntersectionObserver((([{isIntersecting:t}])=>{if(t)e.style.height="auto",this.iframe.removeAttribute("style");else{const t=e.getBoundingClientRect().height;e.style.height=`${t}px`,this.iframe.style.position=s,this.iframe.style.bottom=r,this.iframe.style.right=n,this.iframe.style.width=o,this.iframe.style.height=a}}),{root:null,rootMargin:"0px",threshold:.1}),this.observer.observe(e)}destory(){this.observer&&(this.observer.disconnect(),this.observer=null)}}const d=e=>JSON.parse(e.data),c=e=>JSON.stringify(e),p=e=>{if("string"!=typeof e)return!1;try{return JSON.parse(e),!0}catch(e){return console.log("Error parsing JSON",e),!1}};class m{iframe=null;options;registeredEvents={};iFramePiP=null;constructor(e,s){this.options=s;const r=new i({mediaId:s.mediaId,playerId:s.playerId,region:s.region}).buildSrc(),n=new t({selector:e,src:r,width:this.options.width,height:this.options.height});this.iframe=n.iframe,this.iframe&&(n.insertIframe(this.iframe),this.iframe.addEventListener("load",(()=>{this.registerEvents(),this.subscribeToRegisteredEvents()}),{once:!0}),void 0!==this.options.sticky&&this.options.sticky&&(this.iFramePiP=new h(e,this.iframe)))}registerEvents(){!this.options.events||this.options.events&&Object.keys(this.options.events).length<0||Object.entries(this.options.events).forEach((([e,t])=>{e in this.registeredEvents||(this.registeredEvents[e]=t)}))}subscribeEventHandler(e){if(e.source!==this.iframe.contentWindow)return;const t=d(e),i=t.type;i in this.registeredEvents&&this.registeredEvents[i](t.value)}subscribeToRegisteredEvents(){window.addEventListener("message",this.subscribeEventHandler.bind(this))}dispatch(e){if(!this.iframe||!this.iframe.contentWindow)throw new Error("iframe is not provided or parent window not found!");this.iframe.contentWindow.postMessage(c(e),"*")}getterDispatch(e){return new Promise((t=>{this.dispatch(e),window.addEventListener("message",(i=>{const s=d(i);s.type===e.name&&t(s.value)}),{once:!0})}))}play(){this.dispatch({name:"play"})}pause(){this.dispatch({name:"pause"})}mute(){this.dispatch({name:"mute"})}unmute(){this.dispatch({name:"unmute"})}seekTo(e){this.dispatch({name:"seekTo",params:e})}playNext(){this.dispatch({name:"play-next"})}playPrev(){this.dispatch({name:"play-prev"})}isMuted(){return this.getterDispatch({name:"is-muted"})}isPaused(){return this.getterDispatch({name:"is-paused"})}getVideoCurrentTime(){return this.getterDispatch({name:"get-video-current-time"})}getDuration(){return this.getterDispatch({name:"get-duration"})}dispose(){window.removeEventListener("message",this.subscribeEventHandler.bind(this)),this.registeredEvents={},this.iFramePiP?.destory()}}export{m as JetstreamPlayer,c as denormalizeEventData,p as isValidEventData,d as normalizeEventData,e as playerListenEvents};
2
- //# sourceMappingURL=index.js.map
1
+ //#region src/iframe-builder.ts
2
+ var IFrameBuilder = class {
3
+ options;
4
+ iframe = null;
5
+ constructor(options) {
6
+ this.options = options;
7
+ this.iframe = this.generateIframe();
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.`);
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ //#region src/iframe-builder-src.ts
30
+ var IFrameBuilderSrc = class {
31
+ options;
32
+ origin = "https://jstre.am";
33
+ constructor(options) {
34
+ if (options.mediaId === "") throw new Error("mediaId not provided!");
35
+ if (!options.mediaId.startsWith("jsv:")) throw new Error("mediaId must start with \"jsv:\"");
36
+ if (typeof options.playerId !== "undefined") {
37
+ if (options.playerId === "") throw new Error("playerId not provided!");
38
+ if (!options.playerId.startsWith("jsp:")) throw new Error("playerId must start with \"jsp:\"");
39
+ }
40
+ this.options = options;
41
+ }
42
+ getRegionBasedOrigin() {
43
+ const url = new URL(this.origin);
44
+ const urlParts = url.hostname.split(".");
45
+ if (this.options.region && this.options.region === "eu") if (urlParts.length > 2) urlParts.splice(1, 0, "eu");
46
+ else urlParts.splice(0, 0, "eu");
47
+ url.hostname = urlParts.join(".");
48
+ return url.origin;
49
+ }
50
+ appendPath(path, builtUrl) {
51
+ const pathString = String(path).trim();
52
+ if (pathString.indexOf("/") === 0) builtUrl += pathString;
53
+ else builtUrl += `/${pathString}`;
54
+ return builtUrl;
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
+ }
64
+ };
65
+
66
+ //#endregion
67
+ //#region src/iframe-pip-plugin.ts
68
+ const PiP_STYLES = {
69
+ position: "fixed",
70
+ bottom: "20px",
71
+ right: "20px",
72
+ width: "330px",
73
+ height: "180px"
74
+ };
75
+ var IFramePiPPlugin = class {
76
+ observer = null;
77
+ originalParentHeight = null;
78
+ originalIframeStyle = null;
79
+ parent = null;
80
+ constructor(containerSelector, iframe) {
81
+ this.containerSelector = containerSelector;
82
+ this.iframe = iframe;
83
+ this.containerSelector = containerSelector;
84
+ this.iframe = iframe;
85
+ this.init();
86
+ }
87
+ init() {
88
+ if (!this.iframe || !this.containerSelector) return;
89
+ const parent = document.querySelector(this.containerSelector);
90
+ if (!parent) throw new Error("Selector of iframe not found");
91
+ this.parent = parent;
92
+ this.originalParentHeight = parent.style.height || null;
93
+ this.originalIframeStyle = this.iframe.getAttribute("style");
94
+ this.observer = new IntersectionObserver(([{ isIntersecting }]) => {
95
+ if (!isIntersecting) {
96
+ const height = parent.getBoundingClientRect().height;
97
+ parent.style.height = `${height}px`;
98
+ this.iframe.style.position = PiP_STYLES.position;
99
+ this.iframe.style.bottom = PiP_STYLES.bottom;
100
+ this.iframe.style.right = PiP_STYLES.right;
101
+ this.iframe.style.width = PiP_STYLES.width;
102
+ this.iframe.style.height = PiP_STYLES.height;
103
+ } else {
104
+ parent.style.height = this.originalParentHeight ?? "";
105
+ if (this.originalIframeStyle) this.iframe.setAttribute("style", this.originalIframeStyle);
106
+ else this.iframe.removeAttribute("style");
107
+ }
108
+ }, {
109
+ root: null,
110
+ rootMargin: "0px",
111
+ threshold: .1
112
+ });
113
+ this.observer.observe(parent);
114
+ }
115
+ destroy() {
116
+ if (this.observer) {
117
+ this.observer.disconnect();
118
+ this.observer = null;
119
+ }
120
+ if (this.parent) this.parent.style.height = this.originalParentHeight ?? "";
121
+ if (this.iframe) if (this.originalIframeStyle) this.iframe.setAttribute("style", this.originalIframeStyle);
122
+ else this.iframe.removeAttribute("style");
123
+ }
124
+ destory() {
125
+ this.destroy();
126
+ }
127
+ };
128
+
129
+ //#endregion
130
+ //#region src/utils.ts
131
+ const normalizeEventData = (event) => {
132
+ return JSON.parse(event.data);
133
+ };
134
+ const denormalizeEventData = (data) => {
135
+ return JSON.stringify(data);
136
+ };
137
+ const isValidEventData = (rawData) => {
138
+ if (typeof rawData !== "string") return false;
139
+ try {
140
+ JSON.parse(rawData);
141
+ return true;
142
+ } catch (error) {
143
+ console.log("Error parsing JSON", error);
144
+ return false;
145
+ }
146
+ };
147
+
148
+ //#endregion
149
+ //#region src/player.ts
150
+ var JetstreamPlayer = class {
151
+ iframe = null;
152
+ options;
153
+ registeredEvents = {};
154
+ iFramePiP = null;
155
+ targetOrigin = "*";
156
+ boundSubscribeEventHandler;
157
+ constructor(el, options) {
158
+ this.options = options;
159
+ const src = new IFrameBuilderSrc({
160
+ mediaId: options.mediaId,
161
+ playerId: options.playerId,
162
+ region: options.region
163
+ }).buildSrc();
164
+ try {
165
+ this.targetOrigin = new URL(src).origin;
166
+ } catch {
167
+ this.targetOrigin = "*";
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);
184
+ }
185
+ }
186
+ registerEvents() {
187
+ if (!this.options.events || this.options.events && Object.keys(this.options.events).length === 0) return;
188
+ Object.entries(this.options.events).forEach(([eventName, cb]) => {
189
+ if (!(eventName in this.registeredEvents)) this.registeredEvents[eventName] = cb;
190
+ });
191
+ }
192
+ subscribeEventHandler(event) {
193
+ if (!this.iframe?.contentWindow) return;
194
+ if (event.source !== this.iframe.contentWindow) return;
195
+ if (this.targetOrigin !== "*" && event.origin !== this.targetOrigin) return;
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);
201
+ }
202
+ subscribeToRegisteredEvents() {
203
+ window.addEventListener("message", this.boundSubscribeEventHandler);
204
+ }
205
+ dispatch(event) {
206
+ if (!this.iframe || !this.iframe.contentWindow) throw new Error("iframe is not provided or parent window not found!");
207
+ this.iframe.contentWindow.postMessage(denormalizeEventData(event), this.targetOrigin);
208
+ }
209
+ getterDispatch(getterEvent) {
210
+ return new Promise((resolve) => {
211
+ this.dispatch(getterEvent);
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
+ });
220
+ }
221
+ play() {
222
+ this.dispatch({ name: "play" });
223
+ }
224
+ pause() {
225
+ this.dispatch({ name: "pause" });
226
+ }
227
+ mute() {
228
+ this.dispatch({ name: "mute" });
229
+ }
230
+ unmute() {
231
+ this.dispatch({ name: "unmute" });
232
+ }
233
+ seekTo(seconds) {
234
+ this.dispatch({
235
+ name: "seekTo",
236
+ params: seconds
237
+ });
238
+ }
239
+ playNext() {
240
+ this.dispatch({ name: "play-next" });
241
+ }
242
+ playPrev() {
243
+ this.dispatch({ name: "play-prev" });
244
+ }
245
+ isMuted() {
246
+ return this.getterDispatch({ name: "is-muted" });
247
+ }
248
+ isPaused() {
249
+ return this.getterDispatch({ name: "is-paused" });
250
+ }
251
+ getVideoCurrentTime() {
252
+ return this.getterDispatch({ name: "get-video-current-time" });
253
+ }
254
+ getDuration() {
255
+ return this.getterDispatch({ name: "get-duration" });
256
+ }
257
+ dispose() {
258
+ window.removeEventListener("message", this.boundSubscribeEventHandler);
259
+ this.registeredEvents = {};
260
+ this.iFramePiP?.destroy();
261
+ }
262
+ };
263
+
264
+ //#endregion
265
+ //#region src/player.type.ts
266
+ const playerListenEvents = [
267
+ "ready",
268
+ "timeupdate",
269
+ "play",
270
+ "pause",
271
+ "loadedmetadata",
272
+ "ended",
273
+ "volumechange",
274
+ "error"
275
+ ];
276
+
277
+ //#endregion
278
+ export { JetstreamPlayer, denormalizeEventData, isValidEventData, normalizeEventData, playerListenEvents };
@@ -4,6 +4,8 @@ export declare class JetstreamPlayer {
4
4
  private options;
5
5
  private registeredEvents;
6
6
  private iFramePiP;
7
+ private targetOrigin;
8
+ private boundSubscribeEventHandler;
7
9
  constructor(el: string, options: JetstreamPlayerOptions);
8
10
  private registerEvents;
9
11
  private subscribeEventHandler;
@@ -1,28 +1,28 @@
1
1
  export declare const playerListenEvents: readonly ["ready", "timeupdate", "play", "pause", "loadedmetadata", "ended", "volumechange", "error"];
2
2
  export type PlayerEvents = 'ready' | 'timeupdate' | 'play' | 'pause' | 'loadedmetadata' | 'ended' | 'volumechange' | 'error';
3
3
  export type PlayerGetterEvents = 'is-muted' | 'get-video-current-time' | 'get-duration' | 'is-paused';
4
- export type PlayerEvent = {
4
+ export interface PlayerEvent {
5
5
  type: PlayerEvents | PlayerGetterEvents;
6
6
  value?: unknown;
7
- };
7
+ }
8
8
  export type PlayerCommandEvent = 'play' | 'pause' | 'mute' | 'unmute' | 'seekTo' | 'play-next' | 'play-prev';
9
- export type PlayerCommand = {
9
+ export interface PlayerCommand {
10
10
  name: PlayerCommandEvent | PlayerGetterEvents;
11
11
  params?: unknown;
12
- };
13
- export type IFrameBuilderOpt = {
12
+ }
13
+ export interface IFrameBuilderOpt {
14
14
  selector: string;
15
15
  src: string;
16
16
  width?: string;
17
17
  height?: string;
18
- };
19
- export type IFrameBuilderSrcOpt = {
18
+ }
19
+ export interface IFrameBuilderSrcOpt {
20
20
  mediaId: string;
21
21
  playerId?: string;
22
22
  region?: 'eu';
23
23
  audioLang?: string;
24
24
  subtitleLang?: string;
25
- };
25
+ }
26
26
  export type JetstreamPlayerOptions = {
27
27
  sticky?: boolean;
28
28
  events?: Partial<Record<PlayerEvents, (value?: unknown) => void>>;
package/package.json CHANGED
@@ -1,59 +1,47 @@
1
1
  {
2
2
  "name": "@hopecloud/jetstream-player",
3
- "version": "1.1.4",
4
- "description": "Jetstream Embed Player API",
3
+ "version": "1.1.5",
4
+ "description": "Jetstream Embed Player SDK",
5
5
  "type": "module",
6
+ "main": "dist/index.js",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "engines": {
11
+ "node": "24"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
6
19
  "author": {
7
- "email": "code@deepvision.team",
8
- "name": "DeepVision Code"
20
+ "email": "code@deepvision.software",
21
+ "name": "DV Software"
9
22
  },
10
23
  "repository": {
11
24
  "type": "git",
12
- "url": "https://github.com/deeepvision/hcc-jetstream-player.git"
25
+ "url": "https://github.com/hopechannel/jetstream-player"
13
26
  },
14
- "main": "dist/index.js",
15
- "module": "dist/index.js",
16
- "types": "dist/@types/index.d.ts",
17
- "exports": {
18
- ".": {
19
- "import": {
20
- "types": "./dist/@types/index.d.ts",
21
- "default": "./dist/index.js"
22
- }
23
- }
27
+ "devDependencies": {
28
+ "@deepvision/eslint-plugin": "2.0.0-rc.10",
29
+ "@types/node": "^24.10.9",
30
+ "happy-dom": "^20.4.0",
31
+ "rolldown": "^1.0.0-beta.53",
32
+ "typescript": "^5.9.3",
33
+ "vitepress": "1.6.4",
34
+ "vitest": "^4.0.18"
24
35
  },
25
- "files": [
26
- "dist"
27
- ],
28
36
  "scripts": {
29
- "build": "rollup -c",
30
- "pack": "npm run build && npm pack",
31
- "publish:patch": "npm version patch -m \"release: %s\"",
32
- "publish:minor": "npm version minor -m \"release: %s\"",
33
- "postpublish:patch": "git push origin && git push origin --tags",
34
- "postpublish:minor": "git push origin && git push origin --tags",
37
+ "build": "rolldown --config && tsc --emitDeclarationOnly",
35
38
  "docs:dev": "vitepress dev docs",
36
39
  "docs:build": "vitepress build docs",
37
40
  "docs:preview": "vitepress preview docs",
38
41
  "lint": "eslint",
39
42
  "lint:fix": "eslint --fix",
40
- "typecheck": "tsc --noEmit"
41
- },
42
- "devDependencies": {
43
- "@eslint/eslintrc": "^3.2.0",
44
- "@eslint/js": "^9.17.0",
45
- "@rollup/plugin-terser": "^0.4.4",
46
- "@typescript-eslint/eslint-plugin": "^8.19.1",
47
- "@typescript-eslint/parser": "^8.19.1",
48
- "eslint": "^9.17.0",
49
- "eslint-config-prettier": "^9.1.0",
50
- "globals": "^15.14.0",
51
- "prettier": "3.4.2",
52
- "rimraf": "^6.0.1",
53
- "rollup": "^4.30.1",
54
- "rollup-plugin-delete": "^2.1.0",
55
- "rollup-plugin-typescript2": "^0.36.0",
56
- "typescript": "^5.7.3",
57
- "vitepress": "1.5.0"
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest",
45
+ "test:run": "vitest run"
58
46
  }
59
- }
47
+ }
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
File without changes
File without changes