@lightbird/core 0.3.0 → 0.4.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 +100 -14
- package/dist/index.d.cts +44 -3
- package/dist/index.d.ts +44 -3
- package/dist/index.js +93 -9
- package/dist/react/index.d.cts +13 -1
- package/dist/react/index.d.ts +13 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
var assCompiler = require('ass-compiler');
|
|
4
4
|
var webSdk = require('@openfeature/web-sdk');
|
|
5
5
|
var unleashWebProvider = require('@openfeature/unleash-web-provider');
|
|
6
|
-
var ffmpeg = require('@ffmpeg/ffmpeg');
|
|
7
|
-
var util = require('@ffmpeg/util');
|
|
8
6
|
|
|
9
7
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
8
|
// src/config.ts
|
|
@@ -613,6 +611,78 @@ _MKVPlayer._canPlayNatively = canPlayNatively;
|
|
|
613
611
|
_MKVPlayer._workerFactory = null;
|
|
614
612
|
var MKVPlayer = _MKVPlayer;
|
|
615
613
|
|
|
614
|
+
// src/players/hls-player.ts
|
|
615
|
+
var HLS_MIME_HINTS = ["application/x-mpegurl", "application/vnd.apple.mpegurl"];
|
|
616
|
+
function isHlsUrl(url) {
|
|
617
|
+
if (typeof url !== "string" || url.length === 0) return false;
|
|
618
|
+
const lower = url.toLowerCase();
|
|
619
|
+
if (HLS_MIME_HINTS.some((hint) => lower.includes(hint))) return true;
|
|
620
|
+
return lower.split(/[?#]/)[0].endsWith(".m3u8");
|
|
621
|
+
}
|
|
622
|
+
var HLSPlayer = class {
|
|
623
|
+
constructor(url) {
|
|
624
|
+
this.hls = null;
|
|
625
|
+
this.url = url;
|
|
626
|
+
this.playerFile = { url, qualityLevels: [] };
|
|
627
|
+
}
|
|
628
|
+
async initialize(videoElement) {
|
|
629
|
+
const { default: HlsCtor } = await import('hls.js');
|
|
630
|
+
if (!HlsCtor.isSupported()) {
|
|
631
|
+
videoElement.src = this.url;
|
|
632
|
+
return this.playerFile;
|
|
633
|
+
}
|
|
634
|
+
const hls = new HlsCtor();
|
|
635
|
+
this.hls = hls;
|
|
636
|
+
hls.loadSource(this.url);
|
|
637
|
+
hls.attachMedia(videoElement);
|
|
638
|
+
return this.playerFile;
|
|
639
|
+
}
|
|
640
|
+
getAudioTracks() {
|
|
641
|
+
if (!this.hls) return [];
|
|
642
|
+
return this.hls.audioTracks.map((track) => ({
|
|
643
|
+
id: String(track.id),
|
|
644
|
+
name: track.name,
|
|
645
|
+
lang: track.lang || "unknown"
|
|
646
|
+
}));
|
|
647
|
+
}
|
|
648
|
+
getSubtitles() {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
switchAudioTrack(trackId) {
|
|
652
|
+
if (this.hls) {
|
|
653
|
+
const id = Number(trackId);
|
|
654
|
+
if (!Number.isNaN(id)) this.hls.audioTrack = id;
|
|
655
|
+
}
|
|
656
|
+
return Promise.resolve();
|
|
657
|
+
}
|
|
658
|
+
switchSubtitle() {
|
|
659
|
+
return Promise.resolve();
|
|
660
|
+
}
|
|
661
|
+
/** Quality renditions — not on `VideoPlayer`; read directly by the quality hook. */
|
|
662
|
+
getQualityLevels() {
|
|
663
|
+
if (!this.hls) return [];
|
|
664
|
+
return this.hls.levels.map((level, index) => ({
|
|
665
|
+
index,
|
|
666
|
+
height: level.height,
|
|
667
|
+
bitrate: level.bitrate,
|
|
668
|
+
name: level.name || (level.height > 0 ? `${level.height}p` : `Level ${index + 1}`)
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
671
|
+
/** Pins a quality level; `-1` restores automatic (ABR) selection. */
|
|
672
|
+
setQualityLevel(levelIndex) {
|
|
673
|
+
if (this.hls) this.hls.currentLevel = levelIndex;
|
|
674
|
+
}
|
|
675
|
+
destroy() {
|
|
676
|
+
if (this.hls) {
|
|
677
|
+
this.hls.destroy();
|
|
678
|
+
this.hls = null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
static isCompatible(url) {
|
|
682
|
+
return isHlsUrl(url);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
616
686
|
// src/video-processor.ts
|
|
617
687
|
var SimplePlayerAdapter = class {
|
|
618
688
|
constructor(file, externalSubtitles = []) {
|
|
@@ -669,13 +739,21 @@ var MKVPlayerAdapter = class {
|
|
|
669
739
|
return this.player.tracksReady;
|
|
670
740
|
}
|
|
671
741
|
};
|
|
672
|
-
function createVideoPlayer(
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
742
|
+
function createVideoPlayer(source, externalSubtitles = [], onProgress) {
|
|
743
|
+
if (typeof source === "string") {
|
|
744
|
+
if (isHlsUrl(source)) {
|
|
745
|
+
return new HLSPlayer(source);
|
|
746
|
+
}
|
|
747
|
+
throw new Error(
|
|
748
|
+
`createVideoPlayer: unsupported URL "${source}" \u2014 only HLS (.m3u8) stream URLs are supported; pass a File for other formats.`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
if (MKVPlayer.isCompatible(source)) {
|
|
752
|
+
return new MKVPlayerAdapter(source, onProgress);
|
|
753
|
+
} else if (SimplePlayer.isCompatible(source)) {
|
|
754
|
+
return new SimplePlayerAdapter(source, externalSubtitles);
|
|
677
755
|
} else {
|
|
678
|
-
return new SimplePlayerAdapter(
|
|
756
|
+
return new SimplePlayerAdapter(source, externalSubtitles);
|
|
679
757
|
}
|
|
680
758
|
}
|
|
681
759
|
|
|
@@ -1433,6 +1511,8 @@ var ProgressEstimator = class {
|
|
|
1433
1511
|
}
|
|
1434
1512
|
// No reset() method — create a new instance per file load instead.
|
|
1435
1513
|
};
|
|
1514
|
+
|
|
1515
|
+
// src/utils/ffmpeg-singleton.ts
|
|
1436
1516
|
var instance = null;
|
|
1437
1517
|
var loading = null;
|
|
1438
1518
|
var defaultCDN = "https://unpkg.com/@ffmpeg/core@0.12.10/dist/umd";
|
|
@@ -1440,14 +1520,18 @@ async function getFFmpeg() {
|
|
|
1440
1520
|
if (instance) return instance;
|
|
1441
1521
|
if (loading) return loading;
|
|
1442
1522
|
loading = (async () => {
|
|
1443
|
-
const
|
|
1523
|
+
const [{ FFmpeg }, { toBlobURL }] = await Promise.all([
|
|
1524
|
+
import('@ffmpeg/ffmpeg'),
|
|
1525
|
+
import('@ffmpeg/util')
|
|
1526
|
+
]);
|
|
1527
|
+
const ffmpeg = new FFmpeg();
|
|
1444
1528
|
const baseURL = getConfig().ffmpegCDN || defaultCDN;
|
|
1445
|
-
await ffmpeg
|
|
1446
|
-
coreURL: await
|
|
1447
|
-
wasmURL: await
|
|
1529
|
+
await ffmpeg.load({
|
|
1530
|
+
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
|
|
1531
|
+
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm")
|
|
1448
1532
|
});
|
|
1449
|
-
instance = ffmpeg
|
|
1450
|
-
return ffmpeg
|
|
1533
|
+
instance = ffmpeg;
|
|
1534
|
+
return ffmpeg;
|
|
1451
1535
|
})();
|
|
1452
1536
|
return loading;
|
|
1453
1537
|
}
|
|
@@ -1462,6 +1546,7 @@ exports.DEFAULT_SHORTCUTS = DEFAULT_SHORTCUTS;
|
|
|
1462
1546
|
exports.DEFAULT_TRACKERS = DEFAULT_TRACKERS;
|
|
1463
1547
|
exports.DISCLAIMER_KEY = DISCLAIMER_KEY;
|
|
1464
1548
|
exports.FLAG_MAGNET_LINK = FLAG_MAGNET_LINK;
|
|
1549
|
+
exports.HLSPlayer = HLSPlayer;
|
|
1465
1550
|
exports.MKVPlayer = MKVPlayer;
|
|
1466
1551
|
exports.ProgressEstimator = ProgressEstimator;
|
|
1467
1552
|
exports.SimplePlayer = SimplePlayer;
|
|
@@ -1483,6 +1568,7 @@ exports.getVideoFiles = getVideoFiles;
|
|
|
1483
1568
|
exports.getWebTorrentClient = getWebTorrentClient;
|
|
1484
1569
|
exports.hasAcceptedDisclaimer = hasAcceptedDisclaimer;
|
|
1485
1570
|
exports.initFeatureFlags = initFeatureFlags;
|
|
1571
|
+
exports.isHlsUrl = isHlsUrl;
|
|
1486
1572
|
exports.isInteractiveElement = isInteractiveElement;
|
|
1487
1573
|
exports.isMagnetUri = isMagnetUri;
|
|
1488
1574
|
exports.isVideoFile = isVideoFile;
|
package/dist/index.d.cts
CHANGED
|
@@ -83,6 +83,18 @@ interface SubtitleTrackMeta {
|
|
|
83
83
|
format: string | null;
|
|
84
84
|
language: string | null;
|
|
85
85
|
}
|
|
86
|
+
/** A single HLS quality rendition (e.g. 1080p, 720p). */
|
|
87
|
+
interface QualityLevel {
|
|
88
|
+
index: number;
|
|
89
|
+
height: number;
|
|
90
|
+
bitrate: number;
|
|
91
|
+
name: string;
|
|
92
|
+
}
|
|
93
|
+
/** Result of initialising an HLS stream via `HLSPlayer`. */
|
|
94
|
+
interface HLSPlayerFile {
|
|
95
|
+
url: string;
|
|
96
|
+
qualityLevels: QualityLevel[];
|
|
97
|
+
}
|
|
86
98
|
|
|
87
99
|
interface SimplePlayerFile {
|
|
88
100
|
name: string;
|
|
@@ -168,7 +180,7 @@ declare class MKVPlayer {
|
|
|
168
180
|
static isCompatible(file: File): boolean;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
type ProcessedFile = SimplePlayerFile | MKVPlayerFile;
|
|
183
|
+
type ProcessedFile = SimplePlayerFile | MKVPlayerFile | HLSPlayerFile;
|
|
172
184
|
interface VideoPlayer {
|
|
173
185
|
initialize(videoElement: HTMLVideoElement): Promise<ProcessedFile>;
|
|
174
186
|
getAudioTracks(): AudioTrack[];
|
|
@@ -185,7 +197,28 @@ interface VideoPlayer {
|
|
|
185
197
|
*/
|
|
186
198
|
tracksReady?: Promise<void>;
|
|
187
199
|
}
|
|
188
|
-
declare function createVideoPlayer(
|
|
200
|
+
declare function createVideoPlayer(source: File | string, externalSubtitles?: File[], onProgress?: (progress: number) => void): VideoPlayer;
|
|
201
|
+
|
|
202
|
+
/** True for HLS playlist URLs — a `.m3u8` path or an HLS MIME hint. */
|
|
203
|
+
declare function isHlsUrl(url: string): boolean;
|
|
204
|
+
/** Plays HLS (`.m3u8`) streams via hls.js, falling back to native HLS (Safari). */
|
|
205
|
+
declare class HLSPlayer implements VideoPlayer {
|
|
206
|
+
private readonly url;
|
|
207
|
+
private readonly playerFile;
|
|
208
|
+
private hls;
|
|
209
|
+
constructor(url: string);
|
|
210
|
+
initialize(videoElement: HTMLVideoElement): Promise<HLSPlayerFile>;
|
|
211
|
+
getAudioTracks(): AudioTrack[];
|
|
212
|
+
getSubtitles(): Subtitle[];
|
|
213
|
+
switchAudioTrack(trackId: string): Promise<void>;
|
|
214
|
+
switchSubtitle(): Promise<void>;
|
|
215
|
+
/** Quality renditions — not on `VideoPlayer`; read directly by the quality hook. */
|
|
216
|
+
getQualityLevels(): QualityLevel[];
|
|
217
|
+
/** Pins a quality level; `-1` restores automatic (ABR) selection. */
|
|
218
|
+
setQualityLevel(levelIndex: number): void;
|
|
219
|
+
destroy(): void;
|
|
220
|
+
static isCompatible(url: string): boolean;
|
|
221
|
+
}
|
|
189
222
|
|
|
190
223
|
declare class UniversalSubtitleManager {
|
|
191
224
|
private records;
|
|
@@ -382,7 +415,15 @@ declare class ProgressEstimator {
|
|
|
382
415
|
};
|
|
383
416
|
}
|
|
384
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Returns a lazily-initialised FFmpeg.wasm instance.
|
|
420
|
+
*
|
|
421
|
+
* `@ffmpeg/ffmpeg` and `@ffmpeg/util` are pulled in via dynamic `import()` so
|
|
422
|
+
* the multi-MB FFmpeg code path is never part of the base `@lightbird/core`
|
|
423
|
+
* entry chunk. Consumers that only play HTML5-native formats (MP4/WebM) and
|
|
424
|
+
* never call `getFFmpeg()` download zero FFmpeg code. See issue #54.
|
|
425
|
+
*/
|
|
385
426
|
declare function getFFmpeg(): Promise<FFmpeg>;
|
|
386
427
|
declare function resetFFmpeg(): void;
|
|
387
428
|
|
|
388
|
-
export { ASSRenderer, type AudioTrack, type AudioTrackMeta, CancellationError, type Chapter, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, type LightBirdConfig, MKVPlayer, type MKVPlayerFile, type MediaErrorType, type ParsedMediaError, type PlaylistItem, type ProcessedFile, ProgressEstimator, type ShortcutAction, type ShortcutBinding, SimplePlayer, type SimplePlayerFile, type Subtitle, SubtitleConverter, type SubtitleCue, type SubtitleTrackMeta, type TorrentStatus, UniversalSubtitleManager, VIDEO_EXTENSIONS, type VideoFilters, type VideoMetadata, type VideoPlayer, acceptDisclaimer, applyOffsetToVtt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
|
429
|
+
export { ASSRenderer, type AudioTrack, type AudioTrackMeta, CancellationError, type Chapter, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, type HLSPlayerFile, type LightBirdConfig, MKVPlayer, type MKVPlayerFile, type MediaErrorType, type ParsedMediaError, type PlaylistItem, type ProcessedFile, ProgressEstimator, type QualityLevel, type ShortcutAction, type ShortcutBinding, SimplePlayer, type SimplePlayerFile, type Subtitle, SubtitleConverter, type SubtitleCue, type SubtitleTrackMeta, type TorrentStatus, UniversalSubtitleManager, VIDEO_EXTENSIONS, type VideoFilters, type VideoMetadata, type VideoPlayer, acceptDisclaimer, applyOffsetToVtt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,18 @@ interface SubtitleTrackMeta {
|
|
|
83
83
|
format: string | null;
|
|
84
84
|
language: string | null;
|
|
85
85
|
}
|
|
86
|
+
/** A single HLS quality rendition (e.g. 1080p, 720p). */
|
|
87
|
+
interface QualityLevel {
|
|
88
|
+
index: number;
|
|
89
|
+
height: number;
|
|
90
|
+
bitrate: number;
|
|
91
|
+
name: string;
|
|
92
|
+
}
|
|
93
|
+
/** Result of initialising an HLS stream via `HLSPlayer`. */
|
|
94
|
+
interface HLSPlayerFile {
|
|
95
|
+
url: string;
|
|
96
|
+
qualityLevels: QualityLevel[];
|
|
97
|
+
}
|
|
86
98
|
|
|
87
99
|
interface SimplePlayerFile {
|
|
88
100
|
name: string;
|
|
@@ -168,7 +180,7 @@ declare class MKVPlayer {
|
|
|
168
180
|
static isCompatible(file: File): boolean;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
type ProcessedFile = SimplePlayerFile | MKVPlayerFile;
|
|
183
|
+
type ProcessedFile = SimplePlayerFile | MKVPlayerFile | HLSPlayerFile;
|
|
172
184
|
interface VideoPlayer {
|
|
173
185
|
initialize(videoElement: HTMLVideoElement): Promise<ProcessedFile>;
|
|
174
186
|
getAudioTracks(): AudioTrack[];
|
|
@@ -185,7 +197,28 @@ interface VideoPlayer {
|
|
|
185
197
|
*/
|
|
186
198
|
tracksReady?: Promise<void>;
|
|
187
199
|
}
|
|
188
|
-
declare function createVideoPlayer(
|
|
200
|
+
declare function createVideoPlayer(source: File | string, externalSubtitles?: File[], onProgress?: (progress: number) => void): VideoPlayer;
|
|
201
|
+
|
|
202
|
+
/** True for HLS playlist URLs — a `.m3u8` path or an HLS MIME hint. */
|
|
203
|
+
declare function isHlsUrl(url: string): boolean;
|
|
204
|
+
/** Plays HLS (`.m3u8`) streams via hls.js, falling back to native HLS (Safari). */
|
|
205
|
+
declare class HLSPlayer implements VideoPlayer {
|
|
206
|
+
private readonly url;
|
|
207
|
+
private readonly playerFile;
|
|
208
|
+
private hls;
|
|
209
|
+
constructor(url: string);
|
|
210
|
+
initialize(videoElement: HTMLVideoElement): Promise<HLSPlayerFile>;
|
|
211
|
+
getAudioTracks(): AudioTrack[];
|
|
212
|
+
getSubtitles(): Subtitle[];
|
|
213
|
+
switchAudioTrack(trackId: string): Promise<void>;
|
|
214
|
+
switchSubtitle(): Promise<void>;
|
|
215
|
+
/** Quality renditions — not on `VideoPlayer`; read directly by the quality hook. */
|
|
216
|
+
getQualityLevels(): QualityLevel[];
|
|
217
|
+
/** Pins a quality level; `-1` restores automatic (ABR) selection. */
|
|
218
|
+
setQualityLevel(levelIndex: number): void;
|
|
219
|
+
destroy(): void;
|
|
220
|
+
static isCompatible(url: string): boolean;
|
|
221
|
+
}
|
|
189
222
|
|
|
190
223
|
declare class UniversalSubtitleManager {
|
|
191
224
|
private records;
|
|
@@ -382,7 +415,15 @@ declare class ProgressEstimator {
|
|
|
382
415
|
};
|
|
383
416
|
}
|
|
384
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Returns a lazily-initialised FFmpeg.wasm instance.
|
|
420
|
+
*
|
|
421
|
+
* `@ffmpeg/ffmpeg` and `@ffmpeg/util` are pulled in via dynamic `import()` so
|
|
422
|
+
* the multi-MB FFmpeg code path is never part of the base `@lightbird/core`
|
|
423
|
+
* entry chunk. Consumers that only play HTML5-native formats (MP4/WebM) and
|
|
424
|
+
* never call `getFFmpeg()` download zero FFmpeg code. See issue #54.
|
|
425
|
+
*/
|
|
385
426
|
declare function getFFmpeg(): Promise<FFmpeg>;
|
|
386
427
|
declare function resetFFmpeg(): void;
|
|
387
428
|
|
|
388
|
-
export { ASSRenderer, type AudioTrack, type AudioTrackMeta, CancellationError, type Chapter, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, type LightBirdConfig, MKVPlayer, type MKVPlayerFile, type MediaErrorType, type ParsedMediaError, type PlaylistItem, type ProcessedFile, ProgressEstimator, type ShortcutAction, type ShortcutBinding, SimplePlayer, type SimplePlayerFile, type Subtitle, SubtitleConverter, type SubtitleCue, type SubtitleTrackMeta, type TorrentStatus, UniversalSubtitleManager, VIDEO_EXTENSIONS, type VideoFilters, type VideoMetadata, type VideoPlayer, acceptDisclaimer, applyOffsetToVtt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
|
429
|
+
export { ASSRenderer, type AudioTrack, type AudioTrackMeta, CancellationError, type Chapter, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, type HLSPlayerFile, type LightBirdConfig, MKVPlayer, type MKVPlayerFile, type MediaErrorType, type ParsedMediaError, type PlaylistItem, type ProcessedFile, ProgressEstimator, type QualityLevel, type ShortcutAction, type ShortcutBinding, SimplePlayer, type SimplePlayerFile, type Subtitle, SubtitleConverter, type SubtitleCue, type SubtitleTrackMeta, type TorrentStatus, UniversalSubtitleManager, VIDEO_EXTENSIONS, type VideoFilters, type VideoMetadata, type VideoPlayer, acceptDisclaimer, applyOffsetToVtt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { compile } from 'ass-compiler';
|
|
2
2
|
import { OpenFeature } from '@openfeature/web-sdk';
|
|
3
3
|
import { UnleashWebProvider } from '@openfeature/unleash-web-provider';
|
|
4
|
-
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
|
5
|
-
import { toBlobURL } from '@ffmpeg/util';
|
|
6
4
|
|
|
7
5
|
// src/config.ts
|
|
8
6
|
var config = {};
|
|
@@ -610,6 +608,78 @@ _MKVPlayer._canPlayNatively = canPlayNatively;
|
|
|
610
608
|
_MKVPlayer._workerFactory = null;
|
|
611
609
|
var MKVPlayer = _MKVPlayer;
|
|
612
610
|
|
|
611
|
+
// src/players/hls-player.ts
|
|
612
|
+
var HLS_MIME_HINTS = ["application/x-mpegurl", "application/vnd.apple.mpegurl"];
|
|
613
|
+
function isHlsUrl(url) {
|
|
614
|
+
if (typeof url !== "string" || url.length === 0) return false;
|
|
615
|
+
const lower = url.toLowerCase();
|
|
616
|
+
if (HLS_MIME_HINTS.some((hint) => lower.includes(hint))) return true;
|
|
617
|
+
return lower.split(/[?#]/)[0].endsWith(".m3u8");
|
|
618
|
+
}
|
|
619
|
+
var HLSPlayer = class {
|
|
620
|
+
constructor(url) {
|
|
621
|
+
this.hls = null;
|
|
622
|
+
this.url = url;
|
|
623
|
+
this.playerFile = { url, qualityLevels: [] };
|
|
624
|
+
}
|
|
625
|
+
async initialize(videoElement) {
|
|
626
|
+
const { default: HlsCtor } = await import('hls.js');
|
|
627
|
+
if (!HlsCtor.isSupported()) {
|
|
628
|
+
videoElement.src = this.url;
|
|
629
|
+
return this.playerFile;
|
|
630
|
+
}
|
|
631
|
+
const hls = new HlsCtor();
|
|
632
|
+
this.hls = hls;
|
|
633
|
+
hls.loadSource(this.url);
|
|
634
|
+
hls.attachMedia(videoElement);
|
|
635
|
+
return this.playerFile;
|
|
636
|
+
}
|
|
637
|
+
getAudioTracks() {
|
|
638
|
+
if (!this.hls) return [];
|
|
639
|
+
return this.hls.audioTracks.map((track) => ({
|
|
640
|
+
id: String(track.id),
|
|
641
|
+
name: track.name,
|
|
642
|
+
lang: track.lang || "unknown"
|
|
643
|
+
}));
|
|
644
|
+
}
|
|
645
|
+
getSubtitles() {
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
switchAudioTrack(trackId) {
|
|
649
|
+
if (this.hls) {
|
|
650
|
+
const id = Number(trackId);
|
|
651
|
+
if (!Number.isNaN(id)) this.hls.audioTrack = id;
|
|
652
|
+
}
|
|
653
|
+
return Promise.resolve();
|
|
654
|
+
}
|
|
655
|
+
switchSubtitle() {
|
|
656
|
+
return Promise.resolve();
|
|
657
|
+
}
|
|
658
|
+
/** Quality renditions — not on `VideoPlayer`; read directly by the quality hook. */
|
|
659
|
+
getQualityLevels() {
|
|
660
|
+
if (!this.hls) return [];
|
|
661
|
+
return this.hls.levels.map((level, index) => ({
|
|
662
|
+
index,
|
|
663
|
+
height: level.height,
|
|
664
|
+
bitrate: level.bitrate,
|
|
665
|
+
name: level.name || (level.height > 0 ? `${level.height}p` : `Level ${index + 1}`)
|
|
666
|
+
}));
|
|
667
|
+
}
|
|
668
|
+
/** Pins a quality level; `-1` restores automatic (ABR) selection. */
|
|
669
|
+
setQualityLevel(levelIndex) {
|
|
670
|
+
if (this.hls) this.hls.currentLevel = levelIndex;
|
|
671
|
+
}
|
|
672
|
+
destroy() {
|
|
673
|
+
if (this.hls) {
|
|
674
|
+
this.hls.destroy();
|
|
675
|
+
this.hls = null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
static isCompatible(url) {
|
|
679
|
+
return isHlsUrl(url);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
|
|
613
683
|
// src/video-processor.ts
|
|
614
684
|
var SimplePlayerAdapter = class {
|
|
615
685
|
constructor(file, externalSubtitles = []) {
|
|
@@ -666,13 +736,21 @@ var MKVPlayerAdapter = class {
|
|
|
666
736
|
return this.player.tracksReady;
|
|
667
737
|
}
|
|
668
738
|
};
|
|
669
|
-
function createVideoPlayer(
|
|
670
|
-
if (
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
739
|
+
function createVideoPlayer(source, externalSubtitles = [], onProgress) {
|
|
740
|
+
if (typeof source === "string") {
|
|
741
|
+
if (isHlsUrl(source)) {
|
|
742
|
+
return new HLSPlayer(source);
|
|
743
|
+
}
|
|
744
|
+
throw new Error(
|
|
745
|
+
`createVideoPlayer: unsupported URL "${source}" \u2014 only HLS (.m3u8) stream URLs are supported; pass a File for other formats.`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (MKVPlayer.isCompatible(source)) {
|
|
749
|
+
return new MKVPlayerAdapter(source, onProgress);
|
|
750
|
+
} else if (SimplePlayer.isCompatible(source)) {
|
|
751
|
+
return new SimplePlayerAdapter(source, externalSubtitles);
|
|
674
752
|
} else {
|
|
675
|
-
return new SimplePlayerAdapter(
|
|
753
|
+
return new SimplePlayerAdapter(source, externalSubtitles);
|
|
676
754
|
}
|
|
677
755
|
}
|
|
678
756
|
|
|
@@ -1430,6 +1508,8 @@ var ProgressEstimator = class {
|
|
|
1430
1508
|
}
|
|
1431
1509
|
// No reset() method — create a new instance per file load instead.
|
|
1432
1510
|
};
|
|
1511
|
+
|
|
1512
|
+
// src/utils/ffmpeg-singleton.ts
|
|
1433
1513
|
var instance = null;
|
|
1434
1514
|
var loading = null;
|
|
1435
1515
|
var defaultCDN = "https://unpkg.com/@ffmpeg/core@0.12.10/dist/umd";
|
|
@@ -1437,6 +1517,10 @@ async function getFFmpeg() {
|
|
|
1437
1517
|
if (instance) return instance;
|
|
1438
1518
|
if (loading) return loading;
|
|
1439
1519
|
loading = (async () => {
|
|
1520
|
+
const [{ FFmpeg }, { toBlobURL }] = await Promise.all([
|
|
1521
|
+
import('@ffmpeg/ffmpeg'),
|
|
1522
|
+
import('@ffmpeg/util')
|
|
1523
|
+
]);
|
|
1440
1524
|
const ffmpeg = new FFmpeg();
|
|
1441
1525
|
const baseURL = getConfig().ffmpegCDN || defaultCDN;
|
|
1442
1526
|
await ffmpeg.load({
|
|
@@ -1453,4 +1537,4 @@ function resetFFmpeg() {
|
|
|
1453
1537
|
loading = null;
|
|
1454
1538
|
}
|
|
1455
1539
|
|
|
1456
|
-
export { ASSRenderer, CancellationError, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, MKVPlayer, ProgressEstimator, SimplePlayer, SubtitleConverter, UniversalSubtitleManager, VIDEO_EXTENSIONS, acceptDisclaimer, applyOffsetToVtt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
|
1540
|
+
export { ASSRenderer, CancellationError, DEFAULT_SHORTCUTS, DEFAULT_TRACKERS, DISCLAIMER_KEY, FLAG_MAGNET_LINK, HLSPlayer, MKVPlayer, ProgressEstimator, SimplePlayer, SubtitleConverter, UniversalSubtitleManager, VIDEO_EXTENSIONS, acceptDisclaimer, applyOffsetToVtt, captureVideoThumbnail, configureLightBird, createOffsetVttUrl, createVideoPlayer, destroyWebTorrentClient, exportPlaylist, extractNativeMetadata, formatShortcutKey, getFFmpeg, getVideoFiles, getWebTorrentClient, hasAcceptedDisclaimer, initFeatureFlags, isHlsUrl, isInteractiveElement, isMagnetUri, isVideoFile, loadShortcuts, matchesShortcut, parseChaptersFromFFmpegLog, parseChaptersFromVtt, parseM3U8, parseMediaError, resetFFmpeg, saveShortcuts, validateFile };
|
package/dist/react/index.d.cts
CHANGED
|
@@ -95,6 +95,18 @@ interface SubtitleTrackMeta {
|
|
|
95
95
|
format: string | null;
|
|
96
96
|
language: string | null;
|
|
97
97
|
}
|
|
98
|
+
/** A single HLS quality rendition (e.g. 1080p, 720p). */
|
|
99
|
+
interface QualityLevel {
|
|
100
|
+
index: number;
|
|
101
|
+
height: number;
|
|
102
|
+
bitrate: number;
|
|
103
|
+
name: string;
|
|
104
|
+
}
|
|
105
|
+
/** Result of initialising an HLS stream via `HLSPlayer`. */
|
|
106
|
+
interface HLSPlayerFile {
|
|
107
|
+
url: string;
|
|
108
|
+
qualityLevels: QualityLevel[];
|
|
109
|
+
}
|
|
98
110
|
|
|
99
111
|
declare function useVideoFilters(videoRef: RefObject<HTMLVideoElement | null>): {
|
|
100
112
|
filters: VideoFilters;
|
|
@@ -236,7 +248,7 @@ interface MKVPlayerFile {
|
|
|
236
248
|
activeSubtitleTrack: string;
|
|
237
249
|
}
|
|
238
250
|
|
|
239
|
-
type ProcessedFile = SimplePlayerFile | MKVPlayerFile;
|
|
251
|
+
type ProcessedFile = SimplePlayerFile | MKVPlayerFile | HLSPlayerFile;
|
|
240
252
|
interface VideoPlayer {
|
|
241
253
|
initialize(videoElement: HTMLVideoElement): Promise<ProcessedFile>;
|
|
242
254
|
getAudioTracks(): AudioTrack[];
|
package/dist/react/index.d.ts
CHANGED
|
@@ -95,6 +95,18 @@ interface SubtitleTrackMeta {
|
|
|
95
95
|
format: string | null;
|
|
96
96
|
language: string | null;
|
|
97
97
|
}
|
|
98
|
+
/** A single HLS quality rendition (e.g. 1080p, 720p). */
|
|
99
|
+
interface QualityLevel {
|
|
100
|
+
index: number;
|
|
101
|
+
height: number;
|
|
102
|
+
bitrate: number;
|
|
103
|
+
name: string;
|
|
104
|
+
}
|
|
105
|
+
/** Result of initialising an HLS stream via `HLSPlayer`. */
|
|
106
|
+
interface HLSPlayerFile {
|
|
107
|
+
url: string;
|
|
108
|
+
qualityLevels: QualityLevel[];
|
|
109
|
+
}
|
|
98
110
|
|
|
99
111
|
declare function useVideoFilters(videoRef: RefObject<HTMLVideoElement | null>): {
|
|
100
112
|
filters: VideoFilters;
|
|
@@ -236,7 +248,7 @@ interface MKVPlayerFile {
|
|
|
236
248
|
activeSubtitleTrack: string;
|
|
237
249
|
}
|
|
238
250
|
|
|
239
|
-
type ProcessedFile = SimplePlayerFile | MKVPlayerFile;
|
|
251
|
+
type ProcessedFile = SimplePlayerFile | MKVPlayerFile | HLSPlayerFile;
|
|
240
252
|
interface VideoPlayer {
|
|
241
253
|
initialize(videoElement: HTMLVideoElement): Promise<ProcessedFile>;
|
|
242
254
|
getAudioTracks(): AudioTrack[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightbird/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Client-side video player engine. Plays MKV, MP4, WebM with full subtitle, audio track, and chapter support. No server required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Punyam Singh",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@openfeature/unleash-web-provider": "^0.1.1",
|
|
58
58
|
"@openfeature/web-sdk": "^1.6.0",
|
|
59
59
|
"ass-compiler": "^0.1.16",
|
|
60
|
+
"hls.js": "^1.6.16",
|
|
60
61
|
"webtorrent": "^2.8.5"
|
|
61
62
|
},
|
|
62
63
|
"optionalDependencies": {
|