@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 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. FFmpeg is bundled automatically via
26
- `ffmpeg-static` (falling back to a system `ffmpeg` on `PATH` if unavailable).
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 | Description |
79
- | ----------------- | ------------------------ | --------------- | ----------------------------------------------------------------- |
80
- | `mode` | `"single" \| "adaptive"` | `"single"` | `"single"` remuxes the source; `"adaptive"` encodes renditions. |
81
- | `variants` | `HlsVariant[]` | — | Required in `adaptive` mode: `{ height, bitrate }` per rendition. |
82
- | `ffmpegPath` | `string` | `ffmpeg-static` | Explicit ffmpeg executable. |
83
- | `segmentDuration` | `number` | `4` | HLS segment duration in seconds. |
84
- | `segmentType` | `"fmp4" \| "mpegts"` | `"fmp4"` | HLS segment container format. |
85
- | `outputDir` | `string` | `"assets/hls"` | Directory (inside the Vite output) where HLS assets are written. |
86
- | `preset` | `string` | `"medium"` | FFmpeg `-preset` used in adaptive mode. |
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
- (`import.meta.env.BASE_URL` + path), so it works under any `base`.
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 import_node_child_process = require("child_process");
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
- function runFfmpeg(executable, args) {
125
- return new Promise((resolvePromise, reject) => {
126
- const child = (0, import_node_child_process.spawn)(executable, args, {
127
- stdio: ["ignore", "pipe", "pipe"]
128
- });
129
- let stderr = "";
130
- child.stderr.on("data", (data) => {
131
- stderr += data.toString();
132
- });
133
- child.on("error", (error) => {
134
- if ("code" in error && error.code === "ENOENT") {
135
- reject(
136
- new Error(
137
- [
138
- "[vite-plugin-hls] ffmpeg was not found.",
139
- `Executable: ${executable}`,
140
- "Set ffmpegPath or install ffmpeg."
141
- ].join("\n")
142
- )
143
- );
144
- return;
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
- reject(error);
147
- });
148
- child.on("close", (code) => {
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
- reject(
154
- new Error(
155
- [
156
- `[vite-plugin-hls] ffmpeg failed with exit code ${code ?? "unknown"}.`,
157
- stderr.trim()
158
- ].filter(Boolean).join("\n")
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 encodeVariant(source, outputDirectory, options, variant = {}) {
165
- const extension = options.segmentType === "fmp4" ? "m4s" : "ts";
166
- const playlist = (0, import_node_path2.join)(outputDirectory, "index.m3u8");
167
- const args = [
168
- "-y",
169
- "-i",
170
- source,
171
- "-map",
172
- "0:v:0",
173
- "-map",
174
- "0:a:0?"
175
- ];
176
- if (options.mode === "single") {
177
- args.push(
178
- "-c:v",
179
- "copy",
180
- "-c:a",
181
- "copy"
182
- );
183
- } else {
184
- args.push(
185
- "-c:v",
186
- "libx264",
187
- "-preset",
188
- options.preset,
189
- "-crf",
190
- String(options.crf),
191
- "-pix_fmt",
192
- "yuv420p",
193
- "-c:a",
194
- "aac",
195
- "-b:a",
196
- "128k"
197
- );
198
- if (variant.height !== void 0) {
199
- args.push(
200
- "-vf",
201
- `scale=-2:${variant.height}:force_original_aspect_ratio=decrease`
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
- if (variant.bitrate !== void 0) {
205
- const bitrate = bitrateToNumber(variant.bitrate);
206
- args.push(
207
- "-b:v",
208
- variant.bitrate,
209
- "-maxrate",
210
- variant.bitrate,
211
- "-bufsize",
212
- String(Math.round(bitrate * 1.5))
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
- args.push(
217
- "-f",
218
- "hls",
219
- "-hls_time",
220
- String(options.segmentDuration),
221
- "-hls_playlist_type",
222
- "vod",
223
- "-hls_segment_filename",
224
- (0, import_node_path2.join)(outputDirectory, `segment-%05d.${extension}`)
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 `import.meta.env.BASE_URL + ${JSON.stringify(path)}`;
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
  `;
@@ -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 writeFile2 } from "fs/promises";
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 { spawn } from "child_process";
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
- function runFfmpeg(executable, args) {
88
- return new Promise((resolvePromise, reject) => {
89
- const child = spawn(executable, args, {
90
- stdio: ["ignore", "pipe", "pipe"]
91
- });
92
- let stderr = "";
93
- child.stderr.on("data", (data) => {
94
- stderr += data.toString();
95
- });
96
- child.on("error", (error) => {
97
- if ("code" in error && error.code === "ENOENT") {
98
- reject(
99
- new Error(
100
- [
101
- "[vite-plugin-hls] ffmpeg was not found.",
102
- `Executable: ${executable}`,
103
- "Set ffmpegPath or install ffmpeg."
104
- ].join("\n")
105
- )
106
- );
107
- return;
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
- reject(error);
110
- });
111
- child.on("close", (code) => {
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
- reject(
117
- new Error(
118
- [
119
- `[vite-plugin-hls] ffmpeg failed with exit code ${code ?? "unknown"}.`,
120
- stderr.trim()
121
- ].filter(Boolean).join("\n")
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 encodeVariant(source, outputDirectory, options, variant = {}) {
128
- const extension = options.segmentType === "fmp4" ? "m4s" : "ts";
129
- const playlist = join(outputDirectory, "index.m3u8");
130
- const args = [
131
- "-y",
132
- "-i",
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
- if (variant.bitrate !== void 0) {
168
- const bitrate = bitrateToNumber(variant.bitrate);
169
- args.push(
170
- "-b:v",
171
- variant.bitrate,
172
- "-maxrate",
173
- variant.bitrate,
174
- "-bufsize",
175
- String(Math.round(bitrate * 1.5))
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
- args.push(
180
- "-f",
181
- "hls",
182
- "-hls_time",
183
- String(options.segmentDuration),
184
- "-hls_playlist_type",
185
- "vod",
186
- "-hls_segment_filename",
187
- join(outputDirectory, `segment-%05d.${extension}`)
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 readFile2, rename, rm as rm2, stat, writeFile } from "fs/promises";
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 readFile(filename)
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 readFile2(source);
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 readFile2(join3(directory, "manifest.json"), "utf8")
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 writeFile(destination, data);
374
+ await writeFile2(destination, data);
279
375
  }
280
- await writeFile(
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 writeFile2(join4(temporaryDirectory, "master.m3u8"), master, "utf8");
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 `import.meta.env.BASE_URL + ${JSON.stringify(path)}`;
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.0.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
- "ffmpeg-static": "^5.3.0"
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
  }