playsvideo 0.0.1
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/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/adapters/node-ffmpeg.d.ts +15 -0
- package/dist/adapters/node-ffmpeg.d.ts.map +1 -0
- package/dist/adapters/node-ffmpeg.js +35 -0
- package/dist/adapters/node-ffmpeg.js.map +1 -0
- package/dist/adapters/node-ffprobe.d.ts +12 -0
- package/dist/adapters/node-ffprobe.d.ts.map +1 -0
- package/dist/adapters/node-ffprobe.js +55 -0
- package/dist/adapters/node-ffprobe.js.map +1 -0
- package/dist/engine.d.ts +51 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +386 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/adts-parse.d.ts +3 -0
- package/dist/pipeline/adts-parse.d.ts.map +1 -0
- package/dist/pipeline/adts-parse.js +36 -0
- package/dist/pipeline/adts-parse.js.map +1 -0
- package/dist/pipeline/audio-transcode.d.ts +42 -0
- package/dist/pipeline/audio-transcode.d.ts.map +1 -0
- package/dist/pipeline/audio-transcode.js +147 -0
- package/dist/pipeline/audio-transcode.js.map +1 -0
- package/dist/pipeline/codec-probe.d.ts +22 -0
- package/dist/pipeline/codec-probe.d.ts.map +1 -0
- package/dist/pipeline/codec-probe.js +70 -0
- package/dist/pipeline/codec-probe.js.map +1 -0
- package/dist/pipeline/demux.d.ts +23 -0
- package/dist/pipeline/demux.d.ts.map +1 -0
- package/dist/pipeline/demux.js +97 -0
- package/dist/pipeline/demux.js.map +1 -0
- package/dist/pipeline/mux.d.ts +15 -0
- package/dist/pipeline/mux.d.ts.map +1 -0
- package/dist/pipeline/mux.js +60 -0
- package/dist/pipeline/mux.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +22 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +112 -0
- package/dist/pipeline/pipeline.js.map +1 -0
- package/dist/pipeline/playlist.d.ts +5 -0
- package/dist/pipeline/playlist.d.ts.map +1 -0
- package/dist/pipeline/playlist.js +72 -0
- package/dist/pipeline/playlist.js.map +1 -0
- package/dist/pipeline/segment-plan.d.ts +10 -0
- package/dist/pipeline/segment-plan.d.ts.map +1 -0
- package/dist/pipeline/segment-plan.js +55 -0
- package/dist/pipeline/segment-plan.js.map +1 -0
- package/dist/pipeline/subtitle.d.ts +17 -0
- package/dist/pipeline/subtitle.d.ts.map +1 -0
- package/dist/pipeline/subtitle.js +184 -0
- package/dist/pipeline/subtitle.js.map +1 -0
- package/dist/pipeline/types.d.ts +98 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/pipeline/types.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jonathan Graehl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/wordmark-dark.svg">
|
|
3
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/wordmark-light.svg">
|
|
4
|
+
<img alt="playsvideo" src="docs/wordmark-light.svg" width="340">
|
|
5
|
+
</picture>
|
|
6
|
+
|
|
7
|
+
**You may not need VLC.** Play any video file in the browser — no install, no upload.
|
|
8
|
+
|
|
9
|
+
[Try it at playsvideo.com](https://playsvideo.com) | Drop a file. It plays.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Most video files won't play in a browser — not because the browser can't *decode* the video, but because it can't open the container or handle the audio codec. playsvideo fixes that entirely client-side: it remuxes containers and transcodes audio on the fly, so your MKV with AC-3 audio just works.
|
|
14
|
+
|
|
15
|
+
### What it handles
|
|
16
|
+
|
|
17
|
+
| | Formats | Notes |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **Containers** | MKV, MP4, AVI, TS, WebM | Demuxed and remuxed to fMP4 |
|
|
20
|
+
| **Video** | H.264, H.265, VP9, AV1 | Passthrough — plays ~99% of files (~90% on Firefox; HEVC transcode planned) |
|
|
21
|
+
| **Audio** | AAC, MP3, AC-3, E-AC-3, DTS, FLAC, Opus | Unsupported codecs transcoded to AAC on the fly |
|
|
22
|
+
| **Subtitles** | SRT, ASS/SSA | Extracted and displayed as WebVTT |
|
|
23
|
+
|
|
24
|
+
See [supported media](docs/supported-media.md) for the full codec matrix, browser compatibility, and transcode details.
|
|
25
|
+
|
|
26
|
+
### How it works
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Video file (MKV, MP4, AVI, …)
|
|
30
|
+
→ mediabunny demux (streaming, any file size)
|
|
31
|
+
→ keyframe-aligned segment plan
|
|
32
|
+
→ per segment:
|
|
33
|
+
video passed through or transcoded if needed
|
|
34
|
+
audio transcoded only if needed (AC-3/DTS/FLAC → AAC)
|
|
35
|
+
muxed to fMP4
|
|
36
|
+
→ hls.js plays segments on demand
|
|
37
|
+
→ subtitles extracted to WebVTT
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Video transcode is almost never needed — browsers natively decode the vast majority of video codecs in the wild. When audio transcode is needed, a lightweight 1.5 MB ffmpeg.wasm build is lazy-loaded on demand — a few seconds at a time, entirely in-browser.
|
|
41
|
+
|
|
42
|
+
### Under the hood
|
|
43
|
+
|
|
44
|
+
The obvious approach — ffmpeg compiled to WebAssembly — can't handle large files (WORKERFS is catastrophically slow, MEMFS can't hold them). The trick is to split the problem:
|
|
45
|
+
|
|
46
|
+
- **[mediabunny](https://github.com/Vanilagy/mediabunny)** — streaming demux/remux in pure TypeScript, works on any size file
|
|
47
|
+
- **[ffmpeg.wasm](https://github.com/nicolo-ribaudo/ffmpeg.wasm)** — only transcodes short audio segments via MEMFS
|
|
48
|
+
- **[hls.js](https://github.com/video-dev/hls.js)** — battle-tested HLS playback via Media Source Extensions
|
|
49
|
+
|
|
50
|
+
Each piece existed separately. Nobody combined them.
|
|
51
|
+
|
|
52
|
+
### Use as a library
|
|
53
|
+
|
|
54
|
+
> Not yet published to npm. The API below is the current interface.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { PlaysVideoEngine } from 'playsvideo';
|
|
58
|
+
|
|
59
|
+
const video = document.querySelector('video')!;
|
|
60
|
+
const engine = new PlaysVideoEngine(video);
|
|
61
|
+
|
|
62
|
+
engine.addEventListener('ready', (e) => {
|
|
63
|
+
console.log(`${e.detail.totalSegments} segments, ${e.detail.durationSec}s`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
engine.loadFile(file); // from drag-and-drop or <input type="file">
|
|
67
|
+
engine.destroy(); // clean up
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Roadmap
|
|
71
|
+
|
|
72
|
+
- **npm publish** — `npm install playsvideo`
|
|
73
|
+
- **WebCodecs** — replace ffmpeg.wasm audio transcode with `AudioDecoder`/`AudioEncoder` (smaller bundle, lower latency)
|
|
74
|
+
- **Video transcode** — hardware-accelerated decode via `VideoDecoder` for edge-case codecs
|
|
75
|
+
|
|
76
|
+
<details>
|
|
77
|
+
<summary><strong>Development</strong></summary>
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm run setup # install deps + download ffmpeg-core.wasm
|
|
81
|
+
npm run dev # vite dev server
|
|
82
|
+
npm run typecheck # tsc --noEmit
|
|
83
|
+
npm run test:unit # fast unit tests
|
|
84
|
+
npm run lint # biome lint
|
|
85
|
+
npm run format # biome format
|
|
86
|
+
npm run test:integration # requires test fixtures in tests/fixtures/
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
src/pipeline/ Core modules (demux, mux, segment plan, audio transcode,
|
|
91
|
+
codec probe, playlist, subtitle extraction)
|
|
92
|
+
src/adapters/ Platform adapters (ffmpeg.wasm for browser, node-ffmpeg for tests)
|
|
93
|
+
src/worker.ts Web worker — demux + on-demand segment processing
|
|
94
|
+
src/engine.ts PlaysVideoEngine class (worker, hls.js, subtitles)
|
|
95
|
+
src/pwa-player.ts Browser entry — file picker, drag-and-drop
|
|
96
|
+
tests/ Unit, integration, and e2e (Playwright) tests
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
</details>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FfmpegRunner } from '../pipeline/types.js';
|
|
2
|
+
export declare class NodeFfmpegRunner implements FfmpegRunner {
|
|
3
|
+
private ffmpegPath;
|
|
4
|
+
private dir;
|
|
5
|
+
constructor(dir: string, ffmpegPath?: string);
|
|
6
|
+
writeInput(name: string, data: Uint8Array): Promise<void>;
|
|
7
|
+
readOutput(name: string): Promise<Uint8Array>;
|
|
8
|
+
deleteFile(name: string): Promise<void>;
|
|
9
|
+
run(args: string[]): Promise<{
|
|
10
|
+
exitCode: number;
|
|
11
|
+
stderr: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function makeTempDir(prefix?: string): Promise<string>;
|
|
15
|
+
//# sourceMappingURL=node-ffmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-ffmpeg.d.ts","sourceRoot":"","sources":["../../src/adapters/node-ffmpeg.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAAS;gBAER,GAAG,EAAE,MAAM,EAAE,UAAU,SAAW;IAKxC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAI7C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAgBzE;AAED,wBAAsB,WAAW,CAAC,MAAM,SAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdtemp, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
export class NodeFfmpegRunner {
|
|
6
|
+
ffmpegPath;
|
|
7
|
+
dir;
|
|
8
|
+
constructor(dir, ffmpegPath = 'ffmpeg') {
|
|
9
|
+
this.dir = dir;
|
|
10
|
+
this.ffmpegPath = ffmpegPath;
|
|
11
|
+
}
|
|
12
|
+
async writeInput(name, data) {
|
|
13
|
+
await writeFile(join(this.dir, name), data);
|
|
14
|
+
}
|
|
15
|
+
async readOutput(name) {
|
|
16
|
+
return new Uint8Array(await readFile(join(this.dir, name)));
|
|
17
|
+
}
|
|
18
|
+
async deleteFile(name) {
|
|
19
|
+
await unlink(join(this.dir, name)).catch(() => { });
|
|
20
|
+
}
|
|
21
|
+
async run(args) {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
execFile(this.ffmpegPath, args, { maxBuffer: 100 * 1024 * 1024, cwd: this.dir }, (error, _stdout, stderr) => {
|
|
24
|
+
resolve({
|
|
25
|
+
exitCode: error?.code !== undefined ? (typeof error.code === 'number' ? error.code : 1) : 0,
|
|
26
|
+
stderr: stderr || '',
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function makeTempDir(prefix = 'playsvideo-') {
|
|
33
|
+
return mkdtemp(join(tmpdir(), prefix));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=node-ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-ffmpeg.js","sourceRoot":"","sources":["../../src/adapters/node-ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,OAAO,gBAAgB;IACnB,UAAU,CAAS;IACnB,GAAG,CAAS;IAEpB,YAAY,GAAW,EAAE,UAAU,GAAG,QAAQ;QAC5C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY,EAAE,IAAgB;QAC7C,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,OAAO,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAc;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,QAAQ,CACN,IAAI,CAAC,UAAU,EACf,IAAI,EACJ,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAC/C,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;gBACzB,OAAO,CAAC;oBACN,QAAQ,EACN,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBACnF,MAAM,EAAE,MAAM,IAAI,EAAE;iBACrB,CAAC,CAAC;YACL,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAM,GAAG,aAAa;IACtD,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProbeResult } from '../pipeline/types.js';
|
|
2
|
+
export declare class NodeFfprobeRunner {
|
|
3
|
+
private ffprobePath;
|
|
4
|
+
constructor(ffprobePath?: string);
|
|
5
|
+
probe(inputPath: string): Promise<ProbeResult>;
|
|
6
|
+
verifyDecodable(inputPath: string, ffmpegPath?: string): Promise<{
|
|
7
|
+
ok: boolean;
|
|
8
|
+
stderr: string;
|
|
9
|
+
}>;
|
|
10
|
+
private execJson;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=node-ffprobe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-ffprobe.d.ts","sourceRoot":"","sources":["../../src/adapters/node-ffprobe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,sBAAsB,CAAC;AAErE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,SAAY;IAI7B,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA+B9C,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,UAAU,SAAW,GACpB,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAa3C,OAAO,CAAC,QAAQ;CAWjB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
export class NodeFfprobeRunner {
|
|
3
|
+
ffprobePath;
|
|
4
|
+
constructor(ffprobePath = 'ffprobe') {
|
|
5
|
+
this.ffprobePath = ffprobePath;
|
|
6
|
+
}
|
|
7
|
+
async probe(inputPath) {
|
|
8
|
+
const stdout = await this.execJson([
|
|
9
|
+
'-v',
|
|
10
|
+
'error',
|
|
11
|
+
'-print_format',
|
|
12
|
+
'json',
|
|
13
|
+
'-show_streams',
|
|
14
|
+
'-show_format',
|
|
15
|
+
inputPath,
|
|
16
|
+
]);
|
|
17
|
+
const data = JSON.parse(stdout);
|
|
18
|
+
const streams = (data.streams || []).map((s) => ({
|
|
19
|
+
index: s.index,
|
|
20
|
+
codecType: s.codec_type,
|
|
21
|
+
codecName: s.codec_name,
|
|
22
|
+
width: s.width,
|
|
23
|
+
height: s.height,
|
|
24
|
+
sampleRate: s.sample_rate ? parseInt(s.sample_rate, 10) : undefined,
|
|
25
|
+
channels: s.channels,
|
|
26
|
+
duration: s.duration ? parseFloat(s.duration) : undefined,
|
|
27
|
+
}));
|
|
28
|
+
return {
|
|
29
|
+
format: data.format?.format_name ?? 'unknown',
|
|
30
|
+
duration: parseFloat(data.format?.duration ?? '0'),
|
|
31
|
+
bitRate: data.format?.bit_rate ? parseFloat(data.format.bit_rate) : undefined,
|
|
32
|
+
streams,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async verifyDecodable(inputPath, ffmpegPath = 'ffmpeg') {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
execFile(ffmpegPath, ['-hide_banner', '-loglevel', 'error', '-i', inputPath, '-f', 'null', '-'], { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {
|
|
38
|
+
resolve({ ok: !error, stderr: stderr || '' });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
execJson(args) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
execFile(this.ffprobePath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
45
|
+
if (error) {
|
|
46
|
+
reject(new Error(`ffprobe failed: ${stderr || error.message}`));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
resolve(stdout);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=node-ffprobe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-ffprobe.js","sourceRoot":"","sources":["../../src/adapters/node-ffprobe.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SubtitleTrackInfo } from './pipeline/types.js';
|
|
2
|
+
export type EnginePhase = 'idle' | 'demuxing' | 'ready' | 'error';
|
|
3
|
+
export interface ReadyDetail {
|
|
4
|
+
totalSegments: number;
|
|
5
|
+
durationSec: number;
|
|
6
|
+
subtitleTracks: SubtitleTrackInfo[];
|
|
7
|
+
}
|
|
8
|
+
export interface ErrorDetail {
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface LoadingDetail {
|
|
12
|
+
file: File;
|
|
13
|
+
}
|
|
14
|
+
interface EngineEventMap {
|
|
15
|
+
ready: CustomEvent<ReadyDetail>;
|
|
16
|
+
error: CustomEvent<ErrorDetail>;
|
|
17
|
+
loading: CustomEvent<LoadingDetail>;
|
|
18
|
+
}
|
|
19
|
+
export declare class PlaysVideoEngine extends EventTarget {
|
|
20
|
+
private video;
|
|
21
|
+
private worker;
|
|
22
|
+
private hls;
|
|
23
|
+
private pendingSegments;
|
|
24
|
+
private playlist;
|
|
25
|
+
private initData;
|
|
26
|
+
private pendingInit;
|
|
27
|
+
private pendingPlaylist;
|
|
28
|
+
private segmentRequestTimes;
|
|
29
|
+
private subtitleBlobUrls;
|
|
30
|
+
private _subtitleTracks;
|
|
31
|
+
private _phase;
|
|
32
|
+
private _totalSegments;
|
|
33
|
+
private _durationSec;
|
|
34
|
+
get phase(): EnginePhase;
|
|
35
|
+
get loading(): boolean;
|
|
36
|
+
get totalSegments(): number;
|
|
37
|
+
get durationSec(): number;
|
|
38
|
+
get subtitleTracks(): SubtitleTrackInfo[];
|
|
39
|
+
constructor(video: HTMLVideoElement);
|
|
40
|
+
loadFile(file: File): void;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
addEventListener<K extends keyof EngineEventMap>(type: K, listener: (ev: EngineEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
|
|
43
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
44
|
+
private handleWorkerMessage;
|
|
45
|
+
private requestSegment;
|
|
46
|
+
private startHls;
|
|
47
|
+
private addSubtitleTrack;
|
|
48
|
+
private removeSubtitleTracks;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,iBAAiB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,UAAU,cAAc;IACtB,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAChC,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;CACrC;AAED,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,GAAG,CAAoB;IAG/B,OAAO,CAAC,eAAe,CAGnB;IAGJ,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,WAAW,CAGH;IAChB,OAAO,CAAC,eAAe,CAGP;IAEhB,OAAO,CAAC,mBAAmB,CAA6B;IAGxD,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,eAAe,CAA2B;IAGlD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAK;IAEzB,IAAI,KAAK,IAAI,WAAW,CAEvB;IACD,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,aAAa,IAAI,MAAM,CAE1B;IACD,IAAI,WAAW,IAAI,MAAM,CAExB;IACD,IAAI,cAAc,IAAI,iBAAiB,EAAE,CAExC;gBAEW,KAAK,EAAE,gBAAgB;IAKnC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAkC1B,OAAO,IAAI,IAAI;IAkBf,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7C,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EACzC,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI;IACP,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI;IASP,OAAO,CAAC,mBAAmB;IAiF3B,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,QAAQ;IAgKhB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,oBAAoB;CAK7B"}
|