diff --git a/README.md b/README.md index 89ba2d1..83cab17 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,16 @@ yarn add hls2mp4 import Hls2Mp4 from "hls2mp4"; const hls2mp4 = new Hls2Mp4({ - log: true + /** + * @type {number} + * max retry times while request data failed + */ + maxRetry = 3, + /** + * @type {number} + * the concurrency for download ts + */ + tsDownloadConcurrency = 10 }, (type, progress) => { // type = 0 => load FFmpeg // type = 1 => parse m3u8 diff --git a/dist/README.md b/dist/README.md index 89ba2d1..83cab17 100644 --- a/dist/README.md +++ b/dist/README.md @@ -30,7 +30,16 @@ yarn add hls2mp4 import Hls2Mp4 from "hls2mp4"; const hls2mp4 = new Hls2Mp4({ - log: true + /** + * @type {number} + * max retry times while request data failed + */ + maxRetry = 3, + /** + * @type {number} + * the concurrency for download ts + */ + tsDownloadConcurrency = 10 }, (type, progress) => { // type = 0 => load FFmpeg // type = 1 => parse m3u8 diff --git a/dist/hls2mp4.cjs b/dist/hls2mp4.cjs index 79d0a20..c1b902a 100644 --- a/dist/hls2mp4.cjs +++ b/dist/hls2mp4.cjs @@ -1,6 +1,7 @@ 'use strict'; -var FFmpeg = require('@ffmpeg/ffmpeg'); +var ffmpeg = require('@ffmpeg/ffmpeg'); +var util = require('@ffmpeg/util'); /****************************************************************************** Copyright (c) Microsoft Corporation. @@ -19,18 +20,6 @@ PERFORMANCE OF THIS SOFTWARE. /* global Reflect, Promise */ -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -884,7 +873,6 @@ var aesJs = {exports: {}}; var aesJsExports = aesJs.exports; var aesjs = /*@__PURE__*/getDefaultExportFromCjs(aesJsExports); -var createFFmpeg = FFmpeg.createFFmpeg, fetchFile = FFmpeg.fetchFile; var TaskType; (function (TaskType) { TaskType[TaskType["loadFFmeg"] = 0] = "loadFFmeg"; @@ -903,11 +891,11 @@ function parseUrl(url, path) { } var Hls2Mp4 = /** @class */ (function () { function Hls2Mp4(_a, onProgress) { - var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c, options = __rest(_a, ["maxRetry", "tsDownloadConcurrency"]); + var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c; this.loadRetryTime = 0; this.totalSegments = 0; this.savedSegments = 0; - this.instance = createFFmpeg(options); + this.ffmpeg = new ffmpeg.FFmpeg(); this.maxRetry = maxRetry; this.onProgress = onProgress; this.tsDownloadConcurrency = tsDownloadConcurrency; @@ -952,7 +940,7 @@ var Hls2Mp4 = /** @class */ (function () { case 1: playList = _a.sent(); return [3 /*break*/, 4]; - case 2: return [4 /*yield*/, fetchFile(url).then(function (data) { return aesjs.utils.utf8.fromBytes(data); })]; + case 2: return [4 /*yield*/, util.fetchFile(url).then(function (data) { return aesjs.utils.utf8.fromBytes(data); })]; case 3: playList = _a.sent(); _a.label = 4; @@ -996,7 +984,7 @@ var Hls2Mp4 = /** @class */ (function () { var _b, done, data, fileName; return __generator(this, function (_c) { switch (_c.label) { - case 0: return [4 /*yield*/, this.loopLoadFile(function () { return fetchFile(url); })]; + case 0: return [4 /*yield*/, this.loopLoadFile(function () { return util.fetchFile(url); })]; case 1: _b = _c.sent(), done = _b.done, data = _b.data; if (done) { @@ -1023,7 +1011,7 @@ var Hls2Mp4 = /** @class */ (function () { case 1: tsData = _c.sent(); buffer = key ? this.aesDecrypt(tsData, key, iv) : this.transformBuffer(tsData); - this.instance.FS('writeFile', name, buffer); + this.ffmpeg.writeFile(name, buffer); this.savedSegments += 1; (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.downloadTs, this.savedSegments / this.totalSegments); return [2 /*return*/, { @@ -1163,7 +1151,7 @@ var Hls2Mp4 = /** @class */ (function () { case 14: content = content.replace(keyTagMatchRegExp, ''); m3u8 = 'temp.m3u8'; - this.instance.FS('writeFile', m3u8, content); + this.ffmpeg.writeFile(m3u8, content); return [2 /*return*/, m3u8]; } }); @@ -1202,20 +1190,31 @@ var Hls2Mp4 = /** @class */ (function () { Hls2Mp4.prototype.loadFFmpeg = function () { var _a, _b; return __awaiter(this, void 0, void 0, function () { - var done; - var _this = this; + var baseUrl, coreURL, wasmURL, loaded; return __generator(this, function (_c) { switch (_c.label) { case 0: (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.loadFFmeg, 0); - return [4 /*yield*/, this.loopLoadFile(function () { return _this.instance.load(); })]; + baseUrl = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd'; + return [4 /*yield*/, util.toBlobURL("".concat(baseUrl, "/ffmpeg-core.js"), 'text/javascript')]; case 1: - done = (_c.sent()).done; - if (done) { - (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, done ? 1 : -1); + coreURL = _c.sent(); + return [4 /*yield*/, util.toBlobURL("".concat(baseUrl, "/ffmpeg-core.wasm"), 'application/wasm') + // workerURL = workerURL ?? await toBlobURL(`${baseUrl}/ffmpeg-core.worker.js`, 'text/javascript') + ]; + case 2: + wasmURL = _c.sent(); + return [4 /*yield*/, this.ffmpeg.load({ + coreURL: coreURL, + wasmURL: wasmURL + })]; + case 3: + loaded = _c.sent(); + if (loaded) { + (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, 1); } else { - throw new Error('FFmpeg load failed'); + return [2 /*return*/, this.loadFFmpeg()]; } return [2 /*return*/]; } @@ -1235,13 +1234,15 @@ var Hls2Mp4 = /** @class */ (function () { case 2: m3u8 = _c.sent(); (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.mergeTs, 0); - return [4 /*yield*/, this.instance.run('-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug')]; + return [4 /*yield*/, this.ffmpeg.exec(['-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug'])]; case 3: _c.sent(); - data = this.instance.FS('readFile', 'temp.mp4'); - this.instance.exit(); + return [4 /*yield*/, this.ffmpeg.readFile('temp.mp4')]; + case 4: + data = _c.sent(); + this.ffmpeg.terminate(); (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.mergeTs, 1); - return [2 /*return*/, data.buffer]; + return [2 /*return*/, data]; } }); }); @@ -1254,7 +1255,7 @@ var Hls2Mp4 = /** @class */ (function () { anchor.click(); setTimeout(function () { return URL.revokeObjectURL(objectUrl); }, 100); }; - Hls2Mp4.version = '1.1.9'; + Hls2Mp4.version = '1.2.0'; Hls2Mp4.TaskType = TaskType; return Hls2Mp4; }()); diff --git a/dist/hls2mp4.js b/dist/hls2mp4.js index 24f03a8..4c6af8a 100644 --- a/dist/hls2mp4.js +++ b/dist/hls2mp4.js @@ -1,4 +1,4 @@ -var Hls2Mp4 = (function (FFmpeg) { +var Hls2Mp4 = (function (ffmpeg, util) { 'use strict'; /****************************************************************************** @@ -18,18 +18,6 @@ var Hls2Mp4 = (function (FFmpeg) { /* global Reflect, Promise */ - function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; - } - function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -883,7 +871,6 @@ var Hls2Mp4 = (function (FFmpeg) { var aesJsExports = aesJs.exports; var aesjs = /*@__PURE__*/getDefaultExportFromCjs(aesJsExports); - var createFFmpeg = FFmpeg.createFFmpeg, fetchFile = FFmpeg.fetchFile; var TaskType; (function (TaskType) { TaskType[TaskType["loadFFmeg"] = 0] = "loadFFmeg"; @@ -902,11 +889,11 @@ var Hls2Mp4 = (function (FFmpeg) { } var Hls2Mp4 = /** @class */ (function () { function Hls2Mp4(_a, onProgress) { - var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c, options = __rest(_a, ["maxRetry", "tsDownloadConcurrency"]); + var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c; this.loadRetryTime = 0; this.totalSegments = 0; this.savedSegments = 0; - this.instance = createFFmpeg(options); + this.ffmpeg = new ffmpeg.FFmpeg(); this.maxRetry = maxRetry; this.onProgress = onProgress; this.tsDownloadConcurrency = tsDownloadConcurrency; @@ -951,7 +938,7 @@ var Hls2Mp4 = (function (FFmpeg) { case 1: playList = _a.sent(); return [3 /*break*/, 4]; - case 2: return [4 /*yield*/, fetchFile(url).then(function (data) { return aesjs.utils.utf8.fromBytes(data); })]; + case 2: return [4 /*yield*/, util.fetchFile(url).then(function (data) { return aesjs.utils.utf8.fromBytes(data); })]; case 3: playList = _a.sent(); _a.label = 4; @@ -995,7 +982,7 @@ var Hls2Mp4 = (function (FFmpeg) { var _b, done, data, fileName; return __generator(this, function (_c) { switch (_c.label) { - case 0: return [4 /*yield*/, this.loopLoadFile(function () { return fetchFile(url); })]; + case 0: return [4 /*yield*/, this.loopLoadFile(function () { return util.fetchFile(url); })]; case 1: _b = _c.sent(), done = _b.done, data = _b.data; if (done) { @@ -1022,7 +1009,7 @@ var Hls2Mp4 = (function (FFmpeg) { case 1: tsData = _c.sent(); buffer = key ? this.aesDecrypt(tsData, key, iv) : this.transformBuffer(tsData); - this.instance.FS('writeFile', name, buffer); + this.ffmpeg.writeFile(name, buffer); this.savedSegments += 1; (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.downloadTs, this.savedSegments / this.totalSegments); return [2 /*return*/, { @@ -1162,7 +1149,7 @@ var Hls2Mp4 = (function (FFmpeg) { case 14: content = content.replace(keyTagMatchRegExp, ''); m3u8 = 'temp.m3u8'; - this.instance.FS('writeFile', m3u8, content); + this.ffmpeg.writeFile(m3u8, content); return [2 /*return*/, m3u8]; } }); @@ -1201,20 +1188,31 @@ var Hls2Mp4 = (function (FFmpeg) { Hls2Mp4.prototype.loadFFmpeg = function () { var _a, _b; return __awaiter(this, void 0, void 0, function () { - var done; - var _this = this; + var baseUrl, coreURL, wasmURL, loaded; return __generator(this, function (_c) { switch (_c.label) { case 0: (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.loadFFmeg, 0); - return [4 /*yield*/, this.loopLoadFile(function () { return _this.instance.load(); })]; + baseUrl = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd'; + return [4 /*yield*/, util.toBlobURL("".concat(baseUrl, "/ffmpeg-core.js"), 'text/javascript')]; case 1: - done = (_c.sent()).done; - if (done) { - (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, done ? 1 : -1); + coreURL = _c.sent(); + return [4 /*yield*/, util.toBlobURL("".concat(baseUrl, "/ffmpeg-core.wasm"), 'application/wasm') + // workerURL = workerURL ?? await toBlobURL(`${baseUrl}/ffmpeg-core.worker.js`, 'text/javascript') + ]; + case 2: + wasmURL = _c.sent(); + return [4 /*yield*/, this.ffmpeg.load({ + coreURL: coreURL, + wasmURL: wasmURL + })]; + case 3: + loaded = _c.sent(); + if (loaded) { + (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, 1); } else { - throw new Error('FFmpeg load failed'); + return [2 /*return*/, this.loadFFmpeg()]; } return [2 /*return*/]; } @@ -1234,13 +1232,15 @@ var Hls2Mp4 = (function (FFmpeg) { case 2: m3u8 = _c.sent(); (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.mergeTs, 0); - return [4 /*yield*/, this.instance.run('-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug')]; + return [4 /*yield*/, this.ffmpeg.exec(['-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug'])]; case 3: _c.sent(); - data = this.instance.FS('readFile', 'temp.mp4'); - this.instance.exit(); + return [4 /*yield*/, this.ffmpeg.readFile('temp.mp4')]; + case 4: + data = _c.sent(); + this.ffmpeg.terminate(); (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.mergeTs, 1); - return [2 /*return*/, data.buffer]; + return [2 /*return*/, data]; } }); }); @@ -1253,11 +1253,11 @@ var Hls2Mp4 = (function (FFmpeg) { anchor.click(); setTimeout(function () { return URL.revokeObjectURL(objectUrl); }, 100); }; - Hls2Mp4.version = '1.1.9'; + Hls2Mp4.version = '1.2.0'; Hls2Mp4.TaskType = TaskType; return Hls2Mp4; }()); return Hls2Mp4; -})(FFmpeg); +})(FFmpeg, util); diff --git a/dist/hls2mp4.umd.js b/dist/hls2mp4.umd.js index 3adf0b7..d40ad83 100644 --- a/dist/hls2mp4.umd.js +++ b/dist/hls2mp4.umd.js @@ -1,8 +1,8 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@ffmpeg/ffmpeg')) : - typeof define === 'function' && define.amd ? define(['@ffmpeg/ffmpeg'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Hls2Mp4 = factory(global.FFmpeg)); -})(this, (function (FFmpeg) { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@ffmpeg/ffmpeg'), require('@ffmpeg/util')) : + typeof define === 'function' && define.amd ? define(['@ffmpeg/ffmpeg', '@ffmpeg/util'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Hls2Mp4 = factory(global.FFmpeg, global.util)); +})(this, (function (ffmpeg, util) { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. @@ -21,18 +21,6 @@ /* global Reflect, Promise */ - function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; - } - function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -886,7 +874,6 @@ var aesJsExports = aesJs.exports; var aesjs = /*@__PURE__*/getDefaultExportFromCjs(aesJsExports); - var createFFmpeg = FFmpeg.createFFmpeg, fetchFile = FFmpeg.fetchFile; var TaskType; (function (TaskType) { TaskType[TaskType["loadFFmeg"] = 0] = "loadFFmeg"; @@ -905,11 +892,11 @@ } var Hls2Mp4 = /** @class */ (function () { function Hls2Mp4(_a, onProgress) { - var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c, options = __rest(_a, ["maxRetry", "tsDownloadConcurrency"]); + var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c; this.loadRetryTime = 0; this.totalSegments = 0; this.savedSegments = 0; - this.instance = createFFmpeg(options); + this.ffmpeg = new ffmpeg.FFmpeg(); this.maxRetry = maxRetry; this.onProgress = onProgress; this.tsDownloadConcurrency = tsDownloadConcurrency; @@ -954,7 +941,7 @@ case 1: playList = _a.sent(); return [3 /*break*/, 4]; - case 2: return [4 /*yield*/, fetchFile(url).then(function (data) { return aesjs.utils.utf8.fromBytes(data); })]; + case 2: return [4 /*yield*/, util.fetchFile(url).then(function (data) { return aesjs.utils.utf8.fromBytes(data); })]; case 3: playList = _a.sent(); _a.label = 4; @@ -998,7 +985,7 @@ var _b, done, data, fileName; return __generator(this, function (_c) { switch (_c.label) { - case 0: return [4 /*yield*/, this.loopLoadFile(function () { return fetchFile(url); })]; + case 0: return [4 /*yield*/, this.loopLoadFile(function () { return util.fetchFile(url); })]; case 1: _b = _c.sent(), done = _b.done, data = _b.data; if (done) { @@ -1025,7 +1012,7 @@ case 1: tsData = _c.sent(); buffer = key ? this.aesDecrypt(tsData, key, iv) : this.transformBuffer(tsData); - this.instance.FS('writeFile', name, buffer); + this.ffmpeg.writeFile(name, buffer); this.savedSegments += 1; (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.downloadTs, this.savedSegments / this.totalSegments); return [2 /*return*/, { @@ -1165,7 +1152,7 @@ case 14: content = content.replace(keyTagMatchRegExp, ''); m3u8 = 'temp.m3u8'; - this.instance.FS('writeFile', m3u8, content); + this.ffmpeg.writeFile(m3u8, content); return [2 /*return*/, m3u8]; } }); @@ -1204,20 +1191,31 @@ Hls2Mp4.prototype.loadFFmpeg = function () { var _a, _b; return __awaiter(this, void 0, void 0, function () { - var done; - var _this = this; + var baseUrl, coreURL, wasmURL, loaded; return __generator(this, function (_c) { switch (_c.label) { case 0: (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.loadFFmeg, 0); - return [4 /*yield*/, this.loopLoadFile(function () { return _this.instance.load(); })]; + baseUrl = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd'; + return [4 /*yield*/, util.toBlobURL("".concat(baseUrl, "/ffmpeg-core.js"), 'text/javascript')]; case 1: - done = (_c.sent()).done; - if (done) { - (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, done ? 1 : -1); + coreURL = _c.sent(); + return [4 /*yield*/, util.toBlobURL("".concat(baseUrl, "/ffmpeg-core.wasm"), 'application/wasm') + // workerURL = workerURL ?? await toBlobURL(`${baseUrl}/ffmpeg-core.worker.js`, 'text/javascript') + ]; + case 2: + wasmURL = _c.sent(); + return [4 /*yield*/, this.ffmpeg.load({ + coreURL: coreURL, + wasmURL: wasmURL + })]; + case 3: + loaded = _c.sent(); + if (loaded) { + (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, 1); } else { - throw new Error('FFmpeg load failed'); + return [2 /*return*/, this.loadFFmpeg()]; } return [2 /*return*/]; } @@ -1237,13 +1235,15 @@ case 2: m3u8 = _c.sent(); (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.mergeTs, 0); - return [4 /*yield*/, this.instance.run('-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug')]; + return [4 /*yield*/, this.ffmpeg.exec(['-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug'])]; case 3: _c.sent(); - data = this.instance.FS('readFile', 'temp.mp4'); - this.instance.exit(); + return [4 /*yield*/, this.ffmpeg.readFile('temp.mp4')]; + case 4: + data = _c.sent(); + this.ffmpeg.terminate(); (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.mergeTs, 1); - return [2 /*return*/, data.buffer]; + return [2 /*return*/, data]; } }); }); @@ -1256,7 +1256,7 @@ anchor.click(); setTimeout(function () { return URL.revokeObjectURL(objectUrl); }, 100); }; - Hls2Mp4.version = '1.1.9'; + Hls2Mp4.version = '1.2.0'; Hls2Mp4.TaskType = TaskType; return Hls2Mp4; }()); diff --git a/dist/index.d.ts b/dist/index.d.ts index 8054633..edc8d62 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,4 +1,3 @@ -import { type CreateFFmpegOptions } from '@ffmpeg/ffmpeg'; declare enum TaskType { loadFFmeg = 0, parseM3u8 = 1, @@ -23,7 +22,7 @@ export interface M3u8Parsed { content: string; } declare class Hls2Mp4 { - private instance; + private ffmpeg; private maxRetry; private loadRetryTime; private onProgress?; @@ -32,7 +31,7 @@ declare class Hls2Mp4 { private savedSegments; static version: string; static TaskType: typeof TaskType; - constructor({ maxRetry, tsDownloadConcurrency, ...options }: CreateFFmpegOptions & Hls2Mp4Options, onProgress?: ProgressCallback); + constructor({ maxRetry, tsDownloadConcurrency }: Hls2Mp4Options, onProgress?: ProgressCallback); private transformBuffer; private hexToUint8Array; private aesDecrypt; @@ -43,7 +42,7 @@ declare class Hls2Mp4 { private downloadM3u8; private loopLoadFile; private loadFFmpeg; - download(url: string): Promise; - saveToFile(buffer: ArrayBufferLike, filename: string): void; + download(url: string): Promise; + saveToFile(buffer: ArrayBuffer | string, filename: string): void; } export default Hls2Mp4; diff --git a/dist/index.js b/dist/index.js index cc3b3e9..ec321a5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,4 +1,5 @@ -import FFmpeg from '@ffmpeg/ffmpeg'; +import { FFmpeg } from '@ffmpeg/ffmpeg'; +import { fetchFile, toBlobURL } from '@ffmpeg/util'; /****************************************************************************** Copyright (c) Microsoft Corporation. @@ -17,18 +18,6 @@ PERFORMANCE OF THIS SOFTWARE. /* global Reflect, Promise */ -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -882,7 +871,6 @@ var aesJs = {exports: {}}; var aesJsExports = aesJs.exports; var aesjs = /*@__PURE__*/getDefaultExportFromCjs(aesJsExports); -var createFFmpeg = FFmpeg.createFFmpeg, fetchFile = FFmpeg.fetchFile; var TaskType; (function (TaskType) { TaskType[TaskType["loadFFmeg"] = 0] = "loadFFmeg"; @@ -901,11 +889,11 @@ function parseUrl(url, path) { } var Hls2Mp4 = /** @class */ (function () { function Hls2Mp4(_a, onProgress) { - var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c, options = __rest(_a, ["maxRetry", "tsDownloadConcurrency"]); + var _b = _a.maxRetry, maxRetry = _b === void 0 ? 3 : _b, _c = _a.tsDownloadConcurrency, tsDownloadConcurrency = _c === void 0 ? 10 : _c; this.loadRetryTime = 0; this.totalSegments = 0; this.savedSegments = 0; - this.instance = createFFmpeg(options); + this.ffmpeg = new FFmpeg(); this.maxRetry = maxRetry; this.onProgress = onProgress; this.tsDownloadConcurrency = tsDownloadConcurrency; @@ -1021,7 +1009,7 @@ var Hls2Mp4 = /** @class */ (function () { case 1: tsData = _c.sent(); buffer = key ? this.aesDecrypt(tsData, key, iv) : this.transformBuffer(tsData); - this.instance.FS('writeFile', name, buffer); + this.ffmpeg.writeFile(name, buffer); this.savedSegments += 1; (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.downloadTs, this.savedSegments / this.totalSegments); return [2 /*return*/, { @@ -1161,7 +1149,7 @@ var Hls2Mp4 = /** @class */ (function () { case 14: content = content.replace(keyTagMatchRegExp, ''); m3u8 = 'temp.m3u8'; - this.instance.FS('writeFile', m3u8, content); + this.ffmpeg.writeFile(m3u8, content); return [2 /*return*/, m3u8]; } }); @@ -1200,20 +1188,31 @@ var Hls2Mp4 = /** @class */ (function () { Hls2Mp4.prototype.loadFFmpeg = function () { var _a, _b; return __awaiter(this, void 0, void 0, function () { - var done; - var _this = this; + var baseUrl, coreURL, wasmURL, loaded; return __generator(this, function (_c) { switch (_c.label) { case 0: (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.loadFFmeg, 0); - return [4 /*yield*/, this.loopLoadFile(function () { return _this.instance.load(); })]; + baseUrl = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd'; + return [4 /*yield*/, toBlobURL("".concat(baseUrl, "/ffmpeg-core.js"), 'text/javascript')]; case 1: - done = (_c.sent()).done; - if (done) { - (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, done ? 1 : -1); + coreURL = _c.sent(); + return [4 /*yield*/, toBlobURL("".concat(baseUrl, "/ffmpeg-core.wasm"), 'application/wasm') + // workerURL = workerURL ?? await toBlobURL(`${baseUrl}/ffmpeg-core.worker.js`, 'text/javascript') + ]; + case 2: + wasmURL = _c.sent(); + return [4 /*yield*/, this.ffmpeg.load({ + coreURL: coreURL, + wasmURL: wasmURL + })]; + case 3: + loaded = _c.sent(); + if (loaded) { + (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.loadFFmeg, 1); } else { - throw new Error('FFmpeg load failed'); + return [2 /*return*/, this.loadFFmpeg()]; } return [2 /*return*/]; } @@ -1233,13 +1232,15 @@ var Hls2Mp4 = /** @class */ (function () { case 2: m3u8 = _c.sent(); (_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, TaskType.mergeTs, 0); - return [4 /*yield*/, this.instance.run('-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug')]; + return [4 /*yield*/, this.ffmpeg.exec(['-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug'])]; case 3: _c.sent(); - data = this.instance.FS('readFile', 'temp.mp4'); - this.instance.exit(); + return [4 /*yield*/, this.ffmpeg.readFile('temp.mp4')]; + case 4: + data = _c.sent(); + this.ffmpeg.terminate(); (_b = this.onProgress) === null || _b === void 0 ? void 0 : _b.call(this, TaskType.mergeTs, 1); - return [2 /*return*/, data.buffer]; + return [2 /*return*/, data]; } }); }); @@ -1252,7 +1253,7 @@ var Hls2Mp4 = /** @class */ (function () { anchor.click(); setTimeout(function () { return URL.revokeObjectURL(objectUrl); }, 100); }; - Hls2Mp4.version = '1.1.9'; + Hls2Mp4.version = '1.2.0'; Hls2Mp4.TaskType = TaskType; return Hls2Mp4; }()); diff --git a/dist/package.json b/dist/package.json index 14fe5fe..dea37ed 100644 --- a/dist/package.json +++ b/dist/package.json @@ -1,6 +1,6 @@ { "name": "hls2mp4", - "version": "1.1.9", + "version": "1.2.0", "description": "a tool for download hls/m3u8 to mp4", "main": "index.js", "types": "index.d.ts", diff --git a/package.json b/package.json index 9f09eb0..1c30448 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hls2mp4", - "version": "1.1.9", + "version": "1.2.0", "description": "a tool for download hls/m3u8 to mp4", "main": "index.js", "types": "index.d.ts", @@ -19,10 +19,10 @@ "aes-js": "^3.1.2" }, "devDependencies": { - "@ffmpeg/core": "^0.11.0", - "@ffmpeg/ffmpeg": "^0.11.6", - "@rollup/plugin-node-resolve": "^15.0.2", + "@ffmpeg/ffmpeg": "^0.12.6", + "@ffmpeg/util": "^0.12.1", "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-node-resolve": "^15.0.2", "@rollup/plugin-typescript": "^11.1.0", "@types/aes-js": "^3.1.1", "rollup": "^3.21.4", @@ -43,4 +43,4 @@ "last 1 safari version" ] } -} \ No newline at end of file +} diff --git a/rollup.config.mjs b/rollup.config.mjs index 880f86e..6864666 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -40,7 +40,7 @@ export default { nodeResolve() ], external: [ - '@ffmpeg/core', - '@ffmpeg/ffmpeg' + '@ffmpeg/ffmpeg', + '@ffmpeg/util' ] } diff --git a/src/index.ts b/src/index.ts index 21d7589..e734479 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ -import FFmpeg, { type CreateFFmpegOptions, type FFmpeg as FFmpegInstance } from '@ffmpeg/ffmpeg'; -import aesjs, { type ByteSource } from 'aes-js'; - -const { createFFmpeg, fetchFile } = FFmpeg; +import { FFmpeg } from '@ffmpeg/ffmpeg' +import { fetchFile, toBlobURL } from '@ffmpeg/util' +import aesjs, { type ByteSource } from 'aes-js' enum TaskType { loadFFmeg = 0, @@ -61,18 +60,18 @@ function parseUrl(url: string, path: string) { class Hls2Mp4 { - private instance: FFmpegInstance; + private ffmpeg: FFmpeg; private maxRetry: number; private loadRetryTime = 0; private onProgress?: ProgressCallback; private tsDownloadConcurrency: number; private totalSegments = 0; private savedSegments = 0; - public static version = '1.1.9'; + public static version = '1.2.0'; public static TaskType = TaskType; - constructor({ maxRetry = 3, tsDownloadConcurrency = 10, ...options }: CreateFFmpegOptions & Hls2Mp4Options, onProgress?: ProgressCallback) { - this.instance = createFFmpeg(options); + constructor({ maxRetry = 3, tsDownloadConcurrency = 10 }: Hls2Mp4Options, onProgress?: ProgressCallback) { + this.ffmpeg = new FFmpeg(); this.maxRetry = maxRetry; this.onProgress = onProgress; this.tsDownloadConcurrency = tsDownloadConcurrency; @@ -165,7 +164,7 @@ class Hls2Mp4 { segs.map(async ({ name, url, source }) => { const tsData = await this.downloadFile(url) const buffer = key ? this.aesDecrypt(tsData!, key, iv) : this.transformBuffer(tsData!) - this.instance.FS('writeFile', name, buffer) + this.ffmpeg.writeFile(name, buffer) this.savedSegments += 1 this.onProgress?.(TaskType.downloadTs, this.savedSegments / this.totalSegments) return { @@ -257,7 +256,7 @@ class Hls2Mp4 { } content = content.replace(keyTagMatchRegExp, '') const m3u8 = 'temp.m3u8' - this.instance.FS('writeFile', m3u8, content) + this.ffmpeg.writeFile(m3u8, content) return m3u8 } @@ -282,16 +281,21 @@ class Hls2Mp4 { } } - private async loadFFmpeg() { + private async loadFFmpeg(): Promise { this.onProgress?.(TaskType.loadFFmeg, 0) - const { done } = await this.loopLoadFile( - () => this.instance.load() - ) - if (done) { - this.onProgress?.(TaskType.loadFFmeg, done ? 1 : -1); + const baseUrl = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd' + const coreURL = await toBlobURL(`${baseUrl}/ffmpeg-core.js`, 'text/javascript') + const wasmURL = await toBlobURL(`${baseUrl}/ffmpeg-core.wasm`, 'application/wasm') + // workerURL = workerURL ?? await toBlobURL(`${baseUrl}/ffmpeg-core.worker.js`, 'text/javascript') + const loaded = await this.ffmpeg.load({ + coreURL, + wasmURL + }) + if (loaded) { + this.onProgress?.(TaskType.loadFFmeg, 1) } else { - throw new Error('FFmpeg load failed') + return this.loadFFmpeg() } } @@ -299,14 +303,14 @@ class Hls2Mp4 { await this.loadFFmpeg(); const m3u8 = await this.downloadM3u8(url); this.onProgress?.(TaskType.mergeTs, 0); - await this.instance.run('-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug'); - const data = this.instance.FS('readFile', 'temp.mp4'); - this.instance.exit(); + await this.ffmpeg.exec(['-i', m3u8, '-c', 'copy', 'temp.mp4', '-loglevel', 'debug']); + const data = await this.ffmpeg.readFile('temp.mp4'); + this.ffmpeg.terminate(); this.onProgress?.(TaskType.mergeTs, 1); - return data.buffer; + return data; } - public saveToFile(buffer: ArrayBufferLike, filename: string) { + public saveToFile(buffer: ArrayBuffer | string, filename: string) { const objectUrl = URL.createObjectURL(new Blob([buffer], { type: 'video/mp4' })); const anchor = document.createElement('a'); anchor.href = objectUrl; diff --git a/yarn.lock b/yarn.lock index 15a8832..85cb53f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,20 +2,22 @@ # yarn lockfile v1 -"@ffmpeg/core@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@ffmpeg/core/-/core-0.11.0.tgz#fc24fe5af587f83cd2575d0e45de455d8eed9ba9" - integrity sha512-9Tt/+2PMpkGPXUK8n6He9G8Y+qR6qmCPSCw9iEKZxHHOvJ9BE/r0Fccj+YgDZTlyu6rXxc9x6EqCaFBIt7qzjA== - -"@ffmpeg/ffmpeg@^0.11.6": - version "0.11.6" - resolved "https://registry.yarnpkg.com/@ffmpeg/ffmpeg/-/ffmpeg-0.11.6.tgz#9cbe42dfb5132fca74ae5570ebeee2341f8c6e51" - integrity sha512-uN8J8KDjADEavPhNva6tYO9Fj0lWs9z82swF3YXnTxWMBoFLGq3LZ6FLlIldRKEzhOBKnkVfA8UnFJuvGvNxcA== +"@ffmpeg/ffmpeg@^0.12.6": + version "0.12.6" + resolved "https://registry.yarnpkg.com/@ffmpeg/ffmpeg/-/ffmpeg-0.12.6.tgz#34dd5959b9446837240a32acb6979474e8935500" + integrity sha512-4CuXDaqrCga5qBwVtiDDR45y65OGPYZd7VzwGCGz3QLdrQH7xaLYEjU19XL4DTCL0WnTSH8752b8Atyb1SiiLw== dependencies: - is-url "^1.2.4" - node-fetch "^2.6.1" - regenerator-runtime "^0.13.7" - resolve-url "^0.2.1" + "@ffmpeg/types" "^0.12.0" + +"@ffmpeg/types@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@ffmpeg/types/-/types-0.12.1.tgz#659e4eb51f3e01e145a6fe99e5b8e12a373aa59a" + integrity sha512-n+v9yvxaBPi1Z/1wy2PvqMHvED3qV7LXBWnBmMBAAzybbs4KWp6wrhCTMxITORMHXpPZEEupsBH1iEoLuzYSUw== + +"@ffmpeg/util@^0.12.1": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@ffmpeg/util/-/util-0.12.1.tgz#98afa20d7b4c0821eebdb205ddcfa5d07b0a4f53" + integrity sha512-10jjfAKWaDyb8+nAkijcsi9wgz/y26LOc1NKJradNMyCIl6usQcBbhkjX5qhALrSBcOy6TOeksunTYa+a03qNQ== "@jridgewell/sourcemap-codec@^1.4.13": version "1.4.15" @@ -187,11 +189,6 @@ is-reference@1.2.1: dependencies: "@types/estree" "*" -is-url@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - magic-string@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" @@ -206,13 +203,6 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -node-fetch@^2.6.1: - version "2.6.11" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" - integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== - dependencies: - whatwg-url "^5.0.0" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -230,16 +220,6 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -regenerator-runtime@^0.13.7: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - resolve@^1.22.1: version "1.22.2" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" @@ -261,11 +241,6 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - tslib@^2.0.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" @@ -276,19 +251,6 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"