@scalemule/gallop 0.0.1 → 0.0.2

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.
@@ -1,38 +0,0 @@
1
- // src/core/EventEmitter.ts
2
- var EventEmitter = class {
3
- constructor() {
4
- this.listeners = /* @__PURE__ */ new Map();
5
- }
6
- on(event, callback) {
7
- if (!this.listeners.has(event)) {
8
- this.listeners.set(event, /* @__PURE__ */ new Set());
9
- }
10
- this.listeners.get(event).add(callback);
11
- }
12
- off(event, callback) {
13
- this.listeners.get(event)?.delete(callback);
14
- }
15
- once(event, callback) {
16
- const wrapper = ((...args) => {
17
- this.off(event, wrapper);
18
- callback(...args);
19
- });
20
- this.on(event, wrapper);
21
- }
22
- emit(event, ...args) {
23
- const set = this.listeners.get(event);
24
- if (!set) return;
25
- for (const listener of set) {
26
- try {
27
- listener(...args);
28
- } catch (err) {
29
- console.error(`[Gallop] Error in ${event} listener:`, err);
30
- }
31
- }
32
- }
33
- removeAllListeners() {
34
- this.listeners.clear();
35
- }
36
- };
37
-
38
- export { EventEmitter };
@@ -1,263 +0,0 @@
1
- import { EventEmitter } from './chunk-SQPWH6EI.js';
2
-
3
- // src/iframe/GallopIframeController.ts
4
- var HASH_CONFIG_ALLOWLIST = [
5
- "autoplay",
6
- "muted",
7
- "loop",
8
- "controls",
9
- "startTime",
10
- "preferredQuality",
11
- "aspectRatio",
12
- "theme",
13
- "debug",
14
- "doNotTrack",
15
- "analytics",
16
- "pageUrl"
17
- ];
18
- var MAX_HASH_CONFIG_BYTES = 4096;
19
- function sanitizeConfigForHash(config) {
20
- const safe = {};
21
- for (const key of HASH_CONFIG_ALLOWLIST) {
22
- if (config[key] !== void 0) {
23
- safe[key] = config[key];
24
- }
25
- }
26
- return safe;
27
- }
28
- var GallopIframeController = class extends EventEmitter {
29
- constructor(container, config) {
30
- super();
31
- this.container = container;
32
- this.config = config;
33
- this.targetOrigin = null;
34
- this.isConnected = false;
35
- this.pendingCalls = /* @__PURE__ */ new Map();
36
- this.stateCache = {};
37
- this.cachedQualityLevels = [];
38
- this.cachedCurrentQuality = -1;
39
- this.cachedDiagnostics = {};
40
- this.sessionId = Math.random().toString(36).substring(7);
41
- this.handshakeInterval = null;
42
- this.iframe = this.createIframe();
43
- this.container.appendChild(this.iframe);
44
- this.boundHandler = this.handleMessage.bind(this);
45
- window.addEventListener("message", this.boundHandler);
46
- this.startHandshake();
47
- }
48
- createIframe() {
49
- const iframe = document.createElement("iframe");
50
- const videoId = this.config.videoId || "unknown";
51
- const baseUrl = this.config.apiBaseUrl ? `${this.config.apiBaseUrl.replace(/\/$/, "")}/v1/videos/embed` : "https://api.scalemule.com/v1/videos/embed";
52
- const url = new URL(`${baseUrl}/${videoId}`);
53
- if (this.config.embedToken) {
54
- url.searchParams.set("token", this.config.embedToken);
55
- }
56
- const safeConfig = sanitizeConfigForHash(this.config);
57
- if (!safeConfig.pageUrl && typeof window !== "undefined") {
58
- safeConfig.pageUrl = window.location.href;
59
- }
60
- const json = JSON.stringify(safeConfig);
61
- const encoded = btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
62
- if (encoded.length > MAX_HASH_CONFIG_BYTES) {
63
- console.warn("[Gallop] Config too large for hash, using defaults");
64
- } else {
65
- url.hash = `config=${encoded}`;
66
- }
67
- iframe.src = url.toString();
68
- iframe.style.width = "100%";
69
- iframe.style.height = "100%";
70
- iframe.style.border = "0";
71
- iframe.allow = "autoplay; fullscreen; picture-in-picture; encrypted-media";
72
- iframe.title = "Gallop Video Player";
73
- return iframe;
74
- }
75
- startHandshake() {
76
- let attempts = 0;
77
- this.handshakeInterval = setInterval(() => {
78
- if (this.isConnected || attempts >= 3) {
79
- if (this.handshakeInterval) clearInterval(this.handshakeInterval);
80
- this.handshakeInterval = null;
81
- if (!this.isConnected && attempts >= 3) {
82
- this.emit("error", { code: "CONNECT_TIMEOUT", message: "iframe handshake timed out" });
83
- }
84
- return;
85
- }
86
- this.sendMessage("gallop:ping", { sessionId: this.sessionId }, "*");
87
- attempts++;
88
- }, 1e3);
89
- }
90
- handleMessage(event) {
91
- const data = event.data;
92
- if (!data || typeof data.type !== "string" || !data.type.startsWith("gallop:")) return;
93
- if (event.source !== this.iframe.contentWindow) return;
94
- if (!this.isConnected) {
95
- if (data.type === "gallop:hello") {
96
- this.sendMessage("gallop:ping", { sessionId: this.sessionId }, "*");
97
- return;
98
- }
99
- if (data.type === "gallop:pong") {
100
- this.targetOrigin = event.origin;
101
- this.isConnected = true;
102
- if (this.handshakeInterval) {
103
- clearInterval(this.handshakeInterval);
104
- this.handshakeInterval = null;
105
- }
106
- if (data.diagnostics) {
107
- this.cachedDiagnostics = data.diagnostics;
108
- }
109
- return;
110
- }
111
- if (data.type === "gallop:error") {
112
- this.emit("error", { code: data.code || "EMBED_LOAD_FAILED", message: data.message || "Embed error" });
113
- return;
114
- }
115
- return;
116
- }
117
- if (event.origin !== this.targetOrigin) return;
118
- switch (data.type) {
119
- case "gallop:event":
120
- if (data.event === "qualitylevels" && data.data?.levels) {
121
- this.cachedQualityLevels = data.data.levels;
122
- }
123
- if (data.event === "qualitychange" && data.data?.level) {
124
- this.cachedCurrentQuality = data.data.level.index ?? -1;
125
- }
126
- this.emit(data.event, data.data);
127
- break;
128
- case "gallop:state":
129
- this.stateCache = data.state;
130
- break;
131
- case "gallop:response":
132
- this.handleResponse(data);
133
- break;
134
- case "gallop:error":
135
- this.emit("error", { code: data.code || "UNKNOWN", message: data.message || "Unknown error" });
136
- break;
137
- }
138
- }
139
- handleResponse(data) {
140
- const call = this.pendingCalls.get(data.callId);
141
- if (call) {
142
- clearTimeout(call.timeout);
143
- if (data.error) {
144
- call.reject(data.error);
145
- } else {
146
- call.resolve(data.result);
147
- }
148
- this.pendingCalls.delete(data.callId);
149
- }
150
- }
151
- sendMessage(type, payload = {}, overrideOrigin) {
152
- const target = overrideOrigin || this.targetOrigin;
153
- if (!target) return;
154
- this.iframe.contentWindow?.postMessage({
155
- type,
156
- ...payload,
157
- version: 1
158
- }, target);
159
- }
160
- callMethod(method, ...args) {
161
- return new Promise((resolve, reject) => {
162
- if (!this.isConnected && method !== "destroy") {
163
- reject(new Error("Player not connected"));
164
- return;
165
- }
166
- if (this.pendingCalls.size >= 20) {
167
- reject(new Error("Too many pending calls"));
168
- return;
169
- }
170
- const callId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
171
- const timeout = setTimeout(() => {
172
- this.pendingCalls.delete(callId);
173
- reject(new Error(`Method ${method} timed out`));
174
- }, 5e3);
175
- this.pendingCalls.set(callId, { resolve, reject, timeout });
176
- this.sendMessage("gallop:method", { method, args, callId });
177
- });
178
- }
179
- // --- GallopPlayer Implementation ---
180
- get currentTime() {
181
- return this.stateCache.currentTime ?? 0;
182
- }
183
- get duration() {
184
- return this.stateCache.duration ?? 0;
185
- }
186
- get paused() {
187
- return this.stateCache.paused ?? true;
188
- }
189
- get status() {
190
- return this.stateCache.status ?? "loading";
191
- }
192
- get isFullscreen() {
193
- return this.stateCache.isFullscreen ?? false;
194
- }
195
- get volume() {
196
- return this.stateCache.volume ?? 1;
197
- }
198
- set volume(v) {
199
- void this.callMethod("setVolume", v);
200
- }
201
- get muted() {
202
- return this.stateCache.muted ?? false;
203
- }
204
- set muted(m) {
205
- void this.callMethod("setMuted", m);
206
- }
207
- get playbackRate() {
208
- return this.stateCache.playbackRate ?? 1;
209
- }
210
- set playbackRate(r) {
211
- void this.callMethod("setPlaybackRate", r);
212
- }
213
- play() {
214
- return this.callMethod("play");
215
- }
216
- pause() {
217
- return this.callMethod("pause");
218
- }
219
- seek(time) {
220
- return this.callMethod("seek", time);
221
- }
222
- setQualityLevel(index) {
223
- return this.callMethod("setQualityLevel", index);
224
- }
225
- setAutoQuality() {
226
- return this.callMethod("setAutoQuality");
227
- }
228
- toggleFullscreen() {
229
- return this.callMethod("toggleFullscreen");
230
- }
231
- getQualityLevels() {
232
- return this.cachedQualityLevels;
233
- }
234
- getCurrentQuality() {
235
- return this.cachedCurrentQuality;
236
- }
237
- getDiagnostics() {
238
- return this.cachedDiagnostics;
239
- }
240
- query(key) {
241
- return this.callMethod("query", key);
242
- }
243
- get connected() {
244
- return this.isConnected;
245
- }
246
- destroy() {
247
- if (this.handshakeInterval) {
248
- clearInterval(this.handshakeInterval);
249
- this.handshakeInterval = null;
250
- }
251
- for (const [, call] of this.pendingCalls) {
252
- clearTimeout(call.timeout);
253
- call.reject(new Error("Player destroyed"));
254
- }
255
- this.pendingCalls.clear();
256
- window.removeEventListener("message", this.boundHandler);
257
- this.iframe.remove();
258
- this.emit("destroy");
259
- this.removeAllListeners();
260
- }
261
- };
262
-
263
- export { GallopIframeController };