@lumra/core 0.1.0
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/dist/index.cjs +271 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +97 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/events.ts
|
|
4
|
+
var EventEmitter = class {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.listeners = {};
|
|
7
|
+
}
|
|
8
|
+
on(event, listener) {
|
|
9
|
+
if (!this.listeners[event]) {
|
|
10
|
+
this.listeners[event] = /* @__PURE__ */ new Set();
|
|
11
|
+
}
|
|
12
|
+
this.listeners[event].add(listener);
|
|
13
|
+
}
|
|
14
|
+
off(event, listener) {
|
|
15
|
+
this.listeners[event]?.delete(listener);
|
|
16
|
+
}
|
|
17
|
+
once(event, listener) {
|
|
18
|
+
const wrapper = (data) => {
|
|
19
|
+
listener(data);
|
|
20
|
+
this.off(event, wrapper);
|
|
21
|
+
};
|
|
22
|
+
this.on(event, wrapper);
|
|
23
|
+
}
|
|
24
|
+
emit(event, data) {
|
|
25
|
+
const set = this.listeners[event];
|
|
26
|
+
if (!set) return;
|
|
27
|
+
for (const listener of set) {
|
|
28
|
+
try {
|
|
29
|
+
listener(data);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(`[lumra] Error in "${event}" listener:`, err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
removeAllListeners() {
|
|
36
|
+
this.listeners = {};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/hls.ts
|
|
41
|
+
var HlsModule = null;
|
|
42
|
+
async function loadHls() {
|
|
43
|
+
if (HlsModule) return HlsModule;
|
|
44
|
+
try {
|
|
45
|
+
const mod = await import('hls.js');
|
|
46
|
+
HlsModule = mod.default;
|
|
47
|
+
return HlsModule;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isHlsSource(src) {
|
|
53
|
+
return src.includes(".m3u8") || src.includes("application/x-mpegURL");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/player.ts
|
|
57
|
+
function levelLabel(height) {
|
|
58
|
+
if (height >= 2160) return "4K";
|
|
59
|
+
if (height >= 1080) return "1080p";
|
|
60
|
+
if (height >= 720) return "720p";
|
|
61
|
+
if (height >= 480) return "480p";
|
|
62
|
+
if (height >= 360) return "360p";
|
|
63
|
+
return `${height}p`;
|
|
64
|
+
}
|
|
65
|
+
var Player = class extends EventEmitter {
|
|
66
|
+
constructor(options) {
|
|
67
|
+
super();
|
|
68
|
+
this.options = options;
|
|
69
|
+
this.hls = null;
|
|
70
|
+
this.plugins = /* @__PURE__ */ new Map();
|
|
71
|
+
this._src = "";
|
|
72
|
+
this._destroyed = false;
|
|
73
|
+
this._isLive = false;
|
|
74
|
+
this._qualityAuto = true;
|
|
75
|
+
this._hlsLevels = [];
|
|
76
|
+
this.video = this.resolveTarget(options.target);
|
|
77
|
+
this.applyOptions();
|
|
78
|
+
this.bindNativeEvents();
|
|
79
|
+
if (options.src) {
|
|
80
|
+
void this.setSrcAsync(options.src);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
resolveTarget(target) {
|
|
84
|
+
if (target instanceof HTMLVideoElement) return target;
|
|
85
|
+
const el = document.querySelector(target);
|
|
86
|
+
if (!(el instanceof HTMLVideoElement)) {
|
|
87
|
+
throw new Error(`[lumra] Target "${target}" is not a <video> element`);
|
|
88
|
+
}
|
|
89
|
+
return el;
|
|
90
|
+
}
|
|
91
|
+
applyOptions() {
|
|
92
|
+
const { muted = false, volume = 1, loop = false, autoplay = false } = this.options;
|
|
93
|
+
this.video.muted = muted;
|
|
94
|
+
this.video.volume = volume;
|
|
95
|
+
this.video.loop = loop;
|
|
96
|
+
this.video.autoplay = autoplay;
|
|
97
|
+
}
|
|
98
|
+
bindNativeEvents() {
|
|
99
|
+
this.video.addEventListener("play", () => this.emit("play", void 0));
|
|
100
|
+
this.video.addEventListener("pause", () => this.emit("pause", void 0));
|
|
101
|
+
this.video.addEventListener("ended", () => this.emit("ended", void 0));
|
|
102
|
+
this.video.addEventListener("waiting", () => this.emit("buffering", void 0));
|
|
103
|
+
this.video.addEventListener("canplay", () => this.emit("ready", void 0));
|
|
104
|
+
this.video.addEventListener(
|
|
105
|
+
"volumechange",
|
|
106
|
+
() => this.emit("volumechange", { volume: this.video.volume, muted: this.video.muted })
|
|
107
|
+
);
|
|
108
|
+
this.video.addEventListener(
|
|
109
|
+
"timeupdate",
|
|
110
|
+
() => this.emit("timeupdate", {
|
|
111
|
+
currentTime: this.video.currentTime,
|
|
112
|
+
duration: this.video.duration || 0
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
this.video.addEventListener(
|
|
116
|
+
"seeking",
|
|
117
|
+
() => this.emit("seeking", { time: this.video.currentTime })
|
|
118
|
+
);
|
|
119
|
+
this.video.addEventListener(
|
|
120
|
+
"seeked",
|
|
121
|
+
() => this.emit("seeked", { time: this.video.currentTime })
|
|
122
|
+
);
|
|
123
|
+
this.video.addEventListener("error", () => {
|
|
124
|
+
const err = this.video.error;
|
|
125
|
+
this.emit("error", {
|
|
126
|
+
code: err?.code ?? -1,
|
|
127
|
+
message: err?.message ?? "Unknown media error"
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async setSrcAsync(src) {
|
|
132
|
+
this._src = src;
|
|
133
|
+
if (isHlsSource(src)) {
|
|
134
|
+
const Hls = await loadHls();
|
|
135
|
+
if (Hls && Hls.isSupported()) {
|
|
136
|
+
this.destroyHls();
|
|
137
|
+
const hls = new Hls(this.options.hlsConfig ?? {});
|
|
138
|
+
this.hls = hls;
|
|
139
|
+
hls.loadSource(src);
|
|
140
|
+
hls.attachMedia(this.video);
|
|
141
|
+
hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
|
|
142
|
+
this._isLive = data.live === true;
|
|
143
|
+
const levels = hls.levels.map((l, i) => ({
|
|
144
|
+
index: i,
|
|
145
|
+
height: l.height,
|
|
146
|
+
bitrate: l.bitrate,
|
|
147
|
+
label: levelLabel(l.height)
|
|
148
|
+
}));
|
|
149
|
+
this._hlsLevels = [
|
|
150
|
+
{ index: -1, height: 0, bitrate: 0, label: "Auto" },
|
|
151
|
+
...levels
|
|
152
|
+
];
|
|
153
|
+
});
|
|
154
|
+
hls.on(Hls.Events.LEVEL_SWITCHED, (_event, data) => {
|
|
155
|
+
this.emit("levelchange", { level: data.level });
|
|
156
|
+
this.emit("qualitychange", { level: data.level, auto: this._qualityAuto });
|
|
157
|
+
});
|
|
158
|
+
hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
159
|
+
if (data.fatal) {
|
|
160
|
+
this.emit("error", { code: -2, message: data.details ?? "HLS fatal error" });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (this.video.canPlayType("application/vnd.apple.mpegurl")) {
|
|
166
|
+
this.video.src = src;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.emit("error", { code: -1, message: "HLS is not supported in this browser" });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.destroyHls();
|
|
173
|
+
this.video.src = src;
|
|
174
|
+
}
|
|
175
|
+
destroyHls() {
|
|
176
|
+
if (this.hls) {
|
|
177
|
+
this.hls.destroy();
|
|
178
|
+
this.hls = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async play() {
|
|
182
|
+
await this.video.play();
|
|
183
|
+
}
|
|
184
|
+
pause() {
|
|
185
|
+
this.video.pause();
|
|
186
|
+
}
|
|
187
|
+
seek(time) {
|
|
188
|
+
this.video.currentTime = time;
|
|
189
|
+
}
|
|
190
|
+
setVolume(volume) {
|
|
191
|
+
this.video.volume = Math.max(0, Math.min(1, volume));
|
|
192
|
+
}
|
|
193
|
+
mute() {
|
|
194
|
+
this.video.muted = true;
|
|
195
|
+
}
|
|
196
|
+
unmute() {
|
|
197
|
+
this.video.muted = false;
|
|
198
|
+
}
|
|
199
|
+
setSrc(src) {
|
|
200
|
+
void this.setSrcAsync(src);
|
|
201
|
+
}
|
|
202
|
+
use(plugin) {
|
|
203
|
+
if (this.plugins.has(plugin.name)) {
|
|
204
|
+
console.warn(`[lumra] Plugin "${plugin.name}" is already registered`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
plugin.init(this);
|
|
208
|
+
this.plugins.set(plugin.name, plugin);
|
|
209
|
+
this.emit("plugin:register", { name: plugin.name });
|
|
210
|
+
}
|
|
211
|
+
destroy() {
|
|
212
|
+
if (this._destroyed) return;
|
|
213
|
+
this._destroyed = true;
|
|
214
|
+
for (const plugin of this.plugins.values()) {
|
|
215
|
+
plugin.destroy();
|
|
216
|
+
}
|
|
217
|
+
this.plugins.clear();
|
|
218
|
+
this.destroyHls();
|
|
219
|
+
this.video.pause();
|
|
220
|
+
this.video.removeAttribute("src");
|
|
221
|
+
this.video.load();
|
|
222
|
+
this.emit("destroy", void 0);
|
|
223
|
+
this.removeAllListeners();
|
|
224
|
+
}
|
|
225
|
+
getQualityLevels() {
|
|
226
|
+
return this._hlsLevels;
|
|
227
|
+
}
|
|
228
|
+
setQuality(levelIndex) {
|
|
229
|
+
if (!this.hls) return;
|
|
230
|
+
this._qualityAuto = levelIndex === -1;
|
|
231
|
+
this.hls.currentLevel = levelIndex;
|
|
232
|
+
this.emit("qualitychange", { level: levelIndex, auto: this._qualityAuto });
|
|
233
|
+
}
|
|
234
|
+
get currentTime() {
|
|
235
|
+
return this.video.currentTime;
|
|
236
|
+
}
|
|
237
|
+
get duration() {
|
|
238
|
+
return this.video.duration || 0;
|
|
239
|
+
}
|
|
240
|
+
get paused() {
|
|
241
|
+
return this.video.paused;
|
|
242
|
+
}
|
|
243
|
+
get volume() {
|
|
244
|
+
return this.video.volume;
|
|
245
|
+
}
|
|
246
|
+
get muted() {
|
|
247
|
+
return this.video.muted;
|
|
248
|
+
}
|
|
249
|
+
get src() {
|
|
250
|
+
return this._src;
|
|
251
|
+
}
|
|
252
|
+
get isLive() {
|
|
253
|
+
return this._isLive;
|
|
254
|
+
}
|
|
255
|
+
get isHls() {
|
|
256
|
+
return this.hls !== null;
|
|
257
|
+
}
|
|
258
|
+
get hlsInstance() {
|
|
259
|
+
return this.hls;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/index.ts
|
|
264
|
+
function createPlayer(options) {
|
|
265
|
+
return new Player(options);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
exports.EventEmitter = EventEmitter;
|
|
269
|
+
exports.createPlayer = createPlayer;
|
|
270
|
+
//# sourceMappingURL=index.cjs.map
|
|
271
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts","../src/hls.ts","../src/player.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { PlayerEvent, PlayerEventListener, PlayerEventMap } from './types'\n\ntype ListenerMap = {\n [E in PlayerEvent]?: Set<PlayerEventListener<E>>\n}\n\nexport class EventEmitter {\n private listeners: ListenerMap = {}\n\n on<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void {\n if (!this.listeners[event]) {\n this.listeners[event] = new Set() as ListenerMap[E]\n }\n ;(this.listeners[event] as Set<PlayerEventListener<E>>).add(listener)\n }\n\n off<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void {\n (this.listeners[event] as Set<PlayerEventListener<E>> | undefined)?.delete(listener)\n }\n\n once<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void {\n const wrapper: PlayerEventListener<E> = (data) => {\n listener(data)\n this.off(event, wrapper)\n }\n this.on(event, wrapper)\n }\n\n emit<E extends PlayerEvent>(event: E, data: PlayerEventMap[E]): void {\n const set = this.listeners[event] as Set<PlayerEventListener<E>> | undefined\n if (!set) return\n for (const listener of set) {\n try {\n listener(data)\n } catch (err) {\n console.error(`[lumra] Error in \"${event}\" listener:`, err)\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners = {}\n }\n}\n","import type Hls from 'hls.js'\n\nlet HlsModule: typeof Hls | null = null\n\nexport async function loadHls(): Promise<typeof Hls | null> {\n if (HlsModule) return HlsModule\n try {\n const mod = await import('hls.js')\n HlsModule = mod.default\n return HlsModule\n } catch {\n return null\n }\n}\n\nexport function isHlsSupported(): boolean {\n try {\n // Synchronous check using the cached module\n return HlsModule ? HlsModule.isSupported() : false\n } catch {\n return false\n }\n}\n\nexport function isHlsSource(src: string): boolean {\n return src.includes('.m3u8') || src.includes('application/x-mpegURL')\n}\n","import type Hls from 'hls.js'\nimport { EventEmitter } from './events'\nimport { isHlsSource, loadHls } from './hls'\nimport type { Plugin, PlayerInstance, PlayerOptions, QualityLevel } from './types'\n\nfunction levelLabel(height: number): string {\n if (height >= 2160) return '4K'\n if (height >= 1080) return '1080p'\n if (height >= 720) return '720p'\n if (height >= 480) return '480p'\n if (height >= 360) return '360p'\n return `${height}p`\n}\n\nexport class Player extends EventEmitter implements PlayerInstance {\n private video: HTMLVideoElement\n private hls: Hls | null = null\n private plugins: Map<string, Plugin> = new Map()\n private _src = ''\n private _destroyed = false\n private _isLive = false\n private _qualityAuto = true\n private _hlsLevels: QualityLevel[] = []\n\n constructor(private readonly options: PlayerOptions) {\n super()\n this.video = this.resolveTarget(options.target)\n this.applyOptions()\n this.bindNativeEvents()\n if (options.src) {\n void this.setSrcAsync(options.src)\n }\n }\n\n private resolveTarget(target: string | HTMLVideoElement): HTMLVideoElement {\n if (target instanceof HTMLVideoElement) return target\n const el = document.querySelector(target)\n if (!(el instanceof HTMLVideoElement)) {\n throw new Error(`[lumra] Target \"${target}\" is not a <video> element`)\n }\n return el\n }\n\n private applyOptions(): void {\n const { muted = false, volume = 1, loop = false, autoplay = false } = this.options\n this.video.muted = muted\n this.video.volume = volume\n this.video.loop = loop\n this.video.autoplay = autoplay\n }\n\n private bindNativeEvents(): void {\n this.video.addEventListener('play', () => this.emit('play', undefined as void))\n this.video.addEventListener('pause', () => this.emit('pause', undefined as void))\n this.video.addEventListener('ended', () => this.emit('ended', undefined as void))\n this.video.addEventListener('waiting', () => this.emit('buffering', undefined as void))\n this.video.addEventListener('canplay', () => this.emit('ready', undefined as void))\n this.video.addEventListener('volumechange', () =>\n this.emit('volumechange', { volume: this.video.volume, muted: this.video.muted })\n )\n this.video.addEventListener('timeupdate', () =>\n this.emit('timeupdate', {\n currentTime: this.video.currentTime,\n duration: this.video.duration || 0,\n })\n )\n this.video.addEventListener('seeking', () =>\n this.emit('seeking', { time: this.video.currentTime })\n )\n this.video.addEventListener('seeked', () =>\n this.emit('seeked', { time: this.video.currentTime })\n )\n this.video.addEventListener('error', () => {\n const err = this.video.error\n this.emit('error', {\n code: err?.code ?? -1,\n message: err?.message ?? 'Unknown media error',\n })\n })\n }\n\n private async setSrcAsync(src: string): Promise<void> {\n this._src = src\n\n if (isHlsSource(src)) {\n const Hls = await loadHls()\n if (Hls && Hls.isSupported()) {\n this.destroyHls()\n const hls = new Hls(this.options.hlsConfig ?? {})\n this.hls = hls\n hls.loadSource(src)\n hls.attachMedia(this.video)\n\n hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {\n this._isLive = (data as { live?: boolean }).live === true\n const levels: QualityLevel[] = hls.levels.map((l, i) => ({\n index: i,\n height: l.height,\n bitrate: l.bitrate,\n label: levelLabel(l.height),\n }))\n this._hlsLevels = [\n { index: -1, height: 0, bitrate: 0, label: 'Auto' },\n ...levels,\n ]\n })\n\n hls.on(Hls.Events.LEVEL_SWITCHED, (_event, data) => {\n this.emit('levelchange', { level: data.level })\n this.emit('qualitychange', { level: data.level, auto: this._qualityAuto })\n })\n\n hls.on(Hls.Events.ERROR, (_event, data) => {\n if (data.fatal) {\n this.emit('error', { code: -2, message: data.details ?? 'HLS fatal error' })\n }\n })\n\n return\n }\n // Fallback: native HLS (Safari)\n if (this.video.canPlayType('application/vnd.apple.mpegurl')) {\n this.video.src = src\n return\n }\n this.emit('error', { code: -1, message: 'HLS is not supported in this browser' })\n return\n }\n\n this.destroyHls()\n this.video.src = src\n }\n\n private destroyHls(): void {\n if (this.hls) {\n this.hls.destroy()\n this.hls = null\n }\n }\n\n async play(): Promise<void> {\n await this.video.play()\n }\n\n pause(): void {\n this.video.pause()\n }\n\n seek(time: number): void {\n this.video.currentTime = time\n }\n\n setVolume(volume: number): void {\n this.video.volume = Math.max(0, Math.min(1, volume))\n }\n\n mute(): void {\n this.video.muted = true\n }\n\n unmute(): void {\n this.video.muted = false\n }\n\n setSrc(src: string): void {\n void this.setSrcAsync(src)\n }\n\n use(plugin: Plugin): void {\n if (this.plugins.has(plugin.name)) {\n console.warn(`[lumra] Plugin \"${plugin.name}\" is already registered`)\n return\n }\n plugin.init(this)\n this.plugins.set(plugin.name, plugin)\n this.emit('plugin:register', { name: plugin.name })\n }\n\n destroy(): void {\n if (this._destroyed) return\n this._destroyed = true\n for (const plugin of this.plugins.values()) {\n plugin.destroy()\n }\n this.plugins.clear()\n this.destroyHls()\n this.video.pause()\n this.video.removeAttribute('src')\n this.video.load()\n this.emit('destroy', undefined as void)\n this.removeAllListeners()\n }\n\n getQualityLevels(): QualityLevel[] {\n return this._hlsLevels\n }\n\n setQuality(levelIndex: number): void {\n if (!this.hls) return\n this._qualityAuto = levelIndex === -1\n this.hls.currentLevel = levelIndex\n this.emit('qualitychange', { level: levelIndex, auto: this._qualityAuto })\n }\n\n get currentTime(): number { return this.video.currentTime }\n get duration(): number { return this.video.duration || 0 }\n get paused(): boolean { return this.video.paused }\n get volume(): number { return this.video.volume }\n get muted(): boolean { return this.video.muted }\n get src(): string { return this._src }\n get isLive(): boolean { return this._isLive }\n get isHls(): boolean { return this.hls !== null }\n get hlsInstance(): unknown | null { return this.hls }\n}\n","import { Player } from './player'\nimport type { PlayerOptions, PlayerInstance } from './types'\n\nexport function createPlayer(options: PlayerOptions): PlayerInstance {\n return new Player(options)\n}\n\nexport type { PlayerOptions, PlayerInstance, Plugin, PlayerEvent, PlayerEventMap, PlayerEventListener, QualityLevel } from './types'\nexport { EventEmitter } from './events'\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
type PlayerEventMap = {
|
|
2
|
+
play: void;
|
|
3
|
+
pause: void;
|
|
4
|
+
ended: void;
|
|
5
|
+
timeupdate: {
|
|
6
|
+
currentTime: number;
|
|
7
|
+
duration: number;
|
|
8
|
+
};
|
|
9
|
+
volumechange: {
|
|
10
|
+
volume: number;
|
|
11
|
+
muted: boolean;
|
|
12
|
+
};
|
|
13
|
+
seeking: {
|
|
14
|
+
time: number;
|
|
15
|
+
};
|
|
16
|
+
seeked: {
|
|
17
|
+
time: number;
|
|
18
|
+
};
|
|
19
|
+
buffering: void;
|
|
20
|
+
ready: void;
|
|
21
|
+
error: {
|
|
22
|
+
code: number;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
destroy: void;
|
|
26
|
+
'plugin:register': {
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
qualitychange: {
|
|
30
|
+
level: number;
|
|
31
|
+
auto: boolean;
|
|
32
|
+
};
|
|
33
|
+
levelchange: {
|
|
34
|
+
level: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
type PlayerEvent = keyof PlayerEventMap;
|
|
38
|
+
type PlayerEventListener<E extends PlayerEvent> = (data: PlayerEventMap[E]) => void;
|
|
39
|
+
interface QualityLevel {
|
|
40
|
+
index: number;
|
|
41
|
+
height: number;
|
|
42
|
+
bitrate: number;
|
|
43
|
+
label: string;
|
|
44
|
+
}
|
|
45
|
+
interface PlayerOptions {
|
|
46
|
+
target: string | HTMLVideoElement;
|
|
47
|
+
src?: string;
|
|
48
|
+
autoplay?: boolean;
|
|
49
|
+
muted?: boolean;
|
|
50
|
+
volume?: number;
|
|
51
|
+
loop?: boolean;
|
|
52
|
+
headless?: boolean;
|
|
53
|
+
hlsConfig?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
interface Plugin {
|
|
56
|
+
name: string;
|
|
57
|
+
init(player: PlayerInstance): void;
|
|
58
|
+
destroy(): void;
|
|
59
|
+
}
|
|
60
|
+
interface PlayerInstance {
|
|
61
|
+
play(): Promise<void>;
|
|
62
|
+
pause(): void;
|
|
63
|
+
seek(time: number): void;
|
|
64
|
+
setVolume(volume: number): void;
|
|
65
|
+
mute(): void;
|
|
66
|
+
unmute(): void;
|
|
67
|
+
setSrc(src: string): void;
|
|
68
|
+
destroy(): void;
|
|
69
|
+
on<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
70
|
+
off<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
71
|
+
once<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
72
|
+
use(plugin: Plugin): void;
|
|
73
|
+
readonly currentTime: number;
|
|
74
|
+
readonly duration: number;
|
|
75
|
+
readonly paused: boolean;
|
|
76
|
+
readonly volume: number;
|
|
77
|
+
readonly muted: boolean;
|
|
78
|
+
readonly src: string;
|
|
79
|
+
getQualityLevels(): QualityLevel[];
|
|
80
|
+
setQuality(levelIndex: number): void;
|
|
81
|
+
readonly isLive: boolean;
|
|
82
|
+
readonly isHls: boolean;
|
|
83
|
+
readonly hlsInstance: unknown | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare class EventEmitter {
|
|
87
|
+
private listeners;
|
|
88
|
+
on<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
89
|
+
off<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
90
|
+
once<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
91
|
+
emit<E extends PlayerEvent>(event: E, data: PlayerEventMap[E]): void;
|
|
92
|
+
removeAllListeners(): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
declare function createPlayer(options: PlayerOptions): PlayerInstance;
|
|
96
|
+
|
|
97
|
+
export { EventEmitter, type PlayerEvent, type PlayerEventListener, type PlayerEventMap, type PlayerInstance, type PlayerOptions, type Plugin, type QualityLevel, createPlayer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
type PlayerEventMap = {
|
|
2
|
+
play: void;
|
|
3
|
+
pause: void;
|
|
4
|
+
ended: void;
|
|
5
|
+
timeupdate: {
|
|
6
|
+
currentTime: number;
|
|
7
|
+
duration: number;
|
|
8
|
+
};
|
|
9
|
+
volumechange: {
|
|
10
|
+
volume: number;
|
|
11
|
+
muted: boolean;
|
|
12
|
+
};
|
|
13
|
+
seeking: {
|
|
14
|
+
time: number;
|
|
15
|
+
};
|
|
16
|
+
seeked: {
|
|
17
|
+
time: number;
|
|
18
|
+
};
|
|
19
|
+
buffering: void;
|
|
20
|
+
ready: void;
|
|
21
|
+
error: {
|
|
22
|
+
code: number;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
destroy: void;
|
|
26
|
+
'plugin:register': {
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
qualitychange: {
|
|
30
|
+
level: number;
|
|
31
|
+
auto: boolean;
|
|
32
|
+
};
|
|
33
|
+
levelchange: {
|
|
34
|
+
level: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
type PlayerEvent = keyof PlayerEventMap;
|
|
38
|
+
type PlayerEventListener<E extends PlayerEvent> = (data: PlayerEventMap[E]) => void;
|
|
39
|
+
interface QualityLevel {
|
|
40
|
+
index: number;
|
|
41
|
+
height: number;
|
|
42
|
+
bitrate: number;
|
|
43
|
+
label: string;
|
|
44
|
+
}
|
|
45
|
+
interface PlayerOptions {
|
|
46
|
+
target: string | HTMLVideoElement;
|
|
47
|
+
src?: string;
|
|
48
|
+
autoplay?: boolean;
|
|
49
|
+
muted?: boolean;
|
|
50
|
+
volume?: number;
|
|
51
|
+
loop?: boolean;
|
|
52
|
+
headless?: boolean;
|
|
53
|
+
hlsConfig?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
interface Plugin {
|
|
56
|
+
name: string;
|
|
57
|
+
init(player: PlayerInstance): void;
|
|
58
|
+
destroy(): void;
|
|
59
|
+
}
|
|
60
|
+
interface PlayerInstance {
|
|
61
|
+
play(): Promise<void>;
|
|
62
|
+
pause(): void;
|
|
63
|
+
seek(time: number): void;
|
|
64
|
+
setVolume(volume: number): void;
|
|
65
|
+
mute(): void;
|
|
66
|
+
unmute(): void;
|
|
67
|
+
setSrc(src: string): void;
|
|
68
|
+
destroy(): void;
|
|
69
|
+
on<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
70
|
+
off<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
71
|
+
once<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
72
|
+
use(plugin: Plugin): void;
|
|
73
|
+
readonly currentTime: number;
|
|
74
|
+
readonly duration: number;
|
|
75
|
+
readonly paused: boolean;
|
|
76
|
+
readonly volume: number;
|
|
77
|
+
readonly muted: boolean;
|
|
78
|
+
readonly src: string;
|
|
79
|
+
getQualityLevels(): QualityLevel[];
|
|
80
|
+
setQuality(levelIndex: number): void;
|
|
81
|
+
readonly isLive: boolean;
|
|
82
|
+
readonly isHls: boolean;
|
|
83
|
+
readonly hlsInstance: unknown | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare class EventEmitter {
|
|
87
|
+
private listeners;
|
|
88
|
+
on<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
89
|
+
off<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
90
|
+
once<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void;
|
|
91
|
+
emit<E extends PlayerEvent>(event: E, data: PlayerEventMap[E]): void;
|
|
92
|
+
removeAllListeners(): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
declare function createPlayer(options: PlayerOptions): PlayerInstance;
|
|
96
|
+
|
|
97
|
+
export { EventEmitter, type PlayerEvent, type PlayerEventListener, type PlayerEventMap, type PlayerInstance, type PlayerOptions, type Plugin, type QualityLevel, createPlayer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var EventEmitter = class {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.listeners = {};
|
|
5
|
+
}
|
|
6
|
+
on(event, listener) {
|
|
7
|
+
if (!this.listeners[event]) {
|
|
8
|
+
this.listeners[event] = /* @__PURE__ */ new Set();
|
|
9
|
+
}
|
|
10
|
+
this.listeners[event].add(listener);
|
|
11
|
+
}
|
|
12
|
+
off(event, listener) {
|
|
13
|
+
this.listeners[event]?.delete(listener);
|
|
14
|
+
}
|
|
15
|
+
once(event, listener) {
|
|
16
|
+
const wrapper = (data) => {
|
|
17
|
+
listener(data);
|
|
18
|
+
this.off(event, wrapper);
|
|
19
|
+
};
|
|
20
|
+
this.on(event, wrapper);
|
|
21
|
+
}
|
|
22
|
+
emit(event, data) {
|
|
23
|
+
const set = this.listeners[event];
|
|
24
|
+
if (!set) return;
|
|
25
|
+
for (const listener of set) {
|
|
26
|
+
try {
|
|
27
|
+
listener(data);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(`[lumra] Error in "${event}" listener:`, err);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
removeAllListeners() {
|
|
34
|
+
this.listeners = {};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/hls.ts
|
|
39
|
+
var HlsModule = null;
|
|
40
|
+
async function loadHls() {
|
|
41
|
+
if (HlsModule) return HlsModule;
|
|
42
|
+
try {
|
|
43
|
+
const mod = await import('hls.js');
|
|
44
|
+
HlsModule = mod.default;
|
|
45
|
+
return HlsModule;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isHlsSource(src) {
|
|
51
|
+
return src.includes(".m3u8") || src.includes("application/x-mpegURL");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/player.ts
|
|
55
|
+
function levelLabel(height) {
|
|
56
|
+
if (height >= 2160) return "4K";
|
|
57
|
+
if (height >= 1080) return "1080p";
|
|
58
|
+
if (height >= 720) return "720p";
|
|
59
|
+
if (height >= 480) return "480p";
|
|
60
|
+
if (height >= 360) return "360p";
|
|
61
|
+
return `${height}p`;
|
|
62
|
+
}
|
|
63
|
+
var Player = class extends EventEmitter {
|
|
64
|
+
constructor(options) {
|
|
65
|
+
super();
|
|
66
|
+
this.options = options;
|
|
67
|
+
this.hls = null;
|
|
68
|
+
this.plugins = /* @__PURE__ */ new Map();
|
|
69
|
+
this._src = "";
|
|
70
|
+
this._destroyed = false;
|
|
71
|
+
this._isLive = false;
|
|
72
|
+
this._qualityAuto = true;
|
|
73
|
+
this._hlsLevels = [];
|
|
74
|
+
this.video = this.resolveTarget(options.target);
|
|
75
|
+
this.applyOptions();
|
|
76
|
+
this.bindNativeEvents();
|
|
77
|
+
if (options.src) {
|
|
78
|
+
void this.setSrcAsync(options.src);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
resolveTarget(target) {
|
|
82
|
+
if (target instanceof HTMLVideoElement) return target;
|
|
83
|
+
const el = document.querySelector(target);
|
|
84
|
+
if (!(el instanceof HTMLVideoElement)) {
|
|
85
|
+
throw new Error(`[lumra] Target "${target}" is not a <video> element`);
|
|
86
|
+
}
|
|
87
|
+
return el;
|
|
88
|
+
}
|
|
89
|
+
applyOptions() {
|
|
90
|
+
const { muted = false, volume = 1, loop = false, autoplay = false } = this.options;
|
|
91
|
+
this.video.muted = muted;
|
|
92
|
+
this.video.volume = volume;
|
|
93
|
+
this.video.loop = loop;
|
|
94
|
+
this.video.autoplay = autoplay;
|
|
95
|
+
}
|
|
96
|
+
bindNativeEvents() {
|
|
97
|
+
this.video.addEventListener("play", () => this.emit("play", void 0));
|
|
98
|
+
this.video.addEventListener("pause", () => this.emit("pause", void 0));
|
|
99
|
+
this.video.addEventListener("ended", () => this.emit("ended", void 0));
|
|
100
|
+
this.video.addEventListener("waiting", () => this.emit("buffering", void 0));
|
|
101
|
+
this.video.addEventListener("canplay", () => this.emit("ready", void 0));
|
|
102
|
+
this.video.addEventListener(
|
|
103
|
+
"volumechange",
|
|
104
|
+
() => this.emit("volumechange", { volume: this.video.volume, muted: this.video.muted })
|
|
105
|
+
);
|
|
106
|
+
this.video.addEventListener(
|
|
107
|
+
"timeupdate",
|
|
108
|
+
() => this.emit("timeupdate", {
|
|
109
|
+
currentTime: this.video.currentTime,
|
|
110
|
+
duration: this.video.duration || 0
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
this.video.addEventListener(
|
|
114
|
+
"seeking",
|
|
115
|
+
() => this.emit("seeking", { time: this.video.currentTime })
|
|
116
|
+
);
|
|
117
|
+
this.video.addEventListener(
|
|
118
|
+
"seeked",
|
|
119
|
+
() => this.emit("seeked", { time: this.video.currentTime })
|
|
120
|
+
);
|
|
121
|
+
this.video.addEventListener("error", () => {
|
|
122
|
+
const err = this.video.error;
|
|
123
|
+
this.emit("error", {
|
|
124
|
+
code: err?.code ?? -1,
|
|
125
|
+
message: err?.message ?? "Unknown media error"
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async setSrcAsync(src) {
|
|
130
|
+
this._src = src;
|
|
131
|
+
if (isHlsSource(src)) {
|
|
132
|
+
const Hls = await loadHls();
|
|
133
|
+
if (Hls && Hls.isSupported()) {
|
|
134
|
+
this.destroyHls();
|
|
135
|
+
const hls = new Hls(this.options.hlsConfig ?? {});
|
|
136
|
+
this.hls = hls;
|
|
137
|
+
hls.loadSource(src);
|
|
138
|
+
hls.attachMedia(this.video);
|
|
139
|
+
hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
|
|
140
|
+
this._isLive = data.live === true;
|
|
141
|
+
const levels = hls.levels.map((l, i) => ({
|
|
142
|
+
index: i,
|
|
143
|
+
height: l.height,
|
|
144
|
+
bitrate: l.bitrate,
|
|
145
|
+
label: levelLabel(l.height)
|
|
146
|
+
}));
|
|
147
|
+
this._hlsLevels = [
|
|
148
|
+
{ index: -1, height: 0, bitrate: 0, label: "Auto" },
|
|
149
|
+
...levels
|
|
150
|
+
];
|
|
151
|
+
});
|
|
152
|
+
hls.on(Hls.Events.LEVEL_SWITCHED, (_event, data) => {
|
|
153
|
+
this.emit("levelchange", { level: data.level });
|
|
154
|
+
this.emit("qualitychange", { level: data.level, auto: this._qualityAuto });
|
|
155
|
+
});
|
|
156
|
+
hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
157
|
+
if (data.fatal) {
|
|
158
|
+
this.emit("error", { code: -2, message: data.details ?? "HLS fatal error" });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (this.video.canPlayType("application/vnd.apple.mpegurl")) {
|
|
164
|
+
this.video.src = src;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.emit("error", { code: -1, message: "HLS is not supported in this browser" });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.destroyHls();
|
|
171
|
+
this.video.src = src;
|
|
172
|
+
}
|
|
173
|
+
destroyHls() {
|
|
174
|
+
if (this.hls) {
|
|
175
|
+
this.hls.destroy();
|
|
176
|
+
this.hls = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async play() {
|
|
180
|
+
await this.video.play();
|
|
181
|
+
}
|
|
182
|
+
pause() {
|
|
183
|
+
this.video.pause();
|
|
184
|
+
}
|
|
185
|
+
seek(time) {
|
|
186
|
+
this.video.currentTime = time;
|
|
187
|
+
}
|
|
188
|
+
setVolume(volume) {
|
|
189
|
+
this.video.volume = Math.max(0, Math.min(1, volume));
|
|
190
|
+
}
|
|
191
|
+
mute() {
|
|
192
|
+
this.video.muted = true;
|
|
193
|
+
}
|
|
194
|
+
unmute() {
|
|
195
|
+
this.video.muted = false;
|
|
196
|
+
}
|
|
197
|
+
setSrc(src) {
|
|
198
|
+
void this.setSrcAsync(src);
|
|
199
|
+
}
|
|
200
|
+
use(plugin) {
|
|
201
|
+
if (this.plugins.has(plugin.name)) {
|
|
202
|
+
console.warn(`[lumra] Plugin "${plugin.name}" is already registered`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
plugin.init(this);
|
|
206
|
+
this.plugins.set(plugin.name, plugin);
|
|
207
|
+
this.emit("plugin:register", { name: plugin.name });
|
|
208
|
+
}
|
|
209
|
+
destroy() {
|
|
210
|
+
if (this._destroyed) return;
|
|
211
|
+
this._destroyed = true;
|
|
212
|
+
for (const plugin of this.plugins.values()) {
|
|
213
|
+
plugin.destroy();
|
|
214
|
+
}
|
|
215
|
+
this.plugins.clear();
|
|
216
|
+
this.destroyHls();
|
|
217
|
+
this.video.pause();
|
|
218
|
+
this.video.removeAttribute("src");
|
|
219
|
+
this.video.load();
|
|
220
|
+
this.emit("destroy", void 0);
|
|
221
|
+
this.removeAllListeners();
|
|
222
|
+
}
|
|
223
|
+
getQualityLevels() {
|
|
224
|
+
return this._hlsLevels;
|
|
225
|
+
}
|
|
226
|
+
setQuality(levelIndex) {
|
|
227
|
+
if (!this.hls) return;
|
|
228
|
+
this._qualityAuto = levelIndex === -1;
|
|
229
|
+
this.hls.currentLevel = levelIndex;
|
|
230
|
+
this.emit("qualitychange", { level: levelIndex, auto: this._qualityAuto });
|
|
231
|
+
}
|
|
232
|
+
get currentTime() {
|
|
233
|
+
return this.video.currentTime;
|
|
234
|
+
}
|
|
235
|
+
get duration() {
|
|
236
|
+
return this.video.duration || 0;
|
|
237
|
+
}
|
|
238
|
+
get paused() {
|
|
239
|
+
return this.video.paused;
|
|
240
|
+
}
|
|
241
|
+
get volume() {
|
|
242
|
+
return this.video.volume;
|
|
243
|
+
}
|
|
244
|
+
get muted() {
|
|
245
|
+
return this.video.muted;
|
|
246
|
+
}
|
|
247
|
+
get src() {
|
|
248
|
+
return this._src;
|
|
249
|
+
}
|
|
250
|
+
get isLive() {
|
|
251
|
+
return this._isLive;
|
|
252
|
+
}
|
|
253
|
+
get isHls() {
|
|
254
|
+
return this.hls !== null;
|
|
255
|
+
}
|
|
256
|
+
get hlsInstance() {
|
|
257
|
+
return this.hls;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/index.ts
|
|
262
|
+
function createPlayer(options) {
|
|
263
|
+
return new Player(options);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { EventEmitter, createPlayer };
|
|
267
|
+
//# sourceMappingURL=index.js.map
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts","../src/hls.ts","../src/player.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PlayerEvent, PlayerEventListener, PlayerEventMap } from './types'\n\ntype ListenerMap = {\n [E in PlayerEvent]?: Set<PlayerEventListener<E>>\n}\n\nexport class EventEmitter {\n private listeners: ListenerMap = {}\n\n on<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void {\n if (!this.listeners[event]) {\n this.listeners[event] = new Set() as ListenerMap[E]\n }\n ;(this.listeners[event] as Set<PlayerEventListener<E>>).add(listener)\n }\n\n off<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void {\n (this.listeners[event] as Set<PlayerEventListener<E>> | undefined)?.delete(listener)\n }\n\n once<E extends PlayerEvent>(event: E, listener: PlayerEventListener<E>): void {\n const wrapper: PlayerEventListener<E> = (data) => {\n listener(data)\n this.off(event, wrapper)\n }\n this.on(event, wrapper)\n }\n\n emit<E extends PlayerEvent>(event: E, data: PlayerEventMap[E]): void {\n const set = this.listeners[event] as Set<PlayerEventListener<E>> | undefined\n if (!set) return\n for (const listener of set) {\n try {\n listener(data)\n } catch (err) {\n console.error(`[lumra] Error in \"${event}\" listener:`, err)\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners = {}\n }\n}\n","import type Hls from 'hls.js'\n\nlet HlsModule: typeof Hls | null = null\n\nexport async function loadHls(): Promise<typeof Hls | null> {\n if (HlsModule) return HlsModule\n try {\n const mod = await import('hls.js')\n HlsModule = mod.default\n return HlsModule\n } catch {\n return null\n }\n}\n\nexport function isHlsSupported(): boolean {\n try {\n // Synchronous check using the cached module\n return HlsModule ? HlsModule.isSupported() : false\n } catch {\n return false\n }\n}\n\nexport function isHlsSource(src: string): boolean {\n return src.includes('.m3u8') || src.includes('application/x-mpegURL')\n}\n","import type Hls from 'hls.js'\nimport { EventEmitter } from './events'\nimport { isHlsSource, loadHls } from './hls'\nimport type { Plugin, PlayerInstance, PlayerOptions, QualityLevel } from './types'\n\nfunction levelLabel(height: number): string {\n if (height >= 2160) return '4K'\n if (height >= 1080) return '1080p'\n if (height >= 720) return '720p'\n if (height >= 480) return '480p'\n if (height >= 360) return '360p'\n return `${height}p`\n}\n\nexport class Player extends EventEmitter implements PlayerInstance {\n private video: HTMLVideoElement\n private hls: Hls | null = null\n private plugins: Map<string, Plugin> = new Map()\n private _src = ''\n private _destroyed = false\n private _isLive = false\n private _qualityAuto = true\n private _hlsLevels: QualityLevel[] = []\n\n constructor(private readonly options: PlayerOptions) {\n super()\n this.video = this.resolveTarget(options.target)\n this.applyOptions()\n this.bindNativeEvents()\n if (options.src) {\n void this.setSrcAsync(options.src)\n }\n }\n\n private resolveTarget(target: string | HTMLVideoElement): HTMLVideoElement {\n if (target instanceof HTMLVideoElement) return target\n const el = document.querySelector(target)\n if (!(el instanceof HTMLVideoElement)) {\n throw new Error(`[lumra] Target \"${target}\" is not a <video> element`)\n }\n return el\n }\n\n private applyOptions(): void {\n const { muted = false, volume = 1, loop = false, autoplay = false } = this.options\n this.video.muted = muted\n this.video.volume = volume\n this.video.loop = loop\n this.video.autoplay = autoplay\n }\n\n private bindNativeEvents(): void {\n this.video.addEventListener('play', () => this.emit('play', undefined as void))\n this.video.addEventListener('pause', () => this.emit('pause', undefined as void))\n this.video.addEventListener('ended', () => this.emit('ended', undefined as void))\n this.video.addEventListener('waiting', () => this.emit('buffering', undefined as void))\n this.video.addEventListener('canplay', () => this.emit('ready', undefined as void))\n this.video.addEventListener('volumechange', () =>\n this.emit('volumechange', { volume: this.video.volume, muted: this.video.muted })\n )\n this.video.addEventListener('timeupdate', () =>\n this.emit('timeupdate', {\n currentTime: this.video.currentTime,\n duration: this.video.duration || 0,\n })\n )\n this.video.addEventListener('seeking', () =>\n this.emit('seeking', { time: this.video.currentTime })\n )\n this.video.addEventListener('seeked', () =>\n this.emit('seeked', { time: this.video.currentTime })\n )\n this.video.addEventListener('error', () => {\n const err = this.video.error\n this.emit('error', {\n code: err?.code ?? -1,\n message: err?.message ?? 'Unknown media error',\n })\n })\n }\n\n private async setSrcAsync(src: string): Promise<void> {\n this._src = src\n\n if (isHlsSource(src)) {\n const Hls = await loadHls()\n if (Hls && Hls.isSupported()) {\n this.destroyHls()\n const hls = new Hls(this.options.hlsConfig ?? {})\n this.hls = hls\n hls.loadSource(src)\n hls.attachMedia(this.video)\n\n hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {\n this._isLive = (data as { live?: boolean }).live === true\n const levels: QualityLevel[] = hls.levels.map((l, i) => ({\n index: i,\n height: l.height,\n bitrate: l.bitrate,\n label: levelLabel(l.height),\n }))\n this._hlsLevels = [\n { index: -1, height: 0, bitrate: 0, label: 'Auto' },\n ...levels,\n ]\n })\n\n hls.on(Hls.Events.LEVEL_SWITCHED, (_event, data) => {\n this.emit('levelchange', { level: data.level })\n this.emit('qualitychange', { level: data.level, auto: this._qualityAuto })\n })\n\n hls.on(Hls.Events.ERROR, (_event, data) => {\n if (data.fatal) {\n this.emit('error', { code: -2, message: data.details ?? 'HLS fatal error' })\n }\n })\n\n return\n }\n // Fallback: native HLS (Safari)\n if (this.video.canPlayType('application/vnd.apple.mpegurl')) {\n this.video.src = src\n return\n }\n this.emit('error', { code: -1, message: 'HLS is not supported in this browser' })\n return\n }\n\n this.destroyHls()\n this.video.src = src\n }\n\n private destroyHls(): void {\n if (this.hls) {\n this.hls.destroy()\n this.hls = null\n }\n }\n\n async play(): Promise<void> {\n await this.video.play()\n }\n\n pause(): void {\n this.video.pause()\n }\n\n seek(time: number): void {\n this.video.currentTime = time\n }\n\n setVolume(volume: number): void {\n this.video.volume = Math.max(0, Math.min(1, volume))\n }\n\n mute(): void {\n this.video.muted = true\n }\n\n unmute(): void {\n this.video.muted = false\n }\n\n setSrc(src: string): void {\n void this.setSrcAsync(src)\n }\n\n use(plugin: Plugin): void {\n if (this.plugins.has(plugin.name)) {\n console.warn(`[lumra] Plugin \"${plugin.name}\" is already registered`)\n return\n }\n plugin.init(this)\n this.plugins.set(plugin.name, plugin)\n this.emit('plugin:register', { name: plugin.name })\n }\n\n destroy(): void {\n if (this._destroyed) return\n this._destroyed = true\n for (const plugin of this.plugins.values()) {\n plugin.destroy()\n }\n this.plugins.clear()\n this.destroyHls()\n this.video.pause()\n this.video.removeAttribute('src')\n this.video.load()\n this.emit('destroy', undefined as void)\n this.removeAllListeners()\n }\n\n getQualityLevels(): QualityLevel[] {\n return this._hlsLevels\n }\n\n setQuality(levelIndex: number): void {\n if (!this.hls) return\n this._qualityAuto = levelIndex === -1\n this.hls.currentLevel = levelIndex\n this.emit('qualitychange', { level: levelIndex, auto: this._qualityAuto })\n }\n\n get currentTime(): number { return this.video.currentTime }\n get duration(): number { return this.video.duration || 0 }\n get paused(): boolean { return this.video.paused }\n get volume(): number { return this.video.volume }\n get muted(): boolean { return this.video.muted }\n get src(): string { return this._src }\n get isLive(): boolean { return this._isLive }\n get isHls(): boolean { return this.hls !== null }\n get hlsInstance(): unknown | null { return this.hls }\n}\n","import { Player } from './player'\nimport type { PlayerOptions, PlayerInstance } from './types'\n\nexport function createPlayer(options: PlayerOptions): PlayerInstance {\n return new Player(options)\n}\n\nexport type { PlayerOptions, PlayerInstance, Plugin, PlayerEvent, PlayerEventMap, PlayerEventListener, QualityLevel } from './types'\nexport { EventEmitter } from './events'\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumra/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {"access": "public"},
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Lumra headless media player engine",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"lint": "eslint src",
|
|
26
|
+
"clean": "rm -rf dist"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"hls.js": "^1.5.13"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@vitest/browser": "^4.1.5",
|
|
33
|
+
"jsdom": "^29.1.1",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.4.5",
|
|
36
|
+
"vitest": "^1.6.0"
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": false
|
|
39
|
+
}
|