From 53c5bdd28ea345d6c4799130a5eced9a4d8fdaaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E4=B9=BE?= Date: Fri, 30 Jan 2026 11:24:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 25 + BUILD_TROUBLESHOOTING.md | 118 ++++ README.md | 177 +++++- USAGE.md | 176 ++++++ index.html | 66 ++ install-deps.js | 26 + package-lock.json | 1221 +++++++++++++++++++++++++++++++++++++ package.json | 22 + public/vite.svg | 1 + src/flv-player.js | 114 ++++ src/m3u8-player.js | 670 ++++++++++++++++++++ src/main.js | 29 + src/style.css | 107 ++++ test-build.html | 92 +++ vite.config.js | 15 + vite.lib.config.js | 31 + vite.lib.simple.config.js | 29 + 17 files changed, 2917 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 BUILD_TROUBLESHOOTING.md create mode 100644 USAGE.md create mode 100644 index.html create mode 100644 install-deps.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/vite.svg create mode 100644 src/flv-player.js create mode 100644 src/m3u8-player.js create mode 100644 src/main.js create mode 100644 src/style.css create mode 100644 test-build.html create mode 100644 vite.config.js create mode 100644 vite.lib.config.js create mode 100644 vite.lib.simple.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d600b6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + diff --git a/BUILD_TROUBLESHOOTING.md b/BUILD_TROUBLESHOOTING.md new file mode 100644 index 0000000..6f0009e --- /dev/null +++ b/BUILD_TROUBLESHOOTING.md @@ -0,0 +1,118 @@ +# ZAPlayer 构建故障排除指南 + +## 常见问题解决 + +### 1. Terser 依赖错误 + +**错误信息:** +``` +error during build: [vite:terser] terser not found. Since Vite v3, terser has become an optional dependency. You need to install it. +``` + +**解决方案:** + +#### 方案A:使用简化构建(推荐) +```bash +# 使用不需要terser的简化配置 +npm run build:simple +``` + +#### 方案B:安装terser依赖 +```bash +# 安装terser依赖 +npm run install:terser + +# 然后正常构建 +npm run build:min +``` + +#### 方案C:手动安装terser +```bash +npm install --save-dev terser +npm run build:min +``` + +### 2. 构建配置选择 + +我们提供了多个构建配置: + +| 配置文件 | 用途 | 特点 | +|----------|------|------| +| `vite.lib.simple.config.js` | 简化构建 | 使用Vite内置压缩,无需额外依赖 | +| `vite.lib.config.js` | 高级构建 | 使用terser,压缩效果更好 | + +### 3. 构建输出文件 + +成功构建后,会在 `dist/` 目录下生成: +- `za-player.min.js` - UMD格式,浏览器使用 +- `za-player.es.js` - ES模块格式,现代项目使用 + +### 4. 验证构建结果 + +构建完成后,可以通过以下方式验证: + +#### 检查文件是否存在 +```bash +ls -la dist/ +# Windows: dir dist\ +``` + +#### 检查文件大小 +```bash +# 查看文件大小(应该有一定压缩) +wc -c dist/za-player.min.js +wc -c dist/za-player.es.js +``` + +#### 测试功能 +打开 `test-build.html` 文件,在浏览器中测试两个构建文件是否正常工作。 + +### 5. 快速开始(推荐流程) + +```bash +# 1. 安装依赖 +npm install + +# 2. 使用简化构建(避免terser问题) +npm run build:simple + +# 3. 验证构建结果 +ls dist/ +# 应该看到 za-player.min.js 和 za-player.es.js + +# 4. 测试功能 +# 在浏览器中打开 test-build.html +``` + +### 6. 如果仍然有问题 + +1. **清除node_modules并重新安装:** + ```bash + rm -rf node_modules package-lock.json + npm install + ``` + +2. **检查Node.js版本:** + ```bash + node --version + # 建议使用 Node.js 16+ + ``` + +3. **检查Vite版本:** + ```bash + npm list vite + ``` + +4. **查看详细错误信息:** + ```bash + npm run build:simple -- --debug + ``` + +### 7. 获取帮助 + +如果以上方法都不能解决问题,请提供以下信息: +1. 完整的错误信息 +2. Node.js版本 (`node --version`) +3. npm版本 (`npm --version`) +4. 操作系统信息 +5. 使用的构建命令 \ No newline at end of file diff --git a/README.md b/README.md index cae1c3a..b04056d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,176 @@ -# ZAPlayer +# ZAPlayer 使用说明 -ZAPlayer是一个支持HLS(M3U8)和FLV格式的视频播放器类库。 \ No newline at end of file +ZAPlayer是一个支持HLS(M3U8)和FLV格式的视频播放器类库。 + +## 快速开始 + +### 方法1:直接引用构建好的类库文件 + +1. 首先构建类库文件: +```bash +npm run build:min +``` + +2. 构建完成后,会在 `dist/` 目录下生成两个文件: + - `za-player.min.js` - 压缩后的UMD格式,适合浏览器直接使用 + - `za-player.es.js` - ES模块格式,适合现代JavaScript项目 + +3. 在HTML文件中引用(浏览器使用): +```html + + + + ZAPlayer 示例 + + + +
+ + + + + + + +``` + +### 方法2:使用ES模块 + +```html + + + + ZAPlayer ES模块示例 + + +
+ + + + +``` + +### 方法3:在Node.js或现代前端项目中使用ES模块 + +```javascript +// 使用ES模块导入 +import ZAPlayer from './dist/za-player.es.js'; + +// 创建播放器 +const player = new ZAPlayer('#container', { + type: 'flv', + src: 'video.flv' +}); + +player.player.play(); +``` + +### 方法4:动态加载(浏览器版本) + +```javascript +// 动态加载ZAPlayer类库 +function loadZAPlayer(callback) { + const script = document.createElement('script'); + script.src = 'dist/za-player.min.js'; // 使用压缩版本 + script.onload = callback; + document.head.appendChild(script); +} + +// 使用动态加载的类库 +loadZAPlayer(function() { + const player = new ZAPlayer('#videoContainer', { + type: 'flv', + src: 'your-video-url.flv' + }); + + player.player.play(); +}); +``` + +## API 说明 + +### 构造函数 +```javascript +new ZAPlayer(container, options) +``` + +**参数:** +- `container` (string|Element): 视频容器的选择器字符串或DOM元素 +- `options` (object): 配置选项 + - `type` (string): 视频类型,'hls' 或 'flv',默认为 'hls' + - `src` (string, 可选): 视频地址,可以在创建后通过load方法加载 + +**返回值:** +- 返回一个对象,包含player属性,player对象有以下方法: + - `play()`: 播放视频 + - `pause()`: 暂停视频 + - `stop()`: 停止视频 + - `load(url)`: 加载新的视频地址 + +## 示例视频地址 + +### FLV格式 +```javascript +// 示例FLV地址 +player.player.load('http://192.168.1.200:10037/live/PUGE0hFBYluVe_01.flv'); +``` + +### HLS(M3U8)格式 +```javascript +// 示例HLS地址 +player.player.load('http://192.168.1.201:9080/DS-2CD5026FWD20180811AACH220809006/0000000B/hls.m3u8'); +``` + +## 浏览器兼容性 + +- 支持现代浏览器的Media Source Extensions (MSE) +- 支持WebCodecs API(用于高级解码) +- 支持Fetch API + +## 注意事项 + +1. 确保视频地址支持跨域访问(CORS) +2. 对于直播流,确保服务器支持持续的数据传输 +3. 某些浏览器可能需要HTTPS协议才能正常工作 + +## 构建说明 + +```bash +# 安装依赖 +npm install + +# 开发模式 +npm run dev + +# 构建演示页面 +npm run build + +# 构建类库文件(同时生成两个文件) +npm run build:min +``` + +构建完成后,类库文件会在`dist/`目录下生成: +- `za-player.min.js` - 压缩后的UMD格式类库文件,适合浏览器直接使用 +- `za-player.es.js` - ES模块格式,适合现代JavaScript项目和打包工具 \ No newline at end of file diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..b04056d --- /dev/null +++ b/USAGE.md @@ -0,0 +1,176 @@ +# ZAPlayer 使用说明 + +ZAPlayer是一个支持HLS(M3U8)和FLV格式的视频播放器类库。 + +## 快速开始 + +### 方法1:直接引用构建好的类库文件 + +1. 首先构建类库文件: +```bash +npm run build:min +``` + +2. 构建完成后,会在 `dist/` 目录下生成两个文件: + - `za-player.min.js` - 压缩后的UMD格式,适合浏览器直接使用 + - `za-player.es.js` - ES模块格式,适合现代JavaScript项目 + +3. 在HTML文件中引用(浏览器使用): +```html + + + + ZAPlayer 示例 + + + +
+ + + + + + + +``` + +### 方法2:使用ES模块 + +```html + + + + ZAPlayer ES模块示例 + + +
+ + + + +``` + +### 方法3:在Node.js或现代前端项目中使用ES模块 + +```javascript +// 使用ES模块导入 +import ZAPlayer from './dist/za-player.es.js'; + +// 创建播放器 +const player = new ZAPlayer('#container', { + type: 'flv', + src: 'video.flv' +}); + +player.player.play(); +``` + +### 方法4:动态加载(浏览器版本) + +```javascript +// 动态加载ZAPlayer类库 +function loadZAPlayer(callback) { + const script = document.createElement('script'); + script.src = 'dist/za-player.min.js'; // 使用压缩版本 + script.onload = callback; + document.head.appendChild(script); +} + +// 使用动态加载的类库 +loadZAPlayer(function() { + const player = new ZAPlayer('#videoContainer', { + type: 'flv', + src: 'your-video-url.flv' + }); + + player.player.play(); +}); +``` + +## API 说明 + +### 构造函数 +```javascript +new ZAPlayer(container, options) +``` + +**参数:** +- `container` (string|Element): 视频容器的选择器字符串或DOM元素 +- `options` (object): 配置选项 + - `type` (string): 视频类型,'hls' 或 'flv',默认为 'hls' + - `src` (string, 可选): 视频地址,可以在创建后通过load方法加载 + +**返回值:** +- 返回一个对象,包含player属性,player对象有以下方法: + - `play()`: 播放视频 + - `pause()`: 暂停视频 + - `stop()`: 停止视频 + - `load(url)`: 加载新的视频地址 + +## 示例视频地址 + +### FLV格式 +```javascript +// 示例FLV地址 +player.player.load('http://192.168.1.200:10037/live/PUGE0hFBYluVe_01.flv'); +``` + +### HLS(M3U8)格式 +```javascript +// 示例HLS地址 +player.player.load('http://192.168.1.201:9080/DS-2CD5026FWD20180811AACH220809006/0000000B/hls.m3u8'); +``` + +## 浏览器兼容性 + +- 支持现代浏览器的Media Source Extensions (MSE) +- 支持WebCodecs API(用于高级解码) +- 支持Fetch API + +## 注意事项 + +1. 确保视频地址支持跨域访问(CORS) +2. 对于直播流,确保服务器支持持续的数据传输 +3. 某些浏览器可能需要HTTPS协议才能正常工作 + +## 构建说明 + +```bash +# 安装依赖 +npm install + +# 开发模式 +npm run dev + +# 构建演示页面 +npm run build + +# 构建类库文件(同时生成两个文件) +npm run build:min +``` + +构建完成后,类库文件会在`dist/`目录下生成: +- `za-player.min.js` - 压缩后的UMD格式类库文件,适合浏览器直接使用 +- `za-player.es.js` - ES模块格式,适合现代JavaScript项目和打包工具 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..50d1660 --- /dev/null +++ b/index.html @@ -0,0 +1,66 @@ + + + + + + + qhlsplayer + + + +
+
+

