@karsten_zhou/vite-plugin-hls 1.0.1 → 1.1.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/README.md +14 -21
- package/dist/index.cjs +208 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -6
- package/dist/index.d.ts +0 -6
- package/dist/index.js +220 -124
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -22,15 +22,9 @@ npm install -D @karsten_zhou/vite-plugin-hls
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
[`vite`](https://vitejs.dev) is a **peer dependency**; install it if it is not
|
|
25
|
-
already present in your project.
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
## Requirements
|
|
29
|
-
|
|
30
|
-
- Node.js `>=24.3`
|
|
31
|
-
- Vite `^8` (peer)
|
|
32
|
-
- The build runs `ffmpeg`; `ffmpeg-static` downloads a platform binary on
|
|
33
|
-
install.
|
|
25
|
+
already present in your project. Encoding is powered by
|
|
26
|
+
[`node-av`](https://github.com/seydx/node-av), which downloads and manages a
|
|
27
|
+
platform-specific native FFmpeg build on install.
|
|
34
28
|
|
|
35
29
|
## Usage
|
|
36
30
|
|
|
@@ -75,16 +69,15 @@ The plugin also exports `hlsVideos` as the default export, so
|
|
|
75
69
|
|
|
76
70
|
## Options
|
|
77
71
|
|
|
78
|
-
| Option | Type | Default
|
|
79
|
-
| ----------------- | ------------------------ |
|
|
80
|
-
| `mode` | `"single" \| "adaptive"` | `"single"`
|
|
81
|
-
| `variants` | `HlsVariant[]` | —
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `crf` | `number` | `23` | H.264 CRF used in adaptive mode. |
|
|
72
|
+
| Option | Type | Default | Description |
|
|
73
|
+
| ----------------- | ------------------------ | -------------- | ----------------------------------------------------------------- |
|
|
74
|
+
| `mode` | `"single" \| "adaptive"` | `"single"` | `"single"` remuxes the source; `"adaptive"` encodes renditions. |
|
|
75
|
+
| `variants` | `HlsVariant[]` | — | Required in `adaptive` mode: `{ height, bitrate }` per rendition. |
|
|
76
|
+
| `segmentDuration` | `number` | `4` | HLS segment duration in seconds. |
|
|
77
|
+
| `segmentType` | `"fmp4" \| "mpegts"` | `"fmp4"` | HLS segment container format. |
|
|
78
|
+
| `outputDir` | `string` | `"assets/hls"` | Directory (inside the Vite output) where HLS assets are written. |
|
|
79
|
+
| `preset` | `string` | `"medium"` | FFmpeg `-preset` used in adaptive mode. |
|
|
80
|
+
| `crf` | `number` | `23` | H.264 CRF used in adaptive mode. |
|
|
88
81
|
|
|
89
82
|
### Types
|
|
90
83
|
|
|
@@ -105,8 +98,8 @@ import type {
|
|
|
105
98
|
2. `load` transcodes the source with FFmpeg into an HLS output folder inside
|
|
106
99
|
Vite's cache directory (only once per build — results are memoized).
|
|
107
100
|
3. The output is emitted as static assets into `outputDir`, and the module's
|
|
108
|
-
default export is set to the resolved public URL of the playlist
|
|
109
|
-
|
|
101
|
+
default export is set to the resolved public URL of the playlist (with
|
|
102
|
+
Vite's `base` baked in), so it works under any `base` — client or SSR.
|
|
110
103
|
|
|
111
104
|
### SSR note
|
|
112
105
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
8
10
|
var __export = (target, all) => {
|
|
9
11
|
for (var name in all)
|
|
10
12
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -17,15 +19,48 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
19
|
}
|
|
18
20
|
return to;
|
|
19
21
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
22
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
var __using = (stack, value, async) => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
26
|
+
var dispose, inner;
|
|
27
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
28
|
+
if (dispose === void 0) {
|
|
29
|
+
dispose = value[__knownSymbol("dispose")];
|
|
30
|
+
if (async) inner = dispose;
|
|
31
|
+
}
|
|
32
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
33
|
+
if (inner) dispose = function() {
|
|
34
|
+
try {
|
|
35
|
+
inner.call(this);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return Promise.reject(e);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
stack.push([async, dispose, value]);
|
|
41
|
+
} else if (async) {
|
|
42
|
+
stack.push([async]);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
};
|
|
46
|
+
var __callDispose = (stack, error, hasError) => {
|
|
47
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
48
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
49
|
+
};
|
|
50
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
51
|
+
var next = (it) => {
|
|
52
|
+
while (it = stack.pop()) {
|
|
53
|
+
try {
|
|
54
|
+
var result = it[1] && it[1].call(it[2]);
|
|
55
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
fail(e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasError) throw error;
|
|
61
|
+
};
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
29
64
|
|
|
30
65
|
// src/index.ts
|
|
31
66
|
var index_exports = {};
|
|
@@ -36,10 +71,8 @@ __export(index_exports, {
|
|
|
36
71
|
module.exports = __toCommonJS(index_exports);
|
|
37
72
|
|
|
38
73
|
// src/options.ts
|
|
39
|
-
var import_ffmpeg_static = __toESM(require("ffmpeg-static"), 1);
|
|
40
74
|
function resolveOptions(input = {}) {
|
|
41
75
|
const common = {
|
|
42
|
-
ffmpegPath: input.ffmpegPath ?? import_ffmpeg_static.default ?? "ffmpeg",
|
|
43
76
|
segmentDuration: input.segmentDuration ?? 4,
|
|
44
77
|
segmentType: input.segmentType ?? "fmp4",
|
|
45
78
|
outputDir: input.outputDir ?? "assets/hls",
|
|
@@ -106,7 +139,8 @@ var import_promises4 = require("fs/promises");
|
|
|
106
139
|
var import_promises = require("fs/promises");
|
|
107
140
|
var import_node_path2 = require("path");
|
|
108
141
|
var import_node_os = require("os");
|
|
109
|
-
var
|
|
142
|
+
var import_api = require("node-av/api");
|
|
143
|
+
var import_constants = require("node-av/constants");
|
|
110
144
|
|
|
111
145
|
// src/bitrate.ts
|
|
112
146
|
function bitrateToNumber(bitrate) {
|
|
@@ -121,121 +155,171 @@ function bitrateToNumber(bitrate) {
|
|
|
121
155
|
}
|
|
122
156
|
|
|
123
157
|
// src/encoder.ts
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
var PLAYLIST = "index.m3u8";
|
|
159
|
+
function hlsMuxerOptions(outputDirectory, options) {
|
|
160
|
+
const extension = options.segmentType === "fmp4" ? "m4s" : "ts";
|
|
161
|
+
const hls = {
|
|
162
|
+
hls_time: String(options.segmentDuration),
|
|
163
|
+
hls_playlist_type: "vod",
|
|
164
|
+
hls_segment_filename: (0, import_node_path2.join)(outputDirectory, `segment-%05d.${extension}`)
|
|
165
|
+
};
|
|
166
|
+
if (options.segmentType === "fmp4") {
|
|
167
|
+
hls.hls_segment_type = "fmp4";
|
|
168
|
+
hls.hls_fmp4_init_filename = (0, import_node_path2.join)(outputDirectory, "init.mp4");
|
|
169
|
+
}
|
|
170
|
+
return hls;
|
|
171
|
+
}
|
|
172
|
+
async function relativizePlaylist(playlist, outputDirectory) {
|
|
173
|
+
let text;
|
|
174
|
+
try {
|
|
175
|
+
text = await (0, import_promises.readFile)(playlist, "utf8");
|
|
176
|
+
} catch {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const prefix = outputDirectory.replace(/[\\/]+$/, "");
|
|
180
|
+
text = text.split(prefix + "\\").join("").split(prefix + "/").join("");
|
|
181
|
+
await (0, import_promises.writeFile)(playlist, text, "utf8");
|
|
182
|
+
}
|
|
183
|
+
async function remuxToHls(source, playlist, hls) {
|
|
184
|
+
var _stack = [];
|
|
185
|
+
try {
|
|
186
|
+
const input = __using(_stack, await import_api.Demuxer.open(source), true);
|
|
187
|
+
const output = __using(_stack, await import_api.Muxer.open(playlist, {
|
|
188
|
+
format: "hls",
|
|
189
|
+
input,
|
|
190
|
+
options: hls
|
|
191
|
+
}), true);
|
|
192
|
+
const routed = /* @__PURE__ */ new Map();
|
|
193
|
+
const video = input.video();
|
|
194
|
+
if (video) {
|
|
195
|
+
routed.set(video.index, output.addStream(video));
|
|
196
|
+
}
|
|
197
|
+
const audio = input.audio();
|
|
198
|
+
if (audio) {
|
|
199
|
+
routed.set(audio.index, output.addStream(audio));
|
|
200
|
+
}
|
|
201
|
+
for await (const packet of input.packets()) {
|
|
202
|
+
if (packet === null) {
|
|
203
|
+
break;
|
|
145
204
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (code === 0) {
|
|
150
|
-
resolvePromise();
|
|
151
|
-
return;
|
|
205
|
+
const index = routed.get(packet.streamIndex);
|
|
206
|
+
if (index !== void 0) {
|
|
207
|
+
await output.writePacket(packet, index);
|
|
152
208
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
209
|
+
packet.free();
|
|
210
|
+
}
|
|
211
|
+
await output.close();
|
|
212
|
+
await relativizePlaylist(playlist, (0, import_node_path2.dirname)(playlist));
|
|
213
|
+
} catch (_) {
|
|
214
|
+
var _error = _, _hasError = true;
|
|
215
|
+
} finally {
|
|
216
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
217
|
+
_promise && await _promise;
|
|
218
|
+
}
|
|
163
219
|
}
|
|
164
|
-
async function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
220
|
+
async function transcodeToHls(source, playlist, hls, options, variant) {
|
|
221
|
+
var _stack = [];
|
|
222
|
+
try {
|
|
223
|
+
if (options.mode !== "adaptive") {
|
|
224
|
+
throw new Error(
|
|
225
|
+
"[vite-plugin-hls] internal: transcode requires adaptive mode"
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const input = __using(_stack, await import_api.Demuxer.open(source), true);
|
|
229
|
+
const output = __using(_stack, await import_api.Muxer.open(playlist, {
|
|
230
|
+
format: "hls",
|
|
231
|
+
input,
|
|
232
|
+
options: hls
|
|
233
|
+
}), true);
|
|
234
|
+
const tasks = [];
|
|
235
|
+
const video = input.video();
|
|
236
|
+
if (video) {
|
|
237
|
+
const decoder = await import_api.Decoder.create(video);
|
|
238
|
+
const scale = variant.height ? `scale=-2:${variant.height}:force_original_aspect_ratio=decrease,format=yuv420p` : "format=yuv420p";
|
|
239
|
+
const filter = import_api.FilterAPI.create(scale);
|
|
240
|
+
const encoderOptions = {
|
|
241
|
+
preset: options.preset,
|
|
242
|
+
crf: String(options.crf)
|
|
243
|
+
};
|
|
244
|
+
const encoderBase = {
|
|
245
|
+
options: encoderOptions
|
|
246
|
+
};
|
|
247
|
+
if (variant.bitrate !== void 0) {
|
|
248
|
+
const bitrate = bitrateToNumber(variant.bitrate);
|
|
249
|
+
encoderOptions.maxrate = bitrate;
|
|
250
|
+
encoderOptions.bufsize = Math.round(bitrate * 1.5);
|
|
251
|
+
encoderBase.bitrate = bitrate;
|
|
252
|
+
}
|
|
253
|
+
const encoder = await import_api.Encoder.create(
|
|
254
|
+
import_constants.FF_ENCODER_LIBX264,
|
|
255
|
+
encoderBase
|
|
256
|
+
);
|
|
257
|
+
const index = output.addStream(encoder, { inputStream: video });
|
|
258
|
+
tasks.push(
|
|
259
|
+
(async () => {
|
|
260
|
+
try {
|
|
261
|
+
const frames = decoder.frames(input.packets(video.index));
|
|
262
|
+
const scaled = filter.frames(frames);
|
|
263
|
+
for await (const packet of encoder.packets(scaled)) {
|
|
264
|
+
if (packet === null) {
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
await output.writePacket(packet, index);
|
|
268
|
+
packet.free();
|
|
269
|
+
}
|
|
270
|
+
} finally {
|
|
271
|
+
filter.close();
|
|
272
|
+
decoder.close();
|
|
273
|
+
encoder.close();
|
|
274
|
+
}
|
|
275
|
+
})()
|
|
202
276
|
);
|
|
203
277
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
278
|
+
const audio = input.audio();
|
|
279
|
+
if (audio) {
|
|
280
|
+
const decoder = await import_api.Decoder.create(audio);
|
|
281
|
+
const encoder = await import_api.Encoder.create(import_constants.FF_ENCODER_AAC, {
|
|
282
|
+
decoder,
|
|
283
|
+
bitrate: 128e3
|
|
284
|
+
});
|
|
285
|
+
const index = output.addStream(encoder, { inputStream: audio });
|
|
286
|
+
tasks.push(
|
|
287
|
+
(async () => {
|
|
288
|
+
try {
|
|
289
|
+
const frames = decoder.frames(input.packets(audio.index));
|
|
290
|
+
for await (const packet of encoder.packets(frames)) {
|
|
291
|
+
if (packet === null) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
await output.writePacket(packet, index);
|
|
295
|
+
packet.free();
|
|
296
|
+
}
|
|
297
|
+
} finally {
|
|
298
|
+
decoder.close();
|
|
299
|
+
encoder.close();
|
|
300
|
+
}
|
|
301
|
+
})()
|
|
213
302
|
);
|
|
214
303
|
}
|
|
304
|
+
await Promise.all(tasks);
|
|
305
|
+
await output.close();
|
|
306
|
+
await relativizePlaylist(playlist, (0, import_node_path2.dirname)(playlist));
|
|
307
|
+
} catch (_) {
|
|
308
|
+
var _error = _, _hasError = true;
|
|
309
|
+
} finally {
|
|
310
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
311
|
+
_promise && await _promise;
|
|
215
312
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
(
|
|
225
|
-
);
|
|
226
|
-
if (options.segmentType === "fmp4") {
|
|
227
|
-
args.push(
|
|
228
|
-
"-hls_segment_type",
|
|
229
|
-
"fmp4",
|
|
230
|
-
"-hls_fmp4_init_filename",
|
|
231
|
-
"init.mp4"
|
|
232
|
-
);
|
|
313
|
+
}
|
|
314
|
+
async function encodeVariant(source, outputDirectory, options, variant = {}) {
|
|
315
|
+
const hls = hlsMuxerOptions(outputDirectory, options);
|
|
316
|
+
const playlist = (0, import_node_path2.join)(outputDirectory, PLAYLIST);
|
|
317
|
+
await (0, import_promises.mkdir)(outputDirectory, { recursive: true });
|
|
318
|
+
if (options.mode === "single") {
|
|
319
|
+
await remuxToHls(source, playlist, hls);
|
|
320
|
+
} else {
|
|
321
|
+
await transcodeToHls(source, playlist, hls, options, variant);
|
|
233
322
|
}
|
|
234
|
-
args.push(playlist);
|
|
235
|
-
await (0, import_promises.mkdir)(outputDirectory, {
|
|
236
|
-
recursive: true
|
|
237
|
-
});
|
|
238
|
-
await runFfmpeg(options.ffmpegPath, args);
|
|
239
323
|
}
|
|
240
324
|
async function createEncodeDirectory() {
|
|
241
325
|
return (0, import_promises.mkdtemp)((0, import_node_path2.join)((0, import_node_os.tmpdir)(), "vite-plugin-hls-"));
|
|
@@ -275,9 +359,10 @@ async function collectFiles(directory) {
|
|
|
275
359
|
}
|
|
276
360
|
|
|
277
361
|
// src/cache.ts
|
|
362
|
+
var CACHE_VERSION = "2";
|
|
278
363
|
async function getCacheKey(source, options) {
|
|
279
364
|
const sourceData = await (0, import_promises3.readFile)(source);
|
|
280
|
-
return (0, import_node_crypto.createHash)("sha256").update(sourceData).update(JSON.stringify(options)).digest("hex");
|
|
365
|
+
return (0, import_node_crypto.createHash)("sha256").update(CACHE_VERSION).update(sourceData).update(JSON.stringify(options)).digest("hex");
|
|
281
366
|
}
|
|
282
367
|
async function readCache(cacheRoot, key) {
|
|
283
368
|
const directory = (0, import_node_path4.join)(cacheRoot, "hls", key);
|
|
@@ -416,9 +501,9 @@ function emitHls(context, result, options) {
|
|
|
416
501
|
});
|
|
417
502
|
}
|
|
418
503
|
}
|
|
419
|
-
function publicManifestExpression(result, options) {
|
|
504
|
+
function publicManifestExpression(result, options, base) {
|
|
420
505
|
const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;
|
|
421
|
-
return
|
|
506
|
+
return JSON.stringify(`${base}${path}`);
|
|
422
507
|
}
|
|
423
508
|
|
|
424
509
|
// src/plugin.ts
|
|
@@ -460,7 +545,7 @@ function hlsVideos(input) {
|
|
|
460
545
|
if (!loadOptions?.ssr) {
|
|
461
546
|
emitHls(this, result, options);
|
|
462
547
|
}
|
|
463
|
-
const manifest = publicManifestExpression(result, options);
|
|
548
|
+
const manifest = publicManifestExpression(result, options, config.base);
|
|
464
549
|
return `
|
|
465
550
|
export default ${manifest};
|
|
466
551
|
`;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/options.ts","../src/resolve.ts","../src/hls.ts","../src/encoder.ts","../src/bitrate.ts","../src/cache.ts","../src/fs.ts","../src/playlist.ts","../src/assets.ts","../src/plugin.ts"],"sourcesContent":["import { hlsVideos } from \"./plugin\";\n\nexport { hlsVideos };\nexport default hlsVideos;\n\nexport type { HlsPluginOptions, HlsVariant } from \"./types\";\n","import ffmpegStatic from \"ffmpeg-static\";\n\nimport type { HlsPluginOptions, ResolvedHlsOptions } from \"./types\";\n\nexport function resolveOptions(\n input: HlsPluginOptions = {},\n): ResolvedHlsOptions {\n const common = {\n ffmpegPath: input.ffmpegPath ?? ffmpegStatic ?? \"ffmpeg\",\n\n segmentDuration: input.segmentDuration ?? 4,\n\n segmentType: input.segmentType ?? \"fmp4\",\n\n outputDir: input.outputDir ?? \"assets/hls\",\n\n preset: input.preset ?? \"medium\",\n\n crf: input.crf ?? 23,\n };\n\n if (input.mode === \"adaptive\") {\n if (!input.variants || input.variants.length === 0) {\n throw new Error(\n \"[vite-plugin-hls] Adaptive mode requires at least one variant.\",\n );\n }\n\n return {\n ...common,\n mode: \"adaptive\",\n variants: input.variants,\n };\n }\n\n return {\n ...common,\n mode: \"single\",\n };\n}\n","import { extname } from \"node:path\";\n\nimport type { PluginContext } from \"rolldown\";\n\nconst VIDEO_EXTENSIONS = new Set([\n \".mp4\",\n \".mov\",\n \".m4v\",\n \".webm\",\n \".mkv\",\n \".avi\",\n]);\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function isVideoSource(source: string): boolean {\n const pathname = source.split(\"?\")[0] ?? source;\n\n return VIDEO_EXTENSIONS.has(extname(pathname).toLowerCase());\n}\n\nexport function virtualIdForSource(source: string): string {\n return `${VIRTUAL_PREFIX}${encodeURIComponent(source)}`;\n}\n\nexport function sourceFromVirtualId(id: string): string {\n return decodeURIComponent(id.slice(VIRTUAL_PREFIX.length));\n}\n\nexport async function resolveVideoSource(\n source: string,\n importer: string,\n context: PluginContext,\n): Promise<string | undefined> {\n if (!isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await context.resolve(source, importer, {\n skipSelf: true,\n });\n\n if (!resolved || resolved.external) {\n return undefined;\n }\n\n /*\n * Queries such as ?url are irrelevant here.\n * FFmpeg needs the physical file.\n */\n return resolved.id.split(\"?\", 1)[0];\n}\n","import { basename, extname, join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport {\n encodeVariant,\n createEncodeDirectory,\n removeEncodeDirectory,\n} from \"./encoder\";\nimport { readCache, getCacheKey, writeCache } from \"./cache\";\nimport { collectFiles } from \"./fs\";\nimport { slugify, createMasterPlaylist } from \"./playlist\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport async function generateHls(\n source: string,\n cacheRoot: string,\n options: ResolvedHlsOptions,\n): Promise<CachedHls> {\n const key = await getCacheKey(source, options);\n\n const cached = await readCache(cacheRoot, key);\n\n if (cached) {\n console.log(`[vite-plugin-hls] cache hit ${source}`);\n\n return cached;\n }\n\n console.log(`[vite-plugin-hls] encoding ${source}`);\n\n const temporaryDirectory = await createEncodeDirectory();\n\n try {\n const videoName = slugify(basename(source, extname(source)));\n\n const directoryName = `${videoName}-${key.slice(0, 12)}`;\n\n let manifest: string;\n\n if (options.mode === \"single\") {\n await encodeVariant(source, temporaryDirectory, options);\n\n manifest = \"index.m3u8\";\n } else {\n for (const variant of options.variants) {\n await encodeVariant(\n source,\n join(temporaryDirectory, `${variant.height}p`),\n options,\n {\n height: variant.height,\n bitrate: variant.bitrate,\n },\n );\n }\n\n const master = createMasterPlaylist(options.variants);\n\n await writeFile(join(temporaryDirectory, \"master.m3u8\"), master, \"utf8\");\n\n manifest = \"master.m3u8\";\n }\n\n const result: CachedHls = {\n directoryName,\n manifest,\n files: await collectFiles(temporaryDirectory),\n };\n\n await writeCache(cacheRoot, key, result);\n\n console.log(`[vite-plugin-hls] generated ${directoryName}/${manifest}`);\n\n return result;\n } finally {\n await removeEncodeDirectory(temporaryDirectory);\n }\n}\n","import { mkdir, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { ResolvedHlsOptions } from \"./types\";\n\ninterface EncodeVariantOptions {\n height?: number;\n bitrate?: string;\n}\n\nfunction runFfmpeg(executable: string, args: string[]): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(executable, args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stderr = \"\";\n\n child.stderr.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (error) => {\n if (\"code\" in error && error.code === \"ENOENT\") {\n reject(\n new Error(\n [\n \"[vite-plugin-hls] ffmpeg was not found.\",\n `Executable: ${executable}`,\n \"Set ffmpegPath or install ffmpeg.\",\n ].join(\"\\n\"),\n ),\n );\n return;\n }\n\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolvePromise();\n return;\n }\n\n reject(\n new Error(\n [\n `[vite-plugin-hls] ffmpeg failed with exit code ${code ?? \"unknown\"}.`,\n stderr.trim(),\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n ),\n );\n });\n });\n}\n\nexport async function encodeVariant(\n source: string,\n outputDirectory: string,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions = {},\n): Promise<void> {\n const extension = options.segmentType === \"fmp4\" ? \"m4s\" : \"ts\";\n\n const playlist = join(outputDirectory, \"index.m3u8\");\n\n const args: string[] = [\n \"-y\",\n \"-i\",\n source,\n\n \"-map\",\n \"0:v:0\",\n \"-map\",\n \"0:a:0?\",\n ];\n\n /*\n * SINGLE MODE:\n *\n * Do not encode the streams again.\n * FFmpeg only remuxes/segments them into HLS.\n */\n if (options.mode === \"single\") {\n args.push(\n \"-c:v\",\n \"copy\",\n\n \"-c:a\",\n \"copy\",\n );\n } else {\n /*\n * ADAPTIVE MODE:\n *\n * Each rendition needs to be encoded because\n * resolution and bitrate are changed.\n */\n args.push(\n \"-c:v\",\n \"libx264\",\n\n \"-preset\",\n options.preset,\n\n \"-crf\",\n String(options.crf),\n\n \"-pix_fmt\",\n \"yuv420p\",\n\n \"-c:a\",\n \"aac\",\n\n \"-b:a\",\n \"128k\",\n );\n\n if (variant.height !== undefined) {\n args.push(\n \"-vf\",\n `scale=-2:${variant.height}:force_original_aspect_ratio=decrease`,\n );\n }\n\n if (variant.bitrate !== undefined) {\n const bitrate = bitrateToNumber(variant.bitrate);\n\n args.push(\n \"-b:v\",\n variant.bitrate,\n\n \"-maxrate\",\n variant.bitrate,\n\n \"-bufsize\",\n String(Math.round(bitrate * 1.5)),\n );\n }\n }\n\n args.push(\n \"-f\",\n \"hls\",\n\n \"-hls_time\",\n String(options.segmentDuration),\n\n \"-hls_playlist_type\",\n \"vod\",\n\n \"-hls_segment_filename\",\n join(outputDirectory, `segment-%05d.${extension}`),\n );\n\n if (options.segmentType === \"fmp4\") {\n args.push(\n \"-hls_segment_type\",\n \"fmp4\",\n\n \"-hls_fmp4_init_filename\",\n \"init.mp4\",\n );\n }\n\n args.push(playlist);\n\n /*\n * FFmpeg's HLS muxer does not create the output directory.\n * Ensure it exists so segments/playlist can be written.\n */\n await mkdir(outputDirectory, {\n recursive: true,\n });\n\n await runFfmpeg(options.ffmpegPath, args);\n}\n\nexport async function createEncodeDirectory(): Promise<string> {\n return mkdtemp(join(tmpdir(), \"vite-plugin-hls-\"));\n}\n\nexport async function removeEncodeDirectory(directory: string): Promise<void> {\n await rm(directory, {\n recursive: true,\n force: true,\n });\n}\n","/**\n * Converts a human-readable bitrate such as \"1200k\", \"2.5M\" or \"1G\"\n * into the equivalent number of bits per second.\n */\nexport function bitrateToNumber(bitrate: string): number {\n const match = /^([\\d.]+)\\s*([kKmMgG]?)$/.exec(bitrate.trim());\n\n if (!match) {\n throw new Error(`[vite-plugin-hls] Invalid bitrate: ${bitrate}`);\n }\n\n const amount = Number(match[1]);\n\n const suffix = (match[2] ?? \"\").toLowerCase();\n\n const multiplier =\n suffix === \"g\"\n ? 1_000_000_000\n : suffix === \"m\"\n ? 1_000_000\n : suffix === \"k\"\n ? 1_000\n : 1;\n\n return Math.round(amount * multiplier);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { collectFiles } from \"./fs\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\ninterface CacheManifest {\n directoryName: string;\n manifest: string;\n}\n\nexport async function getCacheKey(\n source: string,\n options: ResolvedHlsOptions,\n): Promise<string> {\n const sourceData = await readFile(source);\n\n return createHash(\"sha256\")\n .update(sourceData)\n .update(JSON.stringify(options))\n .digest(\"hex\");\n}\n\nexport async function readCache(\n cacheRoot: string,\n key: string,\n): Promise<CachedHls | undefined> {\n const directory = join(cacheRoot, \"hls\", key);\n\n try {\n await stat(directory);\n } catch {\n return undefined;\n }\n\n const metadata = JSON.parse(\n await readFile(join(directory, \"manifest.json\"), \"utf8\"),\n ) as CacheManifest;\n\n const files = await collectFiles(directory);\n\n // manifest.json is internal bookkeeping, never an emitted asset.\n files.delete(\"manifest.json\");\n\n return {\n directoryName: metadata.directoryName,\n\n manifest: metadata.manifest,\n\n files,\n };\n}\n\nexport async function writeCache(\n cacheRoot: string,\n key: string,\n result: CachedHls,\n): Promise<void> {\n const temporaryDirectory = join(cacheRoot, \"hls\", `${key}.tmp`);\n\n const finalDirectory = join(cacheRoot, \"hls\", key);\n\n await rm(temporaryDirectory, {\n recursive: true,\n force: true,\n });\n\n await mkdir(temporaryDirectory, {\n recursive: true,\n });\n\n for (const [filename, data] of result.files) {\n const destination = join(temporaryDirectory, filename);\n\n await mkdir(resolve(destination, \"..\"), {\n recursive: true,\n });\n\n await writeFile(destination, data);\n }\n\n await writeFile(\n join(temporaryDirectory, \"manifest.json\"),\n JSON.stringify({\n directoryName: result.directoryName,\n manifest: result.manifest,\n } satisfies CacheManifest),\n \"utf8\",\n );\n\n await rm(finalDirectory, {\n recursive: true,\n force: true,\n });\n\n await rename(temporaryDirectory, finalDirectory);\n}\n","import { readFile, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\n/**\n * Recursively collects every file under `directory` into a map of\n * directory-relative POSIX paths to their buffer contents.\n */\nexport async function collectFiles(\n directory: string,\n): Promise<Map<string, Buffer>> {\n const result = new Map<string, Buffer>();\n\n async function visit(current: string): Promise<void> {\n for (const entry of await readdir(current, { withFileTypes: true })) {\n const filename = join(current, entry.name);\n\n if (entry.isDirectory()) {\n await visit(filename);\n continue;\n }\n\n result.set(\n relative(directory, filename).replaceAll(\"\\\\\", \"/\"),\n await readFile(filename),\n );\n }\n }\n\n await visit(directory);\n\n return result;\n}\n","import { extname } from \"node:path\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { HlsVariant } from \"./types\";\n\n/** Turns a filename into a filesystem-safe slug used for the output folder. */\nexport function slugify(filename: string): string {\n return (\n filename\n .replace(extname(filename), \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase() || \"video\"\n );\n}\n\n/** Builds the adaptive master playlist pointing at each rendition. */\nexport function createMasterPlaylist(variants: readonly HlsVariant[]): string {\n const lines = [\"#EXTM3U\", \"#EXT-X-VERSION:7\"];\n\n for (const variant of variants) {\n // BANDWIDTH is an estimate derived from the configured bitrate.\n const bandwidth = Math.round(bitrateToNumber(variant.bitrate) * 1.15);\n\n lines.push(\n `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}`,\n `${variant.height}p/index.m3u8`,\n );\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { PluginContext } from \"rolldown\";\n\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport function emitHls(\n context: PluginContext,\n result: CachedHls,\n options: ResolvedHlsOptions,\n): void {\n const prefix = `${options.outputDir}/${result.directoryName}`;\n\n const manifestSource = result.files.get(result.manifest);\n\n if (!manifestSource) {\n throw new Error(`[vite-plugin-hls] Missing manifest ${result.manifest}`);\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${result.manifest}`,\n source: manifestSource,\n });\n\n for (const [filename, data] of result.files) {\n if (filename === result.manifest) {\n continue;\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${filename}`,\n source: data,\n });\n }\n}\n\nexport function publicManifestExpression(\n result: CachedHls,\n options: ResolvedHlsOptions,\n): string {\n const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;\n\n /*\n * BASE_URL is replaced by Vite in both the client\n * and SSR builds.\n *\n * Examples:\n *\n * \"/\" -> /assets/hls/...\n * \"/notes/\" -> /notes/assets/hls/...\n * \"./\" -> ./assets/hls/...\n */\n return `import.meta.env.BASE_URL + ${JSON.stringify(path)}`;\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\n\nimport { resolveOptions } from \"./options\";\nimport {\n isVideoSource,\n resolveVideoSource,\n sourceFromVirtualId,\n virtualIdForSource,\n} from \"./resolve\";\nimport { generateHls } from \"./hls\";\nimport { emitHls, publicManifestExpression } from \"./assets\";\nimport type { HlsPluginOptions } from \"./types\";\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function hlsVideos(input?: HlsPluginOptions): Plugin {\n const options = resolveOptions(input);\n\n let config: ResolvedConfig;\n\n const resolvedSources = new Map<string, string>();\n\n /*\n * This only caches inside the current Vite build.\n *\n * The persistent cache in cache.ts handles the\n * client/server build boundary.\n */\n const generated = new Map<string, Awaited<ReturnType<typeof generateHls>>>();\n\n return {\n name: \"vite-plugin-hls\",\n\n apply: \"build\",\n\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved;\n },\n\n async resolveId(source, importer) {\n if (!importer || !isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await resolveVideoSource(source, importer, this);\n\n if (!resolved) {\n return undefined;\n }\n\n const id = virtualIdForSource(resolved);\n\n resolvedSources.set(id, resolved);\n\n return id;\n },\n\n async load(id, loadOptions) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return undefined;\n }\n\n const source = resolvedSources.get(id) ?? sourceFromVirtualId(id);\n\n let result = generated.get(source);\n\n if (!result) {\n result = await generateHls(source, config.cacheDir, options);\n\n generated.set(source, result);\n }\n\n /*\n * Important:\n *\n * The SSR build must NOT use Vite's emitted-file\n * URL because that resolves to a file:// URL.\n *\n * We only emit the actual files during the client\n * build. SSR receives the public URL directly.\n */\n if (!loadOptions?.ssr) {\n emitHls(this, result, options);\n }\n\n const manifest = publicManifestExpression(result, options);\n\n return `\n export default ${manifest};\n `;\n },\n };\n}\n"],"mappings":"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","names":["ffmpegStatic","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_node_path","VIRTUAL_PREFIX"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/options.ts","../src/resolve.ts","../src/hls.ts","../src/encoder.ts","../src/bitrate.ts","../src/cache.ts","../src/fs.ts","../src/playlist.ts","../src/assets.ts","../src/plugin.ts"],"sourcesContent":["import { hlsVideos } from \"./plugin\";\n\nexport { hlsVideos };\nexport default hlsVideos;\n\nexport type { HlsPluginOptions, HlsVariant } from \"./types\";\n","import type { HlsPluginOptions, ResolvedHlsOptions } from \"./types\";\n\nexport function resolveOptions(\n input: HlsPluginOptions = {},\n): ResolvedHlsOptions {\n const common = {\n segmentDuration: input.segmentDuration ?? 4,\n\n segmentType: input.segmentType ?? \"fmp4\",\n\n outputDir: input.outputDir ?? \"assets/hls\",\n\n preset: input.preset ?? \"medium\",\n\n crf: input.crf ?? 23,\n };\n\n if (input.mode === \"adaptive\") {\n if (!input.variants || input.variants.length === 0) {\n throw new Error(\n \"[vite-plugin-hls] Adaptive mode requires at least one variant.\",\n );\n }\n\n return {\n ...common,\n mode: \"adaptive\",\n variants: input.variants,\n };\n }\n\n return {\n ...common,\n mode: \"single\",\n };\n}\n","import { extname } from \"node:path\";\n\nimport type { PluginContext } from \"rolldown\";\n\nconst VIDEO_EXTENSIONS = new Set([\n \".mp4\",\n \".mov\",\n \".m4v\",\n \".webm\",\n \".mkv\",\n \".avi\",\n]);\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function isVideoSource(source: string): boolean {\n const pathname = source.split(\"?\")[0] ?? source;\n\n return VIDEO_EXTENSIONS.has(extname(pathname).toLowerCase());\n}\n\nexport function virtualIdForSource(source: string): string {\n return `${VIRTUAL_PREFIX}${encodeURIComponent(source)}`;\n}\n\nexport function sourceFromVirtualId(id: string): string {\n return decodeURIComponent(id.slice(VIRTUAL_PREFIX.length));\n}\n\nexport async function resolveVideoSource(\n source: string,\n importer: string,\n context: PluginContext,\n): Promise<string | undefined> {\n if (!isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await context.resolve(source, importer, {\n skipSelf: true,\n });\n\n if (!resolved || resolved.external) {\n return undefined;\n }\n\n /*\n * Queries such as ?url are irrelevant here.\n * FFmpeg needs the physical file.\n */\n return resolved.id.split(\"?\", 1)[0];\n}\n","import { basename, extname, join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport {\n encodeVariant,\n createEncodeDirectory,\n removeEncodeDirectory,\n} from \"./encoder\";\nimport { readCache, getCacheKey, writeCache } from \"./cache\";\nimport { collectFiles } from \"./fs\";\nimport { slugify, createMasterPlaylist } from \"./playlist\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport async function generateHls(\n source: string,\n cacheRoot: string,\n options: ResolvedHlsOptions,\n): Promise<CachedHls> {\n const key = await getCacheKey(source, options);\n\n const cached = await readCache(cacheRoot, key);\n\n if (cached) {\n console.log(`[vite-plugin-hls] cache hit ${source}`);\n\n return cached;\n }\n\n console.log(`[vite-plugin-hls] encoding ${source}`);\n\n const temporaryDirectory = await createEncodeDirectory();\n\n try {\n const videoName = slugify(basename(source, extname(source)));\n\n const directoryName = `${videoName}-${key.slice(0, 12)}`;\n\n let manifest: string;\n\n if (options.mode === \"single\") {\n await encodeVariant(source, temporaryDirectory, options);\n\n manifest = \"index.m3u8\";\n } else {\n for (const variant of options.variants) {\n await encodeVariant(\n source,\n join(temporaryDirectory, `${variant.height}p`),\n options,\n {\n height: variant.height,\n bitrate: variant.bitrate,\n },\n );\n }\n\n const master = createMasterPlaylist(options.variants);\n\n await writeFile(join(temporaryDirectory, \"master.m3u8\"), master, \"utf8\");\n\n manifest = \"master.m3u8\";\n }\n\n const result: CachedHls = {\n directoryName,\n manifest,\n files: await collectFiles(temporaryDirectory),\n };\n\n await writeCache(cacheRoot, key, result);\n\n console.log(`[vite-plugin-hls] generated ${directoryName}/${manifest}`);\n\n return result;\n } finally {\n await removeEncodeDirectory(temporaryDirectory);\n }\n}\n","import { mkdir, mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\nimport { Decoder, Demuxer, Encoder, FilterAPI, Muxer } from \"node-av/api\";\nimport { FF_ENCODER_AAC, FF_ENCODER_LIBX264 } from \"node-av/constants\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { ResolvedHlsOptions } from \"./types\";\n\ninterface EncodeVariantOptions {\n height?: number;\n bitrate?: string;\n}\n\nconst PLAYLIST = \"index.m3u8\";\n\ntype HlsOptions = {\n hls_time: string;\n hls_playlist_type: \"vod\";\n hls_segment_filename: string;\n hls_segment_type?: \"mpegts\" | \"fmp4\";\n hls_fmp4_init_filename?: string;\n};\n\nfunction hlsMuxerOptions(\n outputDirectory: string,\n options: ResolvedHlsOptions,\n): HlsOptions {\n const extension = options.segmentType === \"fmp4\" ? \"m4s\" : \"ts\";\n\n const hls: HlsOptions = {\n hls_time: String(options.segmentDuration),\n hls_playlist_type: \"vod\",\n hls_segment_filename: join(outputDirectory, `segment-%05d.${extension}`),\n };\n\n if (options.segmentType === \"fmp4\") {\n hls.hls_segment_type = \"fmp4\";\n\n /*\n * The init filename must be absolute so the file lands next to the\n * playlist. A relative name would be resolved against the process CWD\n * (the project root) and leak there. The playlist is relativized after\n * encoding (see relativizePlaylist).\n */\n hls.hls_fmp4_init_filename = join(outputDirectory, \"init.mp4\");\n }\n\n return hls;\n}\n\n/*\n * ffmpeg references an absolute init path literally in EXT-X-MAP (segment\n * URIs are already emitted as basenames). Rewrite it to a path relative to\n * the output directory so the playlist stays portable.\n */\nasync function relativizePlaylist(\n playlist: string,\n outputDirectory: string,\n): Promise<void> {\n let text: string;\n try {\n text = await readFile(playlist, \"utf8\");\n } catch {\n return; // nothing written (e.g. mocked in tests)\n }\n\n const prefix = outputDirectory.replace(/[\\\\/]+$/, \"\");\n\n text = text\n .split(prefix + \"\\\\\")\n .join(\"\")\n .split(prefix + \"/\")\n .join(\"\");\n\n await writeFile(playlist, text, \"utf8\");\n}\n\n/*\n * SINGLE MODE (remux): copy the streams, only repackage into HLS.\n */\nasync function remuxToHls(\n source: string,\n playlist: string,\n hls: HlsOptions,\n): Promise<void> {\n await using input = await Demuxer.open(source);\n await using output = await Muxer.open(playlist, {\n format: \"hls\",\n input,\n options: hls,\n });\n\n const routed = new Map<number, number>();\n\n const video = input.video();\n if (video) {\n routed.set(video.index, output.addStream(video));\n }\n\n const audio = input.audio();\n if (audio) {\n routed.set(audio.index, output.addStream(audio));\n }\n\n for await (const packet of input.packets()) {\n if (packet === null) {\n break;\n }\n\n const index = routed.get(packet.streamIndex);\n\n if (index !== undefined) {\n await output.writePacket(packet, index);\n }\n\n packet.free();\n }\n\n await output.close();\n\n await relativizePlaylist(playlist, dirname(playlist));\n}\n\n/*\n * ADAPTIVE MODE (transcode): re-encode each rendition's video (scaled,\n * libx264) and audio (aac) into its HLS output.\n */\nasync function transcodeToHls(\n source: string,\n playlist: string,\n hls: HlsOptions,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions,\n): Promise<void> {\n if (options.mode !== \"adaptive\") {\n throw new Error(\n \"[vite-plugin-hls] internal: transcode requires adaptive mode\",\n );\n }\n\n await using input = await Demuxer.open(source);\n await using output = await Muxer.open(playlist, {\n format: \"hls\",\n input,\n options: hls,\n });\n\n const tasks: Promise<void>[] = [];\n\n const video = input.video();\n if (video) {\n const decoder = await Decoder.create(video);\n\n const scale = variant.height\n ? `scale=-2:${variant.height}:force_original_aspect_ratio=decrease,format=yuv420p`\n : \"format=yuv420p\";\n\n const filter = FilterAPI.create(scale);\n\n const encoderOptions: Record<string, string | number> = {\n preset: options.preset,\n crf: String(options.crf),\n };\n\n const encoderBase: Record<string, unknown> = {\n options: encoderOptions,\n };\n\n if (variant.bitrate !== undefined) {\n const bitrate = bitrateToNumber(variant.bitrate);\n\n encoderOptions.maxrate = bitrate;\n encoderOptions.bufsize = Math.round(bitrate * 1.5);\n encoderBase.bitrate = bitrate;\n }\n\n const encoder = await Encoder.create(\n FF_ENCODER_LIBX264,\n encoderBase as Parameters<typeof Encoder.create>[1],\n );\n\n const index = output.addStream(encoder, { inputStream: video });\n\n tasks.push(\n (async () => {\n try {\n const frames = decoder.frames(input.packets(video.index));\n const scaled = filter.frames(frames);\n\n for await (const packet of encoder.packets(scaled)) {\n if (packet === null) {\n break;\n }\n await output.writePacket(packet, index);\n packet.free();\n }\n } finally {\n filter.close();\n decoder.close();\n encoder.close();\n }\n })(),\n );\n }\n\n const audio = input.audio();\n if (audio) {\n const decoder = await Decoder.create(audio);\n\n const encoder = await Encoder.create(FF_ENCODER_AAC, {\n decoder,\n bitrate: 128_000,\n });\n\n const index = output.addStream(encoder, { inputStream: audio });\n\n tasks.push(\n (async () => {\n try {\n const frames = decoder.frames(input.packets(audio.index));\n\n for await (const packet of encoder.packets(frames)) {\n if (packet === null) {\n break;\n }\n await output.writePacket(packet, index);\n packet.free();\n }\n } finally {\n decoder.close();\n encoder.close();\n }\n })(),\n );\n }\n\n await Promise.all(tasks);\n await output.close();\n\n await relativizePlaylist(playlist, dirname(playlist));\n}\n\nexport async function encodeVariant(\n source: string,\n outputDirectory: string,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions = {},\n): Promise<void> {\n const hls = hlsMuxerOptions(outputDirectory, options);\n const playlist = join(outputDirectory, PLAYLIST);\n\n /*\n * FFmpeg's HLS muxer does not create the output directory.\n * Ensure it exists so segments/playlist can be written.\n */\n await mkdir(outputDirectory, { recursive: true });\n\n if (options.mode === \"single\") {\n await remuxToHls(source, playlist, hls);\n } else {\n await transcodeToHls(source, playlist, hls, options, variant);\n }\n}\n\nexport async function createEncodeDirectory(): Promise<string> {\n return mkdtemp(join(tmpdir(), \"vite-plugin-hls-\"));\n}\n\nexport async function removeEncodeDirectory(directory: string): Promise<void> {\n await rm(directory, {\n recursive: true,\n force: true,\n });\n}\n","/**\n * Converts a human-readable bitrate such as \"1200k\", \"2.5M\" or \"1G\"\n * into the equivalent number of bits per second.\n */\nexport function bitrateToNumber(bitrate: string): number {\n const match = /^([\\d.]+)\\s*([kKmMgG]?)$/.exec(bitrate.trim());\n\n if (!match) {\n throw new Error(`[vite-plugin-hls] Invalid bitrate: ${bitrate}`);\n }\n\n const amount = Number(match[1]);\n\n const suffix = (match[2] ?? \"\").toLowerCase();\n\n const multiplier =\n suffix === \"g\"\n ? 1_000_000_000\n : suffix === \"m\"\n ? 1_000_000\n : suffix === \"k\"\n ? 1_000\n : 1;\n\n return Math.round(amount * multiplier);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { collectFiles } from \"./fs\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\ninterface CacheManifest {\n directoryName: string;\n manifest: string;\n}\n\n/*\n * Bump whenever the encoder's output layout changes (e.g. a bug fix alters\n * which files are produced). Old cache entries are keyed only on source +\n * options and would otherwise be served forever, so the version is folded\n * into the key to invalidate them.\n */\nconst CACHE_VERSION = \"2\";\n\nexport async function getCacheKey(\n source: string,\n options: ResolvedHlsOptions,\n): Promise<string> {\n const sourceData = await readFile(source);\n\n return createHash(\"sha256\")\n .update(CACHE_VERSION)\n .update(sourceData)\n .update(JSON.stringify(options))\n .digest(\"hex\");\n}\n\nexport async function readCache(\n cacheRoot: string,\n key: string,\n): Promise<CachedHls | undefined> {\n const directory = join(cacheRoot, \"hls\", key);\n\n try {\n await stat(directory);\n } catch {\n return undefined;\n }\n\n const metadata = JSON.parse(\n await readFile(join(directory, \"manifest.json\"), \"utf8\"),\n ) as CacheManifest;\n\n const files = await collectFiles(directory);\n\n // manifest.json is internal bookkeeping, never an emitted asset.\n files.delete(\"manifest.json\");\n\n return {\n directoryName: metadata.directoryName,\n\n manifest: metadata.manifest,\n\n files,\n };\n}\n\nexport async function writeCache(\n cacheRoot: string,\n key: string,\n result: CachedHls,\n): Promise<void> {\n const temporaryDirectory = join(cacheRoot, \"hls\", `${key}.tmp`);\n\n const finalDirectory = join(cacheRoot, \"hls\", key);\n\n await rm(temporaryDirectory, {\n recursive: true,\n force: true,\n });\n\n await mkdir(temporaryDirectory, {\n recursive: true,\n });\n\n for (const [filename, data] of result.files) {\n const destination = join(temporaryDirectory, filename);\n\n await mkdir(resolve(destination, \"..\"), {\n recursive: true,\n });\n\n await writeFile(destination, data);\n }\n\n await writeFile(\n join(temporaryDirectory, \"manifest.json\"),\n JSON.stringify({\n directoryName: result.directoryName,\n manifest: result.manifest,\n } satisfies CacheManifest),\n \"utf8\",\n );\n\n await rm(finalDirectory, {\n recursive: true,\n force: true,\n });\n\n await rename(temporaryDirectory, finalDirectory);\n}\n","import { readFile, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\n/**\n * Recursively collects every file under `directory` into a map of\n * directory-relative POSIX paths to their buffer contents.\n */\nexport async function collectFiles(\n directory: string,\n): Promise<Map<string, Buffer>> {\n const result = new Map<string, Buffer>();\n\n async function visit(current: string): Promise<void> {\n for (const entry of await readdir(current, { withFileTypes: true })) {\n const filename = join(current, entry.name);\n\n if (entry.isDirectory()) {\n await visit(filename);\n continue;\n }\n\n result.set(\n relative(directory, filename).replaceAll(\"\\\\\", \"/\"),\n await readFile(filename),\n );\n }\n }\n\n await visit(directory);\n\n return result;\n}\n","import { extname } from \"node:path\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { HlsVariant } from \"./types\";\n\n/** Turns a filename into a filesystem-safe slug used for the output folder. */\nexport function slugify(filename: string): string {\n return (\n filename\n .replace(extname(filename), \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase() || \"video\"\n );\n}\n\n/** Builds the adaptive master playlist pointing at each rendition. */\nexport function createMasterPlaylist(variants: readonly HlsVariant[]): string {\n const lines = [\"#EXTM3U\", \"#EXT-X-VERSION:7\"];\n\n for (const variant of variants) {\n // BANDWIDTH is an estimate derived from the configured bitrate.\n const bandwidth = Math.round(bitrateToNumber(variant.bitrate) * 1.15);\n\n lines.push(\n `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}`,\n `${variant.height}p/index.m3u8`,\n );\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { PluginContext } from \"rolldown\";\n\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport function emitHls(\n context: PluginContext,\n result: CachedHls,\n options: ResolvedHlsOptions,\n): void {\n const prefix = `${options.outputDir}/${result.directoryName}`;\n\n const manifestSource = result.files.get(result.manifest);\n\n if (!manifestSource) {\n throw new Error(`[vite-plugin-hls] Missing manifest ${result.manifest}`);\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${result.manifest}`,\n source: manifestSource,\n });\n\n for (const [filename, data] of result.files) {\n if (filename === result.manifest) {\n continue;\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${filename}`,\n source: data,\n });\n }\n}\n\nexport function publicManifestExpression(\n result: CachedHls,\n options: ResolvedHlsOptions,\n base: string,\n): string {\n const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;\n\n /*\n * Bake Vite's resolved base straight into the module. Vite does not always\n * statically replace import.meta.env.BASE_URL (e.g. in SSR bundles), where\n * import.meta.env is undefined at runtime and crashes. Emitting the base as\n * a plain string is equivalent and works for client and SSR alike.\n *\n * Examples (base -> export):\n *\n * \"/\" -> \"/assets/hls/...\"\n * \"/notes/\" -> \"/notes/assets/hls/...\"\n */\n return JSON.stringify(`${base}${path}`);\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\n\nimport { resolveOptions } from \"./options\";\nimport {\n isVideoSource,\n resolveVideoSource,\n sourceFromVirtualId,\n virtualIdForSource,\n} from \"./resolve\";\nimport { generateHls } from \"./hls\";\nimport { emitHls, publicManifestExpression } from \"./assets\";\nimport type { HlsPluginOptions } from \"./types\";\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function hlsVideos(input?: HlsPluginOptions): Plugin {\n const options = resolveOptions(input);\n\n let config: ResolvedConfig;\n\n const resolvedSources = new Map<string, string>();\n\n /*\n * This only caches inside the current Vite build.\n *\n * The persistent cache in cache.ts handles the\n * client/server build boundary.\n */\n const generated = new Map<string, Awaited<ReturnType<typeof generateHls>>>();\n\n return {\n name: \"vite-plugin-hls\",\n\n apply: \"build\",\n\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved;\n },\n\n async resolveId(source, importer) {\n if (!importer || !isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await resolveVideoSource(source, importer, this);\n\n if (!resolved) {\n return undefined;\n }\n\n const id = virtualIdForSource(resolved);\n\n resolvedSources.set(id, resolved);\n\n return id;\n },\n\n async load(id, loadOptions) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return undefined;\n }\n\n const source = resolvedSources.get(id) ?? sourceFromVirtualId(id);\n\n let result = generated.get(source);\n\n if (!result) {\n result = await generateHls(source, config.cacheDir, options);\n\n generated.set(source, result);\n }\n\n /*\n * Important:\n *\n * The SSR build must NOT use Vite's emitted-file\n * URL because that resolves to a file:// URL.\n *\n * We only emit the actual files during the client\n * build. SSR receives the public URL directly.\n */\n if (!loadOptions?.ssr) {\n emitHls(this, result, options);\n }\n\n const manifest = publicManifestExpression(result, options, config.base);\n\n return `\n export default ${manifest};\n `;\n },\n };\n}\n"],"mappings":"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","names":["import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_node_path","VIRTUAL_PREFIX"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -5,12 +5,6 @@ interface HlsVariant {
|
|
|
5
5
|
bitrate: string;
|
|
6
6
|
}
|
|
7
7
|
interface HlsCommonOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Explicit ffmpeg executable.
|
|
10
|
-
*
|
|
11
|
-
* Defaults to ffmpeg-static, then falls back to "ffmpeg".
|
|
12
|
-
*/
|
|
13
|
-
ffmpegPath?: string;
|
|
14
8
|
/**
|
|
15
9
|
* HLS segment duration in seconds.
|
|
16
10
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,6 @@ interface HlsVariant {
|
|
|
5
5
|
bitrate: string;
|
|
6
6
|
}
|
|
7
7
|
interface HlsCommonOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Explicit ffmpeg executable.
|
|
10
|
-
*
|
|
11
|
-
* Defaults to ffmpeg-static, then falls back to "ffmpeg".
|
|
12
|
-
*/
|
|
13
|
-
ffmpegPath?: string;
|
|
14
8
|
/**
|
|
15
9
|
* HLS segment duration in seconds.
|
|
16
10
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,52 @@
|
|
|
1
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __using = (stack, value, async) => {
|
|
6
|
+
if (value != null) {
|
|
7
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
8
|
+
var dispose, inner;
|
|
9
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
dispose = value[__knownSymbol("dispose")];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
15
|
+
if (inner) dispose = function() {
|
|
16
|
+
try {
|
|
17
|
+
inner.call(this);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return Promise.reject(e);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
stack.push([async, dispose, value]);
|
|
23
|
+
} else if (async) {
|
|
24
|
+
stack.push([async]);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __callDispose = (stack, error, hasError) => {
|
|
29
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
30
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
31
|
+
};
|
|
32
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
33
|
+
var next = (it) => {
|
|
34
|
+
while (it = stack.pop()) {
|
|
35
|
+
try {
|
|
36
|
+
var result = it[1] && it[1].call(it[2]);
|
|
37
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
fail(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (hasError) throw error;
|
|
43
|
+
};
|
|
44
|
+
return next();
|
|
45
|
+
};
|
|
46
|
+
|
|
1
47
|
// src/options.ts
|
|
2
|
-
import ffmpegStatic from "ffmpeg-static";
|
|
3
48
|
function resolveOptions(input = {}) {
|
|
4
49
|
const common = {
|
|
5
|
-
ffmpegPath: input.ffmpegPath ?? ffmpegStatic ?? "ffmpeg",
|
|
6
50
|
segmentDuration: input.segmentDuration ?? 4,
|
|
7
51
|
segmentType: input.segmentType ?? "fmp4",
|
|
8
52
|
outputDir: input.outputDir ?? "assets/hls",
|
|
@@ -63,13 +107,14 @@ async function resolveVideoSource(source, importer, context) {
|
|
|
63
107
|
|
|
64
108
|
// src/hls.ts
|
|
65
109
|
import { basename, extname as extname3, join as join4 } from "path";
|
|
66
|
-
import { writeFile as
|
|
110
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
67
111
|
|
|
68
112
|
// src/encoder.ts
|
|
69
|
-
import { mkdir, mkdtemp, rm } from "fs/promises";
|
|
70
|
-
import { join } from "path";
|
|
113
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises";
|
|
114
|
+
import { dirname, join } from "path";
|
|
71
115
|
import { tmpdir } from "os";
|
|
72
|
-
import {
|
|
116
|
+
import { Decoder, Demuxer, Encoder, FilterAPI, Muxer } from "node-av/api";
|
|
117
|
+
import { FF_ENCODER_AAC, FF_ENCODER_LIBX264 } from "node-av/constants";
|
|
73
118
|
|
|
74
119
|
// src/bitrate.ts
|
|
75
120
|
function bitrateToNumber(bitrate) {
|
|
@@ -84,121 +129,171 @@ function bitrateToNumber(bitrate) {
|
|
|
84
129
|
}
|
|
85
130
|
|
|
86
131
|
// src/encoder.ts
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
var PLAYLIST = "index.m3u8";
|
|
133
|
+
function hlsMuxerOptions(outputDirectory, options) {
|
|
134
|
+
const extension = options.segmentType === "fmp4" ? "m4s" : "ts";
|
|
135
|
+
const hls = {
|
|
136
|
+
hls_time: String(options.segmentDuration),
|
|
137
|
+
hls_playlist_type: "vod",
|
|
138
|
+
hls_segment_filename: join(outputDirectory, `segment-%05d.${extension}`)
|
|
139
|
+
};
|
|
140
|
+
if (options.segmentType === "fmp4") {
|
|
141
|
+
hls.hls_segment_type = "fmp4";
|
|
142
|
+
hls.hls_fmp4_init_filename = join(outputDirectory, "init.mp4");
|
|
143
|
+
}
|
|
144
|
+
return hls;
|
|
145
|
+
}
|
|
146
|
+
async function relativizePlaylist(playlist, outputDirectory) {
|
|
147
|
+
let text;
|
|
148
|
+
try {
|
|
149
|
+
text = await readFile(playlist, "utf8");
|
|
150
|
+
} catch {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const prefix = outputDirectory.replace(/[\\/]+$/, "");
|
|
154
|
+
text = text.split(prefix + "\\").join("").split(prefix + "/").join("");
|
|
155
|
+
await writeFile(playlist, text, "utf8");
|
|
156
|
+
}
|
|
157
|
+
async function remuxToHls(source, playlist, hls) {
|
|
158
|
+
var _stack = [];
|
|
159
|
+
try {
|
|
160
|
+
const input = __using(_stack, await Demuxer.open(source), true);
|
|
161
|
+
const output = __using(_stack, await Muxer.open(playlist, {
|
|
162
|
+
format: "hls",
|
|
163
|
+
input,
|
|
164
|
+
options: hls
|
|
165
|
+
}), true);
|
|
166
|
+
const routed = /* @__PURE__ */ new Map();
|
|
167
|
+
const video = input.video();
|
|
168
|
+
if (video) {
|
|
169
|
+
routed.set(video.index, output.addStream(video));
|
|
170
|
+
}
|
|
171
|
+
const audio = input.audio();
|
|
172
|
+
if (audio) {
|
|
173
|
+
routed.set(audio.index, output.addStream(audio));
|
|
174
|
+
}
|
|
175
|
+
for await (const packet of input.packets()) {
|
|
176
|
+
if (packet === null) {
|
|
177
|
+
break;
|
|
108
178
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (code === 0) {
|
|
113
|
-
resolvePromise();
|
|
114
|
-
return;
|
|
179
|
+
const index = routed.get(packet.streamIndex);
|
|
180
|
+
if (index !== void 0) {
|
|
181
|
+
await output.writePacket(packet, index);
|
|
115
182
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
183
|
+
packet.free();
|
|
184
|
+
}
|
|
185
|
+
await output.close();
|
|
186
|
+
await relativizePlaylist(playlist, dirname(playlist));
|
|
187
|
+
} catch (_) {
|
|
188
|
+
var _error = _, _hasError = true;
|
|
189
|
+
} finally {
|
|
190
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
191
|
+
_promise && await _promise;
|
|
192
|
+
}
|
|
126
193
|
}
|
|
127
|
-
async function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
source,
|
|
134
|
-
"-map",
|
|
135
|
-
"0:v:0",
|
|
136
|
-
"-map",
|
|
137
|
-
"0:a:0?"
|
|
138
|
-
];
|
|
139
|
-
if (options.mode === "single") {
|
|
140
|
-
args.push(
|
|
141
|
-
"-c:v",
|
|
142
|
-
"copy",
|
|
143
|
-
"-c:a",
|
|
144
|
-
"copy"
|
|
145
|
-
);
|
|
146
|
-
} else {
|
|
147
|
-
args.push(
|
|
148
|
-
"-c:v",
|
|
149
|
-
"libx264",
|
|
150
|
-
"-preset",
|
|
151
|
-
options.preset,
|
|
152
|
-
"-crf",
|
|
153
|
-
String(options.crf),
|
|
154
|
-
"-pix_fmt",
|
|
155
|
-
"yuv420p",
|
|
156
|
-
"-c:a",
|
|
157
|
-
"aac",
|
|
158
|
-
"-b:a",
|
|
159
|
-
"128k"
|
|
160
|
-
);
|
|
161
|
-
if (variant.height !== void 0) {
|
|
162
|
-
args.push(
|
|
163
|
-
"-vf",
|
|
164
|
-
`scale=-2:${variant.height}:force_original_aspect_ratio=decrease`
|
|
194
|
+
async function transcodeToHls(source, playlist, hls, options, variant) {
|
|
195
|
+
var _stack = [];
|
|
196
|
+
try {
|
|
197
|
+
if (options.mode !== "adaptive") {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"[vite-plugin-hls] internal: transcode requires adaptive mode"
|
|
165
200
|
);
|
|
166
201
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
202
|
+
const input = __using(_stack, await Demuxer.open(source), true);
|
|
203
|
+
const output = __using(_stack, await Muxer.open(playlist, {
|
|
204
|
+
format: "hls",
|
|
205
|
+
input,
|
|
206
|
+
options: hls
|
|
207
|
+
}), true);
|
|
208
|
+
const tasks = [];
|
|
209
|
+
const video = input.video();
|
|
210
|
+
if (video) {
|
|
211
|
+
const decoder = await Decoder.create(video);
|
|
212
|
+
const scale = variant.height ? `scale=-2:${variant.height}:force_original_aspect_ratio=decrease,format=yuv420p` : "format=yuv420p";
|
|
213
|
+
const filter = FilterAPI.create(scale);
|
|
214
|
+
const encoderOptions = {
|
|
215
|
+
preset: options.preset,
|
|
216
|
+
crf: String(options.crf)
|
|
217
|
+
};
|
|
218
|
+
const encoderBase = {
|
|
219
|
+
options: encoderOptions
|
|
220
|
+
};
|
|
221
|
+
if (variant.bitrate !== void 0) {
|
|
222
|
+
const bitrate = bitrateToNumber(variant.bitrate);
|
|
223
|
+
encoderOptions.maxrate = bitrate;
|
|
224
|
+
encoderOptions.bufsize = Math.round(bitrate * 1.5);
|
|
225
|
+
encoderBase.bitrate = bitrate;
|
|
226
|
+
}
|
|
227
|
+
const encoder = await Encoder.create(
|
|
228
|
+
FF_ENCODER_LIBX264,
|
|
229
|
+
encoderBase
|
|
230
|
+
);
|
|
231
|
+
const index = output.addStream(encoder, { inputStream: video });
|
|
232
|
+
tasks.push(
|
|
233
|
+
(async () => {
|
|
234
|
+
try {
|
|
235
|
+
const frames = decoder.frames(input.packets(video.index));
|
|
236
|
+
const scaled = filter.frames(frames);
|
|
237
|
+
for await (const packet of encoder.packets(scaled)) {
|
|
238
|
+
if (packet === null) {
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
await output.writePacket(packet, index);
|
|
242
|
+
packet.free();
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
filter.close();
|
|
246
|
+
decoder.close();
|
|
247
|
+
encoder.close();
|
|
248
|
+
}
|
|
249
|
+
})()
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const audio = input.audio();
|
|
253
|
+
if (audio) {
|
|
254
|
+
const decoder = await Decoder.create(audio);
|
|
255
|
+
const encoder = await Encoder.create(FF_ENCODER_AAC, {
|
|
256
|
+
decoder,
|
|
257
|
+
bitrate: 128e3
|
|
258
|
+
});
|
|
259
|
+
const index = output.addStream(encoder, { inputStream: audio });
|
|
260
|
+
tasks.push(
|
|
261
|
+
(async () => {
|
|
262
|
+
try {
|
|
263
|
+
const frames = decoder.frames(input.packets(audio.index));
|
|
264
|
+
for await (const packet of encoder.packets(frames)) {
|
|
265
|
+
if (packet === null) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
await output.writePacket(packet, index);
|
|
269
|
+
packet.free();
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
decoder.close();
|
|
273
|
+
encoder.close();
|
|
274
|
+
}
|
|
275
|
+
})()
|
|
176
276
|
);
|
|
177
277
|
}
|
|
278
|
+
await Promise.all(tasks);
|
|
279
|
+
await output.close();
|
|
280
|
+
await relativizePlaylist(playlist, dirname(playlist));
|
|
281
|
+
} catch (_) {
|
|
282
|
+
var _error = _, _hasError = true;
|
|
283
|
+
} finally {
|
|
284
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
285
|
+
_promise && await _promise;
|
|
178
286
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
189
|
-
if (options.segmentType === "fmp4") {
|
|
190
|
-
args.push(
|
|
191
|
-
"-hls_segment_type",
|
|
192
|
-
"fmp4",
|
|
193
|
-
"-hls_fmp4_init_filename",
|
|
194
|
-
"init.mp4"
|
|
195
|
-
);
|
|
287
|
+
}
|
|
288
|
+
async function encodeVariant(source, outputDirectory, options, variant = {}) {
|
|
289
|
+
const hls = hlsMuxerOptions(outputDirectory, options);
|
|
290
|
+
const playlist = join(outputDirectory, PLAYLIST);
|
|
291
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
292
|
+
if (options.mode === "single") {
|
|
293
|
+
await remuxToHls(source, playlist, hls);
|
|
294
|
+
} else {
|
|
295
|
+
await transcodeToHls(source, playlist, hls, options, variant);
|
|
196
296
|
}
|
|
197
|
-
args.push(playlist);
|
|
198
|
-
await mkdir(outputDirectory, {
|
|
199
|
-
recursive: true
|
|
200
|
-
});
|
|
201
|
-
await runFfmpeg(options.ffmpegPath, args);
|
|
202
297
|
}
|
|
203
298
|
async function createEncodeDirectory() {
|
|
204
299
|
return mkdtemp(join(tmpdir(), "vite-plugin-hls-"));
|
|
@@ -212,11 +307,11 @@ async function removeEncodeDirectory(directory) {
|
|
|
212
307
|
|
|
213
308
|
// src/cache.ts
|
|
214
309
|
import { createHash } from "crypto";
|
|
215
|
-
import { mkdir as mkdir2, readFile as
|
|
310
|
+
import { mkdir as mkdir2, readFile as readFile3, rename, rm as rm2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
216
311
|
import { join as join3, resolve } from "path";
|
|
217
312
|
|
|
218
313
|
// src/fs.ts
|
|
219
|
-
import { readFile, readdir } from "fs/promises";
|
|
314
|
+
import { readFile as readFile2, readdir } from "fs/promises";
|
|
220
315
|
import { join as join2, relative } from "path";
|
|
221
316
|
async function collectFiles(directory) {
|
|
222
317
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -229,7 +324,7 @@ async function collectFiles(directory) {
|
|
|
229
324
|
}
|
|
230
325
|
result.set(
|
|
231
326
|
relative(directory, filename).replaceAll("\\", "/"),
|
|
232
|
-
await
|
|
327
|
+
await readFile2(filename)
|
|
233
328
|
);
|
|
234
329
|
}
|
|
235
330
|
}
|
|
@@ -238,9 +333,10 @@ async function collectFiles(directory) {
|
|
|
238
333
|
}
|
|
239
334
|
|
|
240
335
|
// src/cache.ts
|
|
336
|
+
var CACHE_VERSION = "2";
|
|
241
337
|
async function getCacheKey(source, options) {
|
|
242
|
-
const sourceData = await
|
|
243
|
-
return createHash("sha256").update(sourceData).update(JSON.stringify(options)).digest("hex");
|
|
338
|
+
const sourceData = await readFile3(source);
|
|
339
|
+
return createHash("sha256").update(CACHE_VERSION).update(sourceData).update(JSON.stringify(options)).digest("hex");
|
|
244
340
|
}
|
|
245
341
|
async function readCache(cacheRoot, key) {
|
|
246
342
|
const directory = join3(cacheRoot, "hls", key);
|
|
@@ -250,7 +346,7 @@ async function readCache(cacheRoot, key) {
|
|
|
250
346
|
return void 0;
|
|
251
347
|
}
|
|
252
348
|
const metadata = JSON.parse(
|
|
253
|
-
await
|
|
349
|
+
await readFile3(join3(directory, "manifest.json"), "utf8")
|
|
254
350
|
);
|
|
255
351
|
const files = await collectFiles(directory);
|
|
256
352
|
files.delete("manifest.json");
|
|
@@ -275,9 +371,9 @@ async function writeCache(cacheRoot, key, result) {
|
|
|
275
371
|
await mkdir2(resolve(destination, ".."), {
|
|
276
372
|
recursive: true
|
|
277
373
|
});
|
|
278
|
-
await
|
|
374
|
+
await writeFile2(destination, data);
|
|
279
375
|
}
|
|
280
|
-
await
|
|
376
|
+
await writeFile2(
|
|
281
377
|
join3(temporaryDirectory, "manifest.json"),
|
|
282
378
|
JSON.stringify({
|
|
283
379
|
directoryName: result.directoryName,
|
|
@@ -340,7 +436,7 @@ async function generateHls(source, cacheRoot, options) {
|
|
|
340
436
|
);
|
|
341
437
|
}
|
|
342
438
|
const master = createMasterPlaylist(options.variants);
|
|
343
|
-
await
|
|
439
|
+
await writeFile3(join4(temporaryDirectory, "master.m3u8"), master, "utf8");
|
|
344
440
|
manifest = "master.m3u8";
|
|
345
441
|
}
|
|
346
442
|
const result = {
|
|
@@ -379,9 +475,9 @@ function emitHls(context, result, options) {
|
|
|
379
475
|
});
|
|
380
476
|
}
|
|
381
477
|
}
|
|
382
|
-
function publicManifestExpression(result, options) {
|
|
478
|
+
function publicManifestExpression(result, options, base) {
|
|
383
479
|
const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;
|
|
384
|
-
return
|
|
480
|
+
return JSON.stringify(`${base}${path}`);
|
|
385
481
|
}
|
|
386
482
|
|
|
387
483
|
// src/plugin.ts
|
|
@@ -423,7 +519,7 @@ function hlsVideos(input) {
|
|
|
423
519
|
if (!loadOptions?.ssr) {
|
|
424
520
|
emitHls(this, result, options);
|
|
425
521
|
}
|
|
426
|
-
const manifest = publicManifestExpression(result, options);
|
|
522
|
+
const manifest = publicManifestExpression(result, options, config.base);
|
|
427
523
|
return `
|
|
428
524
|
export default ${manifest};
|
|
429
525
|
`;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/options.ts","../src/resolve.ts","../src/hls.ts","../src/encoder.ts","../src/bitrate.ts","../src/cache.ts","../src/fs.ts","../src/playlist.ts","../src/assets.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["import ffmpegStatic from \"ffmpeg-static\";\n\nimport type { HlsPluginOptions, ResolvedHlsOptions } from \"./types\";\n\nexport function resolveOptions(\n input: HlsPluginOptions = {},\n): ResolvedHlsOptions {\n const common = {\n ffmpegPath: input.ffmpegPath ?? ffmpegStatic ?? \"ffmpeg\",\n\n segmentDuration: input.segmentDuration ?? 4,\n\n segmentType: input.segmentType ?? \"fmp4\",\n\n outputDir: input.outputDir ?? \"assets/hls\",\n\n preset: input.preset ?? \"medium\",\n\n crf: input.crf ?? 23,\n };\n\n if (input.mode === \"adaptive\") {\n if (!input.variants || input.variants.length === 0) {\n throw new Error(\n \"[vite-plugin-hls] Adaptive mode requires at least one variant.\",\n );\n }\n\n return {\n ...common,\n mode: \"adaptive\",\n variants: input.variants,\n };\n }\n\n return {\n ...common,\n mode: \"single\",\n };\n}\n","import { extname } from \"node:path\";\n\nimport type { PluginContext } from \"rolldown\";\n\nconst VIDEO_EXTENSIONS = new Set([\n \".mp4\",\n \".mov\",\n \".m4v\",\n \".webm\",\n \".mkv\",\n \".avi\",\n]);\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function isVideoSource(source: string): boolean {\n const pathname = source.split(\"?\")[0] ?? source;\n\n return VIDEO_EXTENSIONS.has(extname(pathname).toLowerCase());\n}\n\nexport function virtualIdForSource(source: string): string {\n return `${VIRTUAL_PREFIX}${encodeURIComponent(source)}`;\n}\n\nexport function sourceFromVirtualId(id: string): string {\n return decodeURIComponent(id.slice(VIRTUAL_PREFIX.length));\n}\n\nexport async function resolveVideoSource(\n source: string,\n importer: string,\n context: PluginContext,\n): Promise<string | undefined> {\n if (!isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await context.resolve(source, importer, {\n skipSelf: true,\n });\n\n if (!resolved || resolved.external) {\n return undefined;\n }\n\n /*\n * Queries such as ?url are irrelevant here.\n * FFmpeg needs the physical file.\n */\n return resolved.id.split(\"?\", 1)[0];\n}\n","import { basename, extname, join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport {\n encodeVariant,\n createEncodeDirectory,\n removeEncodeDirectory,\n} from \"./encoder\";\nimport { readCache, getCacheKey, writeCache } from \"./cache\";\nimport { collectFiles } from \"./fs\";\nimport { slugify, createMasterPlaylist } from \"./playlist\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport async function generateHls(\n source: string,\n cacheRoot: string,\n options: ResolvedHlsOptions,\n): Promise<CachedHls> {\n const key = await getCacheKey(source, options);\n\n const cached = await readCache(cacheRoot, key);\n\n if (cached) {\n console.log(`[vite-plugin-hls] cache hit ${source}`);\n\n return cached;\n }\n\n console.log(`[vite-plugin-hls] encoding ${source}`);\n\n const temporaryDirectory = await createEncodeDirectory();\n\n try {\n const videoName = slugify(basename(source, extname(source)));\n\n const directoryName = `${videoName}-${key.slice(0, 12)}`;\n\n let manifest: string;\n\n if (options.mode === \"single\") {\n await encodeVariant(source, temporaryDirectory, options);\n\n manifest = \"index.m3u8\";\n } else {\n for (const variant of options.variants) {\n await encodeVariant(\n source,\n join(temporaryDirectory, `${variant.height}p`),\n options,\n {\n height: variant.height,\n bitrate: variant.bitrate,\n },\n );\n }\n\n const master = createMasterPlaylist(options.variants);\n\n await writeFile(join(temporaryDirectory, \"master.m3u8\"), master, \"utf8\");\n\n manifest = \"master.m3u8\";\n }\n\n const result: CachedHls = {\n directoryName,\n manifest,\n files: await collectFiles(temporaryDirectory),\n };\n\n await writeCache(cacheRoot, key, result);\n\n console.log(`[vite-plugin-hls] generated ${directoryName}/${manifest}`);\n\n return result;\n } finally {\n await removeEncodeDirectory(temporaryDirectory);\n }\n}\n","import { mkdir, mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { ResolvedHlsOptions } from \"./types\";\n\ninterface EncodeVariantOptions {\n height?: number;\n bitrate?: string;\n}\n\nfunction runFfmpeg(executable: string, args: string[]): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(executable, args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stderr = \"\";\n\n child.stderr.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (error) => {\n if (\"code\" in error && error.code === \"ENOENT\") {\n reject(\n new Error(\n [\n \"[vite-plugin-hls] ffmpeg was not found.\",\n `Executable: ${executable}`,\n \"Set ffmpegPath or install ffmpeg.\",\n ].join(\"\\n\"),\n ),\n );\n return;\n }\n\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolvePromise();\n return;\n }\n\n reject(\n new Error(\n [\n `[vite-plugin-hls] ffmpeg failed with exit code ${code ?? \"unknown\"}.`,\n stderr.trim(),\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n ),\n );\n });\n });\n}\n\nexport async function encodeVariant(\n source: string,\n outputDirectory: string,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions = {},\n): Promise<void> {\n const extension = options.segmentType === \"fmp4\" ? \"m4s\" : \"ts\";\n\n const playlist = join(outputDirectory, \"index.m3u8\");\n\n const args: string[] = [\n \"-y\",\n \"-i\",\n source,\n\n \"-map\",\n \"0:v:0\",\n \"-map\",\n \"0:a:0?\",\n ];\n\n /*\n * SINGLE MODE:\n *\n * Do not encode the streams again.\n * FFmpeg only remuxes/segments them into HLS.\n */\n if (options.mode === \"single\") {\n args.push(\n \"-c:v\",\n \"copy\",\n\n \"-c:a\",\n \"copy\",\n );\n } else {\n /*\n * ADAPTIVE MODE:\n *\n * Each rendition needs to be encoded because\n * resolution and bitrate are changed.\n */\n args.push(\n \"-c:v\",\n \"libx264\",\n\n \"-preset\",\n options.preset,\n\n \"-crf\",\n String(options.crf),\n\n \"-pix_fmt\",\n \"yuv420p\",\n\n \"-c:a\",\n \"aac\",\n\n \"-b:a\",\n \"128k\",\n );\n\n if (variant.height !== undefined) {\n args.push(\n \"-vf\",\n `scale=-2:${variant.height}:force_original_aspect_ratio=decrease`,\n );\n }\n\n if (variant.bitrate !== undefined) {\n const bitrate = bitrateToNumber(variant.bitrate);\n\n args.push(\n \"-b:v\",\n variant.bitrate,\n\n \"-maxrate\",\n variant.bitrate,\n\n \"-bufsize\",\n String(Math.round(bitrate * 1.5)),\n );\n }\n }\n\n args.push(\n \"-f\",\n \"hls\",\n\n \"-hls_time\",\n String(options.segmentDuration),\n\n \"-hls_playlist_type\",\n \"vod\",\n\n \"-hls_segment_filename\",\n join(outputDirectory, `segment-%05d.${extension}`),\n );\n\n if (options.segmentType === \"fmp4\") {\n args.push(\n \"-hls_segment_type\",\n \"fmp4\",\n\n \"-hls_fmp4_init_filename\",\n \"init.mp4\",\n );\n }\n\n args.push(playlist);\n\n /*\n * FFmpeg's HLS muxer does not create the output directory.\n * Ensure it exists so segments/playlist can be written.\n */\n await mkdir(outputDirectory, {\n recursive: true,\n });\n\n await runFfmpeg(options.ffmpegPath, args);\n}\n\nexport async function createEncodeDirectory(): Promise<string> {\n return mkdtemp(join(tmpdir(), \"vite-plugin-hls-\"));\n}\n\nexport async function removeEncodeDirectory(directory: string): Promise<void> {\n await rm(directory, {\n recursive: true,\n force: true,\n });\n}\n","/**\n * Converts a human-readable bitrate such as \"1200k\", \"2.5M\" or \"1G\"\n * into the equivalent number of bits per second.\n */\nexport function bitrateToNumber(bitrate: string): number {\n const match = /^([\\d.]+)\\s*([kKmMgG]?)$/.exec(bitrate.trim());\n\n if (!match) {\n throw new Error(`[vite-plugin-hls] Invalid bitrate: ${bitrate}`);\n }\n\n const amount = Number(match[1]);\n\n const suffix = (match[2] ?? \"\").toLowerCase();\n\n const multiplier =\n suffix === \"g\"\n ? 1_000_000_000\n : suffix === \"m\"\n ? 1_000_000\n : suffix === \"k\"\n ? 1_000\n : 1;\n\n return Math.round(amount * multiplier);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { collectFiles } from \"./fs\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\ninterface CacheManifest {\n directoryName: string;\n manifest: string;\n}\n\nexport async function getCacheKey(\n source: string,\n options: ResolvedHlsOptions,\n): Promise<string> {\n const sourceData = await readFile(source);\n\n return createHash(\"sha256\")\n .update(sourceData)\n .update(JSON.stringify(options))\n .digest(\"hex\");\n}\n\nexport async function readCache(\n cacheRoot: string,\n key: string,\n): Promise<CachedHls | undefined> {\n const directory = join(cacheRoot, \"hls\", key);\n\n try {\n await stat(directory);\n } catch {\n return undefined;\n }\n\n const metadata = JSON.parse(\n await readFile(join(directory, \"manifest.json\"), \"utf8\"),\n ) as CacheManifest;\n\n const files = await collectFiles(directory);\n\n // manifest.json is internal bookkeeping, never an emitted asset.\n files.delete(\"manifest.json\");\n\n return {\n directoryName: metadata.directoryName,\n\n manifest: metadata.manifest,\n\n files,\n };\n}\n\nexport async function writeCache(\n cacheRoot: string,\n key: string,\n result: CachedHls,\n): Promise<void> {\n const temporaryDirectory = join(cacheRoot, \"hls\", `${key}.tmp`);\n\n const finalDirectory = join(cacheRoot, \"hls\", key);\n\n await rm(temporaryDirectory, {\n recursive: true,\n force: true,\n });\n\n await mkdir(temporaryDirectory, {\n recursive: true,\n });\n\n for (const [filename, data] of result.files) {\n const destination = join(temporaryDirectory, filename);\n\n await mkdir(resolve(destination, \"..\"), {\n recursive: true,\n });\n\n await writeFile(destination, data);\n }\n\n await writeFile(\n join(temporaryDirectory, \"manifest.json\"),\n JSON.stringify({\n directoryName: result.directoryName,\n manifest: result.manifest,\n } satisfies CacheManifest),\n \"utf8\",\n );\n\n await rm(finalDirectory, {\n recursive: true,\n force: true,\n });\n\n await rename(temporaryDirectory, finalDirectory);\n}\n","import { readFile, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\n/**\n * Recursively collects every file under `directory` into a map of\n * directory-relative POSIX paths to their buffer contents.\n */\nexport async function collectFiles(\n directory: string,\n): Promise<Map<string, Buffer>> {\n const result = new Map<string, Buffer>();\n\n async function visit(current: string): Promise<void> {\n for (const entry of await readdir(current, { withFileTypes: true })) {\n const filename = join(current, entry.name);\n\n if (entry.isDirectory()) {\n await visit(filename);\n continue;\n }\n\n result.set(\n relative(directory, filename).replaceAll(\"\\\\\", \"/\"),\n await readFile(filename),\n );\n }\n }\n\n await visit(directory);\n\n return result;\n}\n","import { extname } from \"node:path\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { HlsVariant } from \"./types\";\n\n/** Turns a filename into a filesystem-safe slug used for the output folder. */\nexport function slugify(filename: string): string {\n return (\n filename\n .replace(extname(filename), \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase() || \"video\"\n );\n}\n\n/** Builds the adaptive master playlist pointing at each rendition. */\nexport function createMasterPlaylist(variants: readonly HlsVariant[]): string {\n const lines = [\"#EXTM3U\", \"#EXT-X-VERSION:7\"];\n\n for (const variant of variants) {\n // BANDWIDTH is an estimate derived from the configured bitrate.\n const bandwidth = Math.round(bitrateToNumber(variant.bitrate) * 1.15);\n\n lines.push(\n `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}`,\n `${variant.height}p/index.m3u8`,\n );\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { PluginContext } from \"rolldown\";\n\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport function emitHls(\n context: PluginContext,\n result: CachedHls,\n options: ResolvedHlsOptions,\n): void {\n const prefix = `${options.outputDir}/${result.directoryName}`;\n\n const manifestSource = result.files.get(result.manifest);\n\n if (!manifestSource) {\n throw new Error(`[vite-plugin-hls] Missing manifest ${result.manifest}`);\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${result.manifest}`,\n source: manifestSource,\n });\n\n for (const [filename, data] of result.files) {\n if (filename === result.manifest) {\n continue;\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${filename}`,\n source: data,\n });\n }\n}\n\nexport function publicManifestExpression(\n result: CachedHls,\n options: ResolvedHlsOptions,\n): string {\n const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;\n\n /*\n * BASE_URL is replaced by Vite in both the client\n * and SSR builds.\n *\n * Examples:\n *\n * \"/\" -> /assets/hls/...\n * \"/notes/\" -> /notes/assets/hls/...\n * \"./\" -> ./assets/hls/...\n */\n return `import.meta.env.BASE_URL + ${JSON.stringify(path)}`;\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\n\nimport { resolveOptions } from \"./options\";\nimport {\n isVideoSource,\n resolveVideoSource,\n sourceFromVirtualId,\n virtualIdForSource,\n} from \"./resolve\";\nimport { generateHls } from \"./hls\";\nimport { emitHls, publicManifestExpression } from \"./assets\";\nimport type { HlsPluginOptions } from \"./types\";\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function hlsVideos(input?: HlsPluginOptions): Plugin {\n const options = resolveOptions(input);\n\n let config: ResolvedConfig;\n\n const resolvedSources = new Map<string, string>();\n\n /*\n * This only caches inside the current Vite build.\n *\n * The persistent cache in cache.ts handles the\n * client/server build boundary.\n */\n const generated = new Map<string, Awaited<ReturnType<typeof generateHls>>>();\n\n return {\n name: \"vite-plugin-hls\",\n\n apply: \"build\",\n\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved;\n },\n\n async resolveId(source, importer) {\n if (!importer || !isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await resolveVideoSource(source, importer, this);\n\n if (!resolved) {\n return undefined;\n }\n\n const id = virtualIdForSource(resolved);\n\n resolvedSources.set(id, resolved);\n\n return id;\n },\n\n async load(id, loadOptions) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return undefined;\n }\n\n const source = resolvedSources.get(id) ?? sourceFromVirtualId(id);\n\n let result = generated.get(source);\n\n if (!result) {\n result = await generateHls(source, config.cacheDir, options);\n\n generated.set(source, result);\n }\n\n /*\n * Important:\n *\n * The SSR build must NOT use Vite's emitted-file\n * URL because that resolves to a file:// URL.\n *\n * We only emit the actual files during the client\n * build. SSR receives the public URL directly.\n */\n if (!loadOptions?.ssr) {\n emitHls(this, result, options);\n }\n\n const manifest = publicManifestExpression(result, options);\n\n return `\n export default ${manifest};\n `;\n },\n };\n}\n","import { hlsVideos } from \"./plugin\";\n\nexport { hlsVideos };\nexport default hlsVideos;\n\nexport type { HlsPluginOptions, HlsVariant } from \"./types\";\n"],"mappings":"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","names":["extname","join","writeFile","mkdir","readFile","rm","join","join","readFile","join","rm","mkdir","extname","extname","extname","join","writeFile","VIRTUAL_PREFIX"]}
|
|
1
|
+
{"version":3,"sources":["../src/options.ts","../src/resolve.ts","../src/hls.ts","../src/encoder.ts","../src/bitrate.ts","../src/cache.ts","../src/fs.ts","../src/playlist.ts","../src/assets.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["import type { HlsPluginOptions, ResolvedHlsOptions } from \"./types\";\n\nexport function resolveOptions(\n input: HlsPluginOptions = {},\n): ResolvedHlsOptions {\n const common = {\n segmentDuration: input.segmentDuration ?? 4,\n\n segmentType: input.segmentType ?? \"fmp4\",\n\n outputDir: input.outputDir ?? \"assets/hls\",\n\n preset: input.preset ?? \"medium\",\n\n crf: input.crf ?? 23,\n };\n\n if (input.mode === \"adaptive\") {\n if (!input.variants || input.variants.length === 0) {\n throw new Error(\n \"[vite-plugin-hls] Adaptive mode requires at least one variant.\",\n );\n }\n\n return {\n ...common,\n mode: \"adaptive\",\n variants: input.variants,\n };\n }\n\n return {\n ...common,\n mode: \"single\",\n };\n}\n","import { extname } from \"node:path\";\n\nimport type { PluginContext } from \"rolldown\";\n\nconst VIDEO_EXTENSIONS = new Set([\n \".mp4\",\n \".mov\",\n \".m4v\",\n \".webm\",\n \".mkv\",\n \".avi\",\n]);\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function isVideoSource(source: string): boolean {\n const pathname = source.split(\"?\")[0] ?? source;\n\n return VIDEO_EXTENSIONS.has(extname(pathname).toLowerCase());\n}\n\nexport function virtualIdForSource(source: string): string {\n return `${VIRTUAL_PREFIX}${encodeURIComponent(source)}`;\n}\n\nexport function sourceFromVirtualId(id: string): string {\n return decodeURIComponent(id.slice(VIRTUAL_PREFIX.length));\n}\n\nexport async function resolveVideoSource(\n source: string,\n importer: string,\n context: PluginContext,\n): Promise<string | undefined> {\n if (!isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await context.resolve(source, importer, {\n skipSelf: true,\n });\n\n if (!resolved || resolved.external) {\n return undefined;\n }\n\n /*\n * Queries such as ?url are irrelevant here.\n * FFmpeg needs the physical file.\n */\n return resolved.id.split(\"?\", 1)[0];\n}\n","import { basename, extname, join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport {\n encodeVariant,\n createEncodeDirectory,\n removeEncodeDirectory,\n} from \"./encoder\";\nimport { readCache, getCacheKey, writeCache } from \"./cache\";\nimport { collectFiles } from \"./fs\";\nimport { slugify, createMasterPlaylist } from \"./playlist\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport async function generateHls(\n source: string,\n cacheRoot: string,\n options: ResolvedHlsOptions,\n): Promise<CachedHls> {\n const key = await getCacheKey(source, options);\n\n const cached = await readCache(cacheRoot, key);\n\n if (cached) {\n console.log(`[vite-plugin-hls] cache hit ${source}`);\n\n return cached;\n }\n\n console.log(`[vite-plugin-hls] encoding ${source}`);\n\n const temporaryDirectory = await createEncodeDirectory();\n\n try {\n const videoName = slugify(basename(source, extname(source)));\n\n const directoryName = `${videoName}-${key.slice(0, 12)}`;\n\n let manifest: string;\n\n if (options.mode === \"single\") {\n await encodeVariant(source, temporaryDirectory, options);\n\n manifest = \"index.m3u8\";\n } else {\n for (const variant of options.variants) {\n await encodeVariant(\n source,\n join(temporaryDirectory, `${variant.height}p`),\n options,\n {\n height: variant.height,\n bitrate: variant.bitrate,\n },\n );\n }\n\n const master = createMasterPlaylist(options.variants);\n\n await writeFile(join(temporaryDirectory, \"master.m3u8\"), master, \"utf8\");\n\n manifest = \"master.m3u8\";\n }\n\n const result: CachedHls = {\n directoryName,\n manifest,\n files: await collectFiles(temporaryDirectory),\n };\n\n await writeCache(cacheRoot, key, result);\n\n console.log(`[vite-plugin-hls] generated ${directoryName}/${manifest}`);\n\n return result;\n } finally {\n await removeEncodeDirectory(temporaryDirectory);\n }\n}\n","import { mkdir, mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\nimport { Decoder, Demuxer, Encoder, FilterAPI, Muxer } from \"node-av/api\";\nimport { FF_ENCODER_AAC, FF_ENCODER_LIBX264 } from \"node-av/constants\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { ResolvedHlsOptions } from \"./types\";\n\ninterface EncodeVariantOptions {\n height?: number;\n bitrate?: string;\n}\n\nconst PLAYLIST = \"index.m3u8\";\n\ntype HlsOptions = {\n hls_time: string;\n hls_playlist_type: \"vod\";\n hls_segment_filename: string;\n hls_segment_type?: \"mpegts\" | \"fmp4\";\n hls_fmp4_init_filename?: string;\n};\n\nfunction hlsMuxerOptions(\n outputDirectory: string,\n options: ResolvedHlsOptions,\n): HlsOptions {\n const extension = options.segmentType === \"fmp4\" ? \"m4s\" : \"ts\";\n\n const hls: HlsOptions = {\n hls_time: String(options.segmentDuration),\n hls_playlist_type: \"vod\",\n hls_segment_filename: join(outputDirectory, `segment-%05d.${extension}`),\n };\n\n if (options.segmentType === \"fmp4\") {\n hls.hls_segment_type = \"fmp4\";\n\n /*\n * The init filename must be absolute so the file lands next to the\n * playlist. A relative name would be resolved against the process CWD\n * (the project root) and leak there. The playlist is relativized after\n * encoding (see relativizePlaylist).\n */\n hls.hls_fmp4_init_filename = join(outputDirectory, \"init.mp4\");\n }\n\n return hls;\n}\n\n/*\n * ffmpeg references an absolute init path literally in EXT-X-MAP (segment\n * URIs are already emitted as basenames). Rewrite it to a path relative to\n * the output directory so the playlist stays portable.\n */\nasync function relativizePlaylist(\n playlist: string,\n outputDirectory: string,\n): Promise<void> {\n let text: string;\n try {\n text = await readFile(playlist, \"utf8\");\n } catch {\n return; // nothing written (e.g. mocked in tests)\n }\n\n const prefix = outputDirectory.replace(/[\\\\/]+$/, \"\");\n\n text = text\n .split(prefix + \"\\\\\")\n .join(\"\")\n .split(prefix + \"/\")\n .join(\"\");\n\n await writeFile(playlist, text, \"utf8\");\n}\n\n/*\n * SINGLE MODE (remux): copy the streams, only repackage into HLS.\n */\nasync function remuxToHls(\n source: string,\n playlist: string,\n hls: HlsOptions,\n): Promise<void> {\n await using input = await Demuxer.open(source);\n await using output = await Muxer.open(playlist, {\n format: \"hls\",\n input,\n options: hls,\n });\n\n const routed = new Map<number, number>();\n\n const video = input.video();\n if (video) {\n routed.set(video.index, output.addStream(video));\n }\n\n const audio = input.audio();\n if (audio) {\n routed.set(audio.index, output.addStream(audio));\n }\n\n for await (const packet of input.packets()) {\n if (packet === null) {\n break;\n }\n\n const index = routed.get(packet.streamIndex);\n\n if (index !== undefined) {\n await output.writePacket(packet, index);\n }\n\n packet.free();\n }\n\n await output.close();\n\n await relativizePlaylist(playlist, dirname(playlist));\n}\n\n/*\n * ADAPTIVE MODE (transcode): re-encode each rendition's video (scaled,\n * libx264) and audio (aac) into its HLS output.\n */\nasync function transcodeToHls(\n source: string,\n playlist: string,\n hls: HlsOptions,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions,\n): Promise<void> {\n if (options.mode !== \"adaptive\") {\n throw new Error(\n \"[vite-plugin-hls] internal: transcode requires adaptive mode\",\n );\n }\n\n await using input = await Demuxer.open(source);\n await using output = await Muxer.open(playlist, {\n format: \"hls\",\n input,\n options: hls,\n });\n\n const tasks: Promise<void>[] = [];\n\n const video = input.video();\n if (video) {\n const decoder = await Decoder.create(video);\n\n const scale = variant.height\n ? `scale=-2:${variant.height}:force_original_aspect_ratio=decrease,format=yuv420p`\n : \"format=yuv420p\";\n\n const filter = FilterAPI.create(scale);\n\n const encoderOptions: Record<string, string | number> = {\n preset: options.preset,\n crf: String(options.crf),\n };\n\n const encoderBase: Record<string, unknown> = {\n options: encoderOptions,\n };\n\n if (variant.bitrate !== undefined) {\n const bitrate = bitrateToNumber(variant.bitrate);\n\n encoderOptions.maxrate = bitrate;\n encoderOptions.bufsize = Math.round(bitrate * 1.5);\n encoderBase.bitrate = bitrate;\n }\n\n const encoder = await Encoder.create(\n FF_ENCODER_LIBX264,\n encoderBase as Parameters<typeof Encoder.create>[1],\n );\n\n const index = output.addStream(encoder, { inputStream: video });\n\n tasks.push(\n (async () => {\n try {\n const frames = decoder.frames(input.packets(video.index));\n const scaled = filter.frames(frames);\n\n for await (const packet of encoder.packets(scaled)) {\n if (packet === null) {\n break;\n }\n await output.writePacket(packet, index);\n packet.free();\n }\n } finally {\n filter.close();\n decoder.close();\n encoder.close();\n }\n })(),\n );\n }\n\n const audio = input.audio();\n if (audio) {\n const decoder = await Decoder.create(audio);\n\n const encoder = await Encoder.create(FF_ENCODER_AAC, {\n decoder,\n bitrate: 128_000,\n });\n\n const index = output.addStream(encoder, { inputStream: audio });\n\n tasks.push(\n (async () => {\n try {\n const frames = decoder.frames(input.packets(audio.index));\n\n for await (const packet of encoder.packets(frames)) {\n if (packet === null) {\n break;\n }\n await output.writePacket(packet, index);\n packet.free();\n }\n } finally {\n decoder.close();\n encoder.close();\n }\n })(),\n );\n }\n\n await Promise.all(tasks);\n await output.close();\n\n await relativizePlaylist(playlist, dirname(playlist));\n}\n\nexport async function encodeVariant(\n source: string,\n outputDirectory: string,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions = {},\n): Promise<void> {\n const hls = hlsMuxerOptions(outputDirectory, options);\n const playlist = join(outputDirectory, PLAYLIST);\n\n /*\n * FFmpeg's HLS muxer does not create the output directory.\n * Ensure it exists so segments/playlist can be written.\n */\n await mkdir(outputDirectory, { recursive: true });\n\n if (options.mode === \"single\") {\n await remuxToHls(source, playlist, hls);\n } else {\n await transcodeToHls(source, playlist, hls, options, variant);\n }\n}\n\nexport async function createEncodeDirectory(): Promise<string> {\n return mkdtemp(join(tmpdir(), \"vite-plugin-hls-\"));\n}\n\nexport async function removeEncodeDirectory(directory: string): Promise<void> {\n await rm(directory, {\n recursive: true,\n force: true,\n });\n}\n","/**\n * Converts a human-readable bitrate such as \"1200k\", \"2.5M\" or \"1G\"\n * into the equivalent number of bits per second.\n */\nexport function bitrateToNumber(bitrate: string): number {\n const match = /^([\\d.]+)\\s*([kKmMgG]?)$/.exec(bitrate.trim());\n\n if (!match) {\n throw new Error(`[vite-plugin-hls] Invalid bitrate: ${bitrate}`);\n }\n\n const amount = Number(match[1]);\n\n const suffix = (match[2] ?? \"\").toLowerCase();\n\n const multiplier =\n suffix === \"g\"\n ? 1_000_000_000\n : suffix === \"m\"\n ? 1_000_000\n : suffix === \"k\"\n ? 1_000\n : 1;\n\n return Math.round(amount * multiplier);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { collectFiles } from \"./fs\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\ninterface CacheManifest {\n directoryName: string;\n manifest: string;\n}\n\n/*\n * Bump whenever the encoder's output layout changes (e.g. a bug fix alters\n * which files are produced). Old cache entries are keyed only on source +\n * options and would otherwise be served forever, so the version is folded\n * into the key to invalidate them.\n */\nconst CACHE_VERSION = \"2\";\n\nexport async function getCacheKey(\n source: string,\n options: ResolvedHlsOptions,\n): Promise<string> {\n const sourceData = await readFile(source);\n\n return createHash(\"sha256\")\n .update(CACHE_VERSION)\n .update(sourceData)\n .update(JSON.stringify(options))\n .digest(\"hex\");\n}\n\nexport async function readCache(\n cacheRoot: string,\n key: string,\n): Promise<CachedHls | undefined> {\n const directory = join(cacheRoot, \"hls\", key);\n\n try {\n await stat(directory);\n } catch {\n return undefined;\n }\n\n const metadata = JSON.parse(\n await readFile(join(directory, \"manifest.json\"), \"utf8\"),\n ) as CacheManifest;\n\n const files = await collectFiles(directory);\n\n // manifest.json is internal bookkeeping, never an emitted asset.\n files.delete(\"manifest.json\");\n\n return {\n directoryName: metadata.directoryName,\n\n manifest: metadata.manifest,\n\n files,\n };\n}\n\nexport async function writeCache(\n cacheRoot: string,\n key: string,\n result: CachedHls,\n): Promise<void> {\n const temporaryDirectory = join(cacheRoot, \"hls\", `${key}.tmp`);\n\n const finalDirectory = join(cacheRoot, \"hls\", key);\n\n await rm(temporaryDirectory, {\n recursive: true,\n force: true,\n });\n\n await mkdir(temporaryDirectory, {\n recursive: true,\n });\n\n for (const [filename, data] of result.files) {\n const destination = join(temporaryDirectory, filename);\n\n await mkdir(resolve(destination, \"..\"), {\n recursive: true,\n });\n\n await writeFile(destination, data);\n }\n\n await writeFile(\n join(temporaryDirectory, \"manifest.json\"),\n JSON.stringify({\n directoryName: result.directoryName,\n manifest: result.manifest,\n } satisfies CacheManifest),\n \"utf8\",\n );\n\n await rm(finalDirectory, {\n recursive: true,\n force: true,\n });\n\n await rename(temporaryDirectory, finalDirectory);\n}\n","import { readFile, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\n/**\n * Recursively collects every file under `directory` into a map of\n * directory-relative POSIX paths to their buffer contents.\n */\nexport async function collectFiles(\n directory: string,\n): Promise<Map<string, Buffer>> {\n const result = new Map<string, Buffer>();\n\n async function visit(current: string): Promise<void> {\n for (const entry of await readdir(current, { withFileTypes: true })) {\n const filename = join(current, entry.name);\n\n if (entry.isDirectory()) {\n await visit(filename);\n continue;\n }\n\n result.set(\n relative(directory, filename).replaceAll(\"\\\\\", \"/\"),\n await readFile(filename),\n );\n }\n }\n\n await visit(directory);\n\n return result;\n}\n","import { extname } from \"node:path\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { HlsVariant } from \"./types\";\n\n/** Turns a filename into a filesystem-safe slug used for the output folder. */\nexport function slugify(filename: string): string {\n return (\n filename\n .replace(extname(filename), \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase() || \"video\"\n );\n}\n\n/** Builds the adaptive master playlist pointing at each rendition. */\nexport function createMasterPlaylist(variants: readonly HlsVariant[]): string {\n const lines = [\"#EXTM3U\", \"#EXT-X-VERSION:7\"];\n\n for (const variant of variants) {\n // BANDWIDTH is an estimate derived from the configured bitrate.\n const bandwidth = Math.round(bitrateToNumber(variant.bitrate) * 1.15);\n\n lines.push(\n `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}`,\n `${variant.height}p/index.m3u8`,\n );\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { PluginContext } from \"rolldown\";\n\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport function emitHls(\n context: PluginContext,\n result: CachedHls,\n options: ResolvedHlsOptions,\n): void {\n const prefix = `${options.outputDir}/${result.directoryName}`;\n\n const manifestSource = result.files.get(result.manifest);\n\n if (!manifestSource) {\n throw new Error(`[vite-plugin-hls] Missing manifest ${result.manifest}`);\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${result.manifest}`,\n source: manifestSource,\n });\n\n for (const [filename, data] of result.files) {\n if (filename === result.manifest) {\n continue;\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${filename}`,\n source: data,\n });\n }\n}\n\nexport function publicManifestExpression(\n result: CachedHls,\n options: ResolvedHlsOptions,\n base: string,\n): string {\n const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;\n\n /*\n * Bake Vite's resolved base straight into the module. Vite does not always\n * statically replace import.meta.env.BASE_URL (e.g. in SSR bundles), where\n * import.meta.env is undefined at runtime and crashes. Emitting the base as\n * a plain string is equivalent and works for client and SSR alike.\n *\n * Examples (base -> export):\n *\n * \"/\" -> \"/assets/hls/...\"\n * \"/notes/\" -> \"/notes/assets/hls/...\"\n */\n return JSON.stringify(`${base}${path}`);\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\n\nimport { resolveOptions } from \"./options\";\nimport {\n isVideoSource,\n resolveVideoSource,\n sourceFromVirtualId,\n virtualIdForSource,\n} from \"./resolve\";\nimport { generateHls } from \"./hls\";\nimport { emitHls, publicManifestExpression } from \"./assets\";\nimport type { HlsPluginOptions } from \"./types\";\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function hlsVideos(input?: HlsPluginOptions): Plugin {\n const options = resolveOptions(input);\n\n let config: ResolvedConfig;\n\n const resolvedSources = new Map<string, string>();\n\n /*\n * This only caches inside the current Vite build.\n *\n * The persistent cache in cache.ts handles the\n * client/server build boundary.\n */\n const generated = new Map<string, Awaited<ReturnType<typeof generateHls>>>();\n\n return {\n name: \"vite-plugin-hls\",\n\n apply: \"build\",\n\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved;\n },\n\n async resolveId(source, importer) {\n if (!importer || !isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await resolveVideoSource(source, importer, this);\n\n if (!resolved) {\n return undefined;\n }\n\n const id = virtualIdForSource(resolved);\n\n resolvedSources.set(id, resolved);\n\n return id;\n },\n\n async load(id, loadOptions) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return undefined;\n }\n\n const source = resolvedSources.get(id) ?? sourceFromVirtualId(id);\n\n let result = generated.get(source);\n\n if (!result) {\n result = await generateHls(source, config.cacheDir, options);\n\n generated.set(source, result);\n }\n\n /*\n * Important:\n *\n * The SSR build must NOT use Vite's emitted-file\n * URL because that resolves to a file:// URL.\n *\n * We only emit the actual files during the client\n * build. SSR receives the public URL directly.\n */\n if (!loadOptions?.ssr) {\n emitHls(this, result, options);\n }\n\n const manifest = publicManifestExpression(result, options, config.base);\n\n return `\n export default ${manifest};\n `;\n },\n };\n}\n","import { hlsVideos } from \"./plugin\";\n\nexport { hlsVideos };\nexport default hlsVideos;\n\nexport type { HlsPluginOptions, HlsVariant } from \"./types\";\n"],"mappings":"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","names":["extname","join","writeFile","mkdir","readFile","rm","writeFile","join","readFile","join","readFile","join","rm","mkdir","writeFile","extname","extname","extname","join","writeFile","VIRTUAL_PREFIX"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karsten_zhou/vite-plugin-hls",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Transcode video files to HLS (HTTP Live Streaming) at Vite build time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"test": "vitest run",
|
|
55
55
|
"test:watch": "vitest",
|
|
56
56
|
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
58
|
+
"test:e2e:clean": "node test/e2e/clean.ts",
|
|
57
59
|
"check": "npm run typecheck && npm run lint && npm run format:check && npm run test && npm run build",
|
|
58
60
|
"prepare": "husky",
|
|
59
61
|
"changeset": "changeset",
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
"access": "public"
|
|
72
74
|
},
|
|
73
75
|
"dependencies": {
|
|
74
|
-
"
|
|
76
|
+
"node-av": "^6.1.1"
|
|
75
77
|
},
|
|
76
78
|
"peerDependencies": {
|
|
77
79
|
"vite": "^8.0.0"
|
|
@@ -94,5 +96,9 @@
|
|
|
94
96
|
"typescript-eslint": "^8.69.0",
|
|
95
97
|
"vite": "^8.2.2",
|
|
96
98
|
"vitest": "^5.0.0"
|
|
99
|
+
},
|
|
100
|
+
"allowScripts": {
|
|
101
|
+
"node-av": true,
|
|
102
|
+
"esbuild": true
|
|
97
103
|
}
|
|
98
104
|
}
|