ZAPlayer

+
+
+
+ + + + + +
+
+
+ + + + \ No newline at end of file diff --git a/install-deps.js b/install-deps.js new file mode 100644 index 0000000..f6dda24 --- /dev/null +++ b/install-deps.js @@ -0,0 +1,26 @@ +// 依赖安装检查脚本 +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +console.log('检查并安装必要的依赖...'); + +try { + // 检查package.json是否存在 + if (!fs.existsSync('package.json')) { + console.error('package.json 不存在!'); + process.exit(1); + } + + // 安装terser(如果需要高级压缩功能) + console.log('安装 terser...'); + execSync('npm install --save-dev terser', { stdio: 'inherit' }); + + console.log('依赖安装完成!'); + console.log('可以运行以下命令进行构建:'); + console.log(' npm run build:min'); + +} catch (error) { + console.error('依赖安装失败:', error.message); + process.exit(1); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..157a784 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1221 @@ +{ + "name": "qhlsplayer", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "qhlsplayer", + "version": "0.0.0", + "dependencies": { + "flv.js": "^1.6.2" + }, + "devDependencies": { + "terser": "^5.46.0", + "vite": "^7.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flv.js": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/flv.js/-/flv.js-1.6.2.tgz", + "integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==", + "license": "Apache-2.0", + "dependencies": { + "es6-promise": "^4.2.8", + "webworkify-webpack": "^2.1.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/webworkify-webpack": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz", + "integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..09f1eb1 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "qhlsplayer", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:lib": "vite build --config vite.lib.config.js", + "build:simple": "vite build --config vite.lib.simple.config.js", + "build:min": "npm run build:simple && echo '构建完成!文件输出为:dist/za-player.min.js 和 dist/za-player.es.js'", + "install:terser": "npm install --save-dev terser", + "preview": "vite preview" + }, + "devDependencies": { + "terser": "^5.46.0", + "vite": "^7.2.4" + }, + "dependencies": { + "flv.js": "^1.6.2" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/flv-player.js b/src/flv-player.js new file mode 100644 index 0000000..c6297af --- /dev/null +++ b/src/flv-player.js @@ -0,0 +1,114 @@ +// 导入flv.js库 +import flvjs from 'flv.js'; + +class FLVPlayer { + constructor(videoElement) { + this.videoElement = videoElement; + this.flvPlayer = null; + this.flvUrl = null; + this.isLoading = false; + this.isPlaying = false; + this.setupErrorHandlers(); + } + + setupErrorHandlers() { + // 监听视频元素错误 + this.videoElement.addEventListener('error', () => { + console.error('视频元素错误:', this.videoElement.error); + // 不要在错误事件中立即停止,这可能导致无限循环 + if (this.videoElement.error.code === 4 && this.videoElement.src === '') { + // 忽略空src错误,这可能是正常的重置操作 + return; + } + this.stop(); + }); + } + + async load(url) { + try { + // 重置播放器状态 + this.isLoading = true; + this.flvUrl = url; + + // 检查浏览器是否支持flv.js + if (!flvjs.isSupported()) { + throw new Error('浏览器不支持flv.js'); + } + + // 如果已经有播放器实例,先销毁 + if (this.flvPlayer) { + this.flvPlayer.destroy(); + } + + // 创建新的flv.js播放器实例 + this.flvPlayer = flvjs.createPlayer({ + type: 'flv', + url: url + }); + + // 附加到video元素 + this.flvPlayer.attachMediaElement(this.videoElement); + + // 监听flv.js错误事件 + this.flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail, errorInfo) => { + console.error('flv.js错误:', errorType, errorDetail, errorInfo); + this.stop(); + }); + + // 加载视频 + await this.flvPlayer.load(); + + this.isPlaying = true; + } catch (error) { + console.error('加载FLV失败:', error); + this.stop(); + } finally { + this.isLoading = false; + } + } + + // 添加play方法 + play() { + this.isPlaying = true; + if (this.flvPlayer) { + this.flvPlayer.play().catch(error => { + console.error('播放失败:', error); + }); + } + } + + // 添加pause方法 + pause() { + this.isPlaying = false; + if (this.flvPlayer) { + this.flvPlayer.pause(); + } + } + + stop() { + // 停止播放 + this.isPlaying = false; + this.isLoading = false; + + // 暂停视频播放 + this.pause(); + + // 销毁flv.js播放器实例 + if (this.flvPlayer) { + this.flvPlayer.destroy(); + this.flvPlayer = null; + } + + // 清除视频元素的src + this.videoElement.src = ''; + this.videoElement.load(); + } + + destroy() { + // 完全销毁播放器 + this.stop(); + this.videoElement = null; + } +} + +export default FLVPlayer; \ No newline at end of file diff --git a/src/m3u8-player.js b/src/m3u8-player.js new file mode 100644 index 0000000..08f2921 --- /dev/null +++ b/src/m3u8-player.js @@ -0,0 +1,670 @@ +class M3U8Player { + constructor(videoElement) { + this.videoElement = videoElement; + this.mediaSource = null; + this.sourceBuffer = null; + this.segments = []; + this.currentSegmentIndex = 0; + this.isLoading = false; + this.isLiveStream = false; // 标识是否为直播流 + this.refreshInterval = null; // 直播刷新定时器 + this.m3u8Url = null; // 保存M3U8地址 + this.setupErrorHandlers(); + + // WebCodecs相关属性 + this.useWebCodecs = false; + this.videoDecoder = null; + // this.audioDecoder = null; + this.videoQueue = []; + this.audioQueue = []; + this.isPlaying = false; + this.startTime = 0; + this.canvas = null; + this.ctx = null; + this.audioContext = null; + this.audioSource = null; + this.audioProcessor = null; + this.frameRate = 30; + this.lastRenderTime = 0; + } + + setupErrorHandlers() { + // 监听视频元素错误 + this.videoElement.addEventListener('error', () => { + console.error('视频元素错误:', this.videoElement.error); + // 发生错误时跳过当前片段 + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + }); + } + + async load(url) { + try { + // 完全重置播放器状态 + this.stop(); + + // 保存M3U8地址 + this.m3u8Url = url; + + // 完全清除视频元素的src和缓冲 + this.videoElement.src = ''; + this.videoElement.load(); + + // 初始化WebCodecs模式的canvas + this.initWebCodecsCanvas(); + + // 创建新的MediaSource + this.mediaSource = new MediaSource(); + this.videoElement.src = URL.createObjectURL(this.mediaSource); + + await this.waitForMediaSourceOpen(); + await this.parseM3U8(url); + this.startLoading(); + + // 如果是直播流,设置定期刷新 + if (this.isLiveStream) { + this.setupLiveRefresh(); + } + } catch (error) { + console.error('加载M3U8失败:', error); + this.stop(); + } + } + + initWebCodecsCanvas() { + // 创建canvas元素用于WebCodecs渲染 + this.canvas = document.createElement('canvas'); + this.canvas.style.display = 'none'; + this.canvas.width = this.videoElement.clientWidth || 640; + this.canvas.height = this.videoElement.clientHeight || 360; + this.ctx = this.canvas.getContext('2d'); + + // 将canvas插入到video元素后面 + this.videoElement.parentNode.insertBefore(this.canvas, this.videoElement.nextSibling); + } + + waitForMediaSourceOpen() { + return new Promise((resolve, reject) => { + if (this.mediaSource && this.mediaSource.readyState === 'open') { + resolve(); + return; + } + + const timeout = setTimeout(() => { + reject(new Error('MediaSource打开超时')); + }, 5000); + + const onSourceOpen = () => { + clearTimeout(timeout); + this.mediaSource.removeEventListener('sourceopen', onSourceOpen); + this.mediaSource.removeEventListener('sourceerror', onSourceError); + resolve(); + }; + + const onSourceError = (e) => { + clearTimeout(timeout); + this.mediaSource.removeEventListener('sourceopen', onSourceOpen); + this.mediaSource.removeEventListener('sourceerror', onSourceError); + reject(new Error('MediaSource打开错误')); + }; + + this.mediaSource.addEventListener('sourceopen', onSourceOpen); + this.mediaSource.addEventListener('sourceerror', onSourceError); + }); + } + + async parseM3U8(url) { + try { + const response = await fetch(url); + const text = await response.text(); + const lines = text.split('\n'); + let duration = 0; + let segmentUrl = ''; + let newSegments = []; + let currentTimestamp = 0; + + // 检查是否为直播流(没有#EXT-X-ENDLIST标签) + this.isLiveStream = !text.includes('#EXT-X-ENDLIST'); + + // 计算每个片段的时间戳范围 + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (line.startsWith('#EXTINF:')) { + // 获取片段时长 + duration = parseFloat(line.split(':')[1].split(',')[0]); + } else if (!line.startsWith('#') && line) { + // 构建完整的片段URL + if (line.startsWith('http')) { + segmentUrl = line; + } else { + const baseUrl = url.substring(0, url.lastIndexOf('/') + 1); + segmentUrl = baseUrl + line; + } + + // 添加时间戳信息 + newSegments.push({ + url: segmentUrl, + duration, + startTime: currentTimestamp, + endTime: currentTimestamp + duration + }); + + currentTimestamp += duration; + } + } + + //console.log('解析到的片段:', newSegments); + + // 如果是直播流且不是第一次加载,需要特殊处理 + if (this.isLiveStream && this.segments.length > 0) { + // 找出真正的新片段(基于URL比较) + const existingUrls = new Set(this.segments.map(s => s.url)); + const newAddedSegments = newSegments.filter(segment => !existingUrls.has(segment.url)); + + // 确保新片段的时间戳是递增的 + if (newAddedSegments.length > 0) { + // 找到最后一个现有片段的结束时间 + const lastEndTime = this.segments[this.segments.length - 1].endTime; + + // 调整新片段的时间戳 + let adjustedTimestamp = lastEndTime; + newAddedSegments.forEach(segment => { + segment.startTime = adjustedTimestamp; + segment.endTime = adjustedTimestamp + segment.duration; + adjustedTimestamp += segment.duration; + }); + + // 添加新片段到现有列表 + this.segments = [...this.segments, ...newAddedSegments]; + + //console.log('新增片段:', newAddedSegments); + } + } else { + // 第一次加载或非直播流,直接使用新列表 + this.segments = newSegments; + this.currentSegmentIndex = 0; // 确保从第一个片段开始播放 + } + + return this.segments; + } catch (error) { + console.error('解析M3U8失败:', error); + throw error; + } + } + + startLoading() { + this.isLoading = true; + this.loadNextSegment(); + } + + async loadNextSegment() { + if (!this.isLoading || !this.segments.length) { + return; + } + + try { + // 对于直播流,确保索引不越界 + if (this.isLiveStream && this.currentSegmentIndex >= this.segments.length) { + // 等待新的片段 + await new Promise(resolve => setTimeout(resolve, 500)); + this.loadNextSegment(); + return; + } + + // 确保索引在有效范围内 + if (this.currentSegmentIndex >= this.segments.length) { + return; + } + + const segment = this.segments[this.currentSegmentIndex]; + const response = await fetch(segment.url); + const data = await response.arrayBuffer(); + + if (!this.sourceBuffer && !this.useWebCodecs) { + // 提供更全面的MIME类型和编解码器组合的后备方案 + const mimeTypes = [ + // 首选:具体的编解码器组合 + 'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"', // 完整编解码器(原方案) + 'video/mp2t; codecs="avc1.42001E, mp4a.40.2"', // 另一种常见的编解码器组合 + 'video/mp2t; codecs="avc1.64001F, mp4a.40.2"', // 更高质量的视频编解码器 + 'video/mp2t; codecs="avc1.4D401E, mp4a.40.2"', // 另一种常见的H.264编解码器 + + // 备选:只指定视频编解码器 + 'video/mp2t; codecs="avc1.42E01E"', + 'video/mp2t; codecs="avc1.42001E"', + 'video/mp2t; codecs="h264"', + + // 备选:只指定音频编解码器 + 'video/mp2t; codecs="mp4a.40.2"', + 'video/mp2t; codecs="aac"', + + // 最后选择:不指定编解码器或使用其他容器格式 + 'video/mp2t', + 'video/x-mpeg2-transport-stream', + + 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"' + ]; + + // 尝试找到浏览器支持的MIME类型 + let supportedMimeType = null; + for (const mimeType of mimeTypes) { + if (MediaSource.isTypeSupported(mimeType)) { + supportedMimeType = mimeType; + console.log('使用支持的MIME类型:', mimeType); + break; + } + } + + if (!supportedMimeType) { + // 尝试使用最基础的类型,如果失败则会在下面的try-catch中处理 + supportedMimeType = 'video/mp2t'; + } + + // 在调用addSourceBuffer前再次检查 + if (MediaSource.isTypeSupported(supportedMimeType)) { + try { + // 使用sequence模式更适合直接播放 + this.sourceBuffer = this.mediaSource.addSourceBuffer(supportedMimeType); + this.sourceBuffer.mode = 'sequence'; + this.setupSourceBuffer(); + } catch (error) { + console.error('创建SourceBuffer失败:', error); + // 尝试其他方法,比如使用更简单的格式 + try { + this.sourceBuffer = this.mediaSource.addSourceBuffer('video/mp2t; codecs="h264,aac"'); + this.sourceBuffer.mode = 'sequence'; + this.setupSourceBuffer(); + } catch (e) { + console.error('再次创建SourceBuffer失败:', e); + // 如果都失败,尝试使用WebCodecs + console.log('尝试使用WebCodecs解码视频'); + this.useWebCodecs = true; + await this.initWebCodecs(); + } + } + } else { + // 使用WebCodecs解码视频 + console.log('MIME类型不受支持,尝试使用WebCodecs解码视频'); + this.useWebCodecs = true; + await this.initWebCodecs(); + } + } + + if (this.useWebCodecs) { + // 使用WebCodecs解码 + await this.decodeWithWebCodecs(data, segment); + } else { + // 使用MediaSource解码 + await this.appendBuffer(data, segment); + } + + this.currentSegmentIndex++; + this.loadNextSegment(); + } catch (error) { + console.error('加载片段失败:', error); + // 跳过当前片段,继续加载下一个 + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + } + } + + async initWebCodecs() { + // 检查浏览器是否支持WebCodecs + if (!('VideoDecoder' in window) || !('AudioDecoder' in window)) { + throw new Error('浏览器不支持WebCodecs API'); + } + + // 初始化视频解码器 + this.videoDecoder = new VideoDecoder({ + output: this.decodeVideoChunk.bind(this), + error: (e) => { + console.error('视频解码错误:', e); + } + }); + + // 初始化音频解码器 + // this.audioDecoder = new AudioDecoder({ + // output: this.decodeAudioChunk.bind(this), + // error: (e) => { + // console.error('音频解码错误:', e); + // } + // }); + + // 配置解码器(取消注释并确保配置完成) + try { + await this.videoDecoder.configure({ + codec: 'avc1.42E01E', + hardwareAcceleration: 'prefer-hardware' + }); + } catch (e) { + console.error('视频解码器配置失败:', e); + // 尝试使用其他编解码器 + await this.videoDecoder.configure({ + codec: 'h264', + hardwareAcceleration: 'prefer-hardware' + }); + } + + // try { + // await this.audioDecoder.configure({ + // codec: 'mp4a.40.2' + // }); + // } catch (e) { + // console.error('音频解码器配置失败:', e); + // // 尝试使用其他编解码器 + // try { + // await this.audioDecoder.configure({ + // codec: 'aac' + // }); + // } catch (e2) { + // console.error('音频解码器再次配置失败:', e2); + // // 如果音频配置失败,我们仍然可以继续,只播放视频 + // } + // } + + // 显示canvas,隐藏video元素 + this.canvas.style.display = 'block'; + this.videoElement.style.display = 'none'; + + // 开始渲染循环 + this.isPlaying = true; + this.startTime = performance.now(); + this.renderLoop(); + } + + decodeVideoChunk(frame) { + this.videoQueue.push({ + frame, + timestamp: frame.timestamp / 1000000 // 转换为毫秒 + }); + } + + decodeAudioChunk(frame) { + this.audioQueue.push({ + frame, + timestamp: frame.timestamp / 1000000 // 转换为毫秒 + }); + } + + async decodeWithWebCodecs(data, segment) { + // 检查解码器是否已配置 + if (!this.videoDecoder || this.videoDecoder.state !== 'configured') { + console.error('视频解码器未配置'); + return; + } + + } + + renderLoop() { + if (!this.isPlaying) { + return; + } + + const currentTime = performance.now() - this.startTime; + + // 渲染视频帧 + while (this.videoQueue.length > 0 && this.videoQueue[0].timestamp <= currentTime) { + const { frame } = this.videoQueue.shift(); + this.renderFrame(frame); + frame.close(); + } + + // 播放音频帧 + while (this.audioQueue.length > 0 && this.audioQueue[0].timestamp <= currentTime) { + const { frame } = this.audioQueue.shift(); + this.playAudioFrame(frame); + frame.close(); + } + + // 继续渲染循环 + requestAnimationFrame(() => this.renderLoop()); + } + + renderFrame(frame) { + if (!this.ctx) { + return; + } + + // 调整canvas大小 + if (this.canvas.width !== frame.displayWidth || this.canvas.height !== frame.displayHeight) { + this.canvas.width = frame.displayWidth; + this.canvas.height = frame.displayHeight; + } + + // 渲染帧 + this.ctx.drawImage(frame, 0, 0); + } + + playAudioFrame(frame) { + // 初始化音频上下文 + if (!this.audioContext) { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + } + + // 创建音频缓冲区 + const buffer = this.audioContext.createBuffer( + frame.numberOfChannels, + frame.length, + frame.sampleRate + ); + + // 填充音频数据 + for (let channel = 0; channel < frame.numberOfChannels; channel++) { + const data = buffer.getChannelData(channel); + frame.copyTo(data, { planeIndex: channel }); + } + + // 播放音频 + const source = this.audioContext.createBufferSource(); + source.buffer = buffer; + source.connect(this.audioContext.destination); + source.start(); + } + + setupSourceBuffer() { + this.sourceBuffer.addEventListener('updateend', () => { + // 检查视频元素和MediaSource的状态 + if (this.videoElement.error) { + console.error('视频元素出错,停止更新:', this.videoElement.error); + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + return; + } + + // 检查MediaSource状态 + if (!this.mediaSource || this.mediaSource.readyState !== 'open') { + console.error('MediaSource未打开,停止SourceBuffer更新'); + return; + } + + // 立即播放,不等待缓冲 + if (!this.videoElement.paused && this.videoElement.readyState > 2) { + this.videoElement.play().catch(error => { + console.error('自动播放失败:', error); + }); + } + }); + } + + // 简化的appendBuffer方法,不做缓冲处理 + appendBuffer(data, segment) { + return new Promise((resolve) => { + // 检查数据是否为空 + if (!data || data.byteLength === 0) { + console.error('数据为空,无法追加缓冲区'); + // 数据为空,跳过当前片段 + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + resolve(); + return; + } + + // 检查视频元素是否已卸载 + if (!this.videoElement || this.videoElement.disconnected) { + console.error('视频元素已卸载,无法追加缓冲区'); + resolve(); + return; + } + + // 检查当前片段是否有效 + if (!segment || !segment.url) { + console.error('无效的片段信息'); + // 无效片段,跳过当前片段 + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + resolve(); + return; + } + + const append = () => { + // 检查视频元素错误 + if (this.videoElement.error) { + console.error('视频元素错误,无法追加缓冲区:', this.videoElement.error); + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + resolve(); + return; + } + + if (!this.mediaSource) { + console.error('MediaSource未初始化,无法追加缓冲区'); + resolve(); + return; + } + + if (this.mediaSource.readyState !== 'open') { + console.warn('MediaSource未打开,无法追加缓冲区'); + resolve(); + return; + } + + if (!this.sourceBuffer || this.sourceBuffer.updating) { + setTimeout(append, 10); + return; + } + + try { + this.sourceBuffer.appendBuffer(data); + //console.log('追加片段:', segment.url); + resolve(); + } catch (error) { + console.error('追加Buffer失败:', error); + // 任何错误都跳过当前片段 + this.currentSegmentIndex++; + setTimeout(() => this.loadNextSegment(), 0); + resolve(); + } + }; + + append(); + }); + } + + setupLiveRefresh() { + // 每隔几秒重新请求M3U8文件 + this.refreshInterval = setInterval(async () => { + if (!this.isLiveStream || !this.isLoading) return; + + try { + await this.parseM3U8(this.m3u8Url); + } catch (error) { + console.error('刷新M3U8失败:', error); + } + }, 3000); // 每3秒刷新一次 + } + + play() { + if (this.useWebCodecs) { + // WebCodecs模式 + this.isPlaying = true; + this.startTime = performance.now() - (this.lastRenderTime || 0); + this.renderLoop(); + } else { + // MediaSource模式 + // 检查src是否为空 + if (!this.videoElement.src) { + console.error('视频源地址为空,请先加载M3U8文件'); + return; + } + + if (this.videoElement.error) { + console.error('视频元素出错,无法播放:', this.videoElement.error); + return; + } + + this.videoElement.play().catch(error => { + console.error('播放失败:', error); + }); + } + } + + pause() { + if (this.useWebCodecs) { + // WebCodecs模式 + this.isPlaying = false; + } else { + // MediaSource模式 + this.videoElement.pause(); + } + } + + stop() { + this.isLoading = false; + this.isPlaying = false; + this.currentSegmentIndex = 0; + this.lastRenderTime = 0; + + // 清除直播刷新定时器 + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + + if (this.mediaSource) { + try { + URL.revokeObjectURL(this.videoElement.src); + } catch (e) { + console.error('撤销ObjectURL失败:', e); + } + this.mediaSource = null; + this.sourceBuffer = null; + } + + // 释放WebCodecs资源 + if (this.videoDecoder) { + this.videoDecoder.close(); + this.videoDecoder = null; + } + + // 注意:当前版本未使用音频解码器 + // if (this.audioDecoder) { + // this.audioDecoder.close(); + // this.audioDecoder = null; + // } + + if (this.audioContext) { + this.audioContext.close(); + this.audioContext = null; + } + + // 清空队列 + this.videoQueue = []; + this.audioQueue = []; + + try { + this.videoElement.pause(); + this.videoElement.src = ''; + this.videoElement.load(); + + // 恢复显示video元素,隐藏canvas + this.videoElement.style.display = 'block'; + if (this.canvas) { + this.canvas.style.display = 'none'; + } + } catch (e) { + console.error('重置视频元素失败:', e); + } + } +} + +export default M3U8Player; \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..3494df0 --- /dev/null +++ b/src/main.js @@ -0,0 +1,29 @@ +import M3U8Player from './m3u8-player.js' +import FLVPlayer from './flv-player.js' + +class ZAPlayer { + constructor(videoWrapper,params={type:'hls'}){ + videoWrapper = (typeof videoWrapper === 'string') ? document.querySelector(videoWrapper)||document.getElementById(videoWrapper) : videoWrapper; + videoWrapper.innerHTML = ''; + const videoElement = document.getElementById('videoPlayer'); + + if(params.type==='hls'){ + // 初始化hls播放器 + this.player = new M3U8Player(videoElement); + if(params.src){ + this.player.load(params.src); + } + }else{ + //flv视频播放支持 + this.player = new FLVPlayer(videoElement); + if(params.src){ + this.player.load(params.src); + } + } + } +} + +// 将ZAPlayer挂载到window对象,使其成为全局可用的类库 +window.ZAPlayer = ZAPlayer; + +export default ZAPlayer; \ No newline at end of file diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..70c18fe --- /dev/null +++ b/src/style.css @@ -0,0 +1,107 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +#app { + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.player-container { + width: 100%; + margin: 0 auto; +} + +.video-wrapper { + margin: 20px 0; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + background: #000; + position: relative; +} + +video { + width: 100%; + height: auto; + display: block; +} + +.controls { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + margin-top: 20px; +} + +input[type="text"] { + flex: 1; + min-width: 200px; + padding: 0.6em 1.2em; + border-radius: 8px; + border: 1px solid #ccc; + font-size: 1em; + font-family: inherit; + background-color: #1a1a1a; + color: white; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} + +button:hover { + border-color: #646cff; +} + +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + input[type="text"] { + background-color: #ffffff; + color: #213547; + border-color: #ccc; + } +} diff --git a/test-build.html b/test-build.html new file mode 100644 index 0000000..f43a529 --- /dev/null +++ b/test-build.html @@ -0,0 +1,92 @@ + + + + + + ZAPlayer 构建测试 + + + +

ZAPlayer 构建文件测试

+ +
+

测试1:UMD格式 (za-player.min.js)

+
+
+
+ +
+

测试2:ES模块格式 (za-player.es.js)

+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..e3c57a1 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + build: { + rollupOptions: { + input: { + main: 'index.html' + } + } + }, + server: { + port: 3000, + open: true + } +}) \ No newline at end of file diff --git a/vite.lib.config.js b/vite.lib.config.js new file mode 100644 index 0000000..d3d7af5 --- /dev/null +++ b/vite.lib.config.js @@ -0,0 +1,31 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, 'src/main.js'), + name: 'ZAPlayer', + fileName: (format) => { + // 根据格式生成不同的文件名 + if (format === 'es') { + return 'za-player.es.js' // ES模块格式 + } else if (format === 'umd') { + return 'za-player.min.js' // UMD压缩格式 + } + return `za-player.${format}.js` + }, + formats: ['es', 'umd'] // 同时生成ES和UMD格式 + }, + rollupOptions: { + external: [], + output: { + globals: {} + } + }, + // 使用Vite内置的压缩(esbuild),不需要额外依赖 + minify: 'esbuild', // 或者使用 true(Vite默认压缩) + // 不生成source map + sourcemap: false + } +}) \ No newline at end of file diff --git a/vite.lib.simple.config.js b/vite.lib.simple.config.js new file mode 100644 index 0000000..692e345 --- /dev/null +++ b/vite.lib.simple.config.js @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, 'src/main.js'), + name: 'ZAPlayer', + fileName: (format) => { + if (format === 'es') { + return 'za-player.es.js' + } else if (format === 'umd') { + return 'za-player.min.js' + } + return `za-player.${format}.js` + }, + formats: ['es', 'umd'] + }, + rollupOptions: { + external: [], + output: { + globals: {} + } + }, + // 使用esbuild压缩(Vite内置,无需额外依赖) + minify: true, + sourcemap: false + } +}) \ No newline at end of file