commit 9c01a78da5f456761fe5e2fa0e0f9ed6d4420de3 Author: 龚皓 <1736436516@qq.com> Date: Wed Jun 10 11:46:45 2026 +0800 API UPDATE FIRST diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..cff0aa4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,313 @@ +# AGENTS.md + +本文档包含针对此 Vue 3 + TypeScript 本地告警管理应用的智能编码代理指南。 + +## 开发命令 + +| 命令 | 说明 | +|------|------| +| `npm install` | 安装依赖 | +| `npm run dev` | 启动热重载开发服务器(8000端口) | +| `npm run build` | 构建生产版本(包含类型检查) | +| `npm run type-check` | 仅运行 TypeScript 类型检查 | +| `npm run preview` | 本地预览生产构建 | + +> 本项目目前未配置测试框架。 + +## 项目结构 + +``` +src/ +├── components/ # 可复用 Vue 组件 +├── html/ # 页面级 Vue 组件 +├── utils/ # 工具函数和 API +│ ├── boxApi.ts # 主 API 接口 +│ ├── useGlobalWebSocket.ts # WebSocket 管理 +│ ├── crossWindowChannel.ts # 跨窗口通信 +│ └── eventBus.ts # 事件总线(mitt) +├── router/ # Vue Router 配置 +└── stores/ # Pinia 状态管理 +``` + +## 代码风格指南 + +### TypeScript +- 路径别名:`@/` 映射到 `src/` +- 始终为 props 和数据接口添加类型 + +### Vue 组件 +- 使用 ` + + + +``` + +> 作为父页面,不可避免需要向其下子组件传递参数,可通过导入vue中的provide方法直接传递多层次的子组件信息,子组件inject获取信息,当然在vue3中数据的变化需要用reactive包裹数据,创建响应式变化。 + + + +#### main.ts(关键) + +> 对于app组件的挂载和改改父组件的依赖使用和申明需要在这里定义使用,当一些新的外部组件引用逻辑正确但是显示异常,可检查是否在根文件中引用 + + + +### 路由(Route) + +``` +└─src + ├─router + │ index.ts +``` + +- 看门狗(to,form,next) + +``` +import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' +const routes = [ + { + path: '/', + name: '', + component: , + children: [] + + } +const router = createRouter({ + history: createWebHashHistory(), + routes +}); +router.beforeEach((to, from, next) => { + const token = localStorage.getItem('token'); + if (to.matched.some(record => record.meta.requiresAuth) && !token) { + next({ name: 'Login' }); + } else { + next(); + } +}); +export default router; + + +``` + + + +## 登录 + +- 页面拦截 + +> 简易拦截,alertToken作为判断条件,拦截对象,当然数据请求需要交互验证。 + +``` +// 导航守卫,检查登录状态 +router.beforeEach((to, from, next) => { + const token = localStorage.getItem('alertToken'); // 检查 token 是否存在,作为是否登录的依据 + if (to.matched.some(record => record.meta.requiresAuth)) { + // 该路由需要认证 + if (!token) { + // 如果没有 token,重定向到登录页面 + next({ + path: '/login', + query: { redirect: to.fullPath } // 将当前路径传递给登录页面,登录后可以重定向回来 + }); + } else { + // 已登录,继续访问 + next(); + } + } else { + // 不需要认证,继续访问 + next(); + } +}); + +``` + + + + + +## 本地告警首页子页面ViewList布局 + +![image-20241030162011722](https://gitee.com/gonghao_git/draw-bed/raw/master/img/%E9%A6%96%E9%A1%B5ViewList%E5%B8%83%E5%B1%80-20241030162011722.png) + + + +![image-20241031164625119](https://gitee.com/gonghao_git/draw-bed/raw/master/img/%E5%A4%A7%E5%B1%8F%E9%A1%B5%E9%9D%A2%E5%88%9D%E7%A8%BF-20241031164625119.png) + + + +![image-20251201110453175](https://gitee.com/gonghao_git/draw-bed/raw/master/img/image-20251201110453175.png) + +## 告警规则设置 + + + +### 界面接口列表 + +``` + private readonly apiLogin: string = "/auth/login"; + - public async login + - (username: string, password: string, cookieLess: boolean = false): Promise + + private readonly apiLogout: string = "/auth/logout"; + - public async logout + - (token: string | null = null): Promise + + + private readonly apiAdduser: string = "/auth/adduser"; + - public async addUser + - (username: string, password: string, email: string = "", token: string | null = null): Promise + + private readonly apiRmuser: string = "/auth/rmuser"; + - public async rmUser + - (username: string, token: string | null = null): Promise + + private readonly apiAllusers: string = "/auth/allusers"; + - public async getAllUsers + - (token: string | null = null): Promise + + private readonly apiCameras: string = "/cameras"; + - public async getCameras + - (limit: number, offset: number, token: string | null = null): Promise + + private readonly apiEvents: string = "/events"; + - public async getEvents + - (token: string | null = null, pageSize: number = 20, currentPage: number = 1): Promise + + - public async getEventsByParams + - ( + token: string | null = null, + pageSize: number = 20, + currentPage: number = 1, + timeBefore: string | null = null, + timeAfter: string | null = null, + types: string | null = null, + camera_id: number | null = null, + status?: 'pending' | 'closed' | null + ): Promise + + - public async setEventStatus + - (eventId: number, status: string, remark: string | null = null, token: string | null = null): Promise + + private readonly apiAlgorithms: string = "/algorithms"; + - public async getAlgorithms + - (token: string | null = null): Promise + + private readonly apiResetUser: string = "/auth/resetuser"; + - public async resetUser + - (username: string, password: string, email: string, token: string | null = null): Promise + + private readonly apiResetPassword: string = "/auth/reset_password"; + - public async resetPassword + - (newPassword: string, token: string | null = null): Promise + + private readonly apiAllCameras: string = "/camera/cameras/get_all"; + - public async getAllCameras + - (token: string | null = null): Promise + + private readonly superCamera: string = "/camera/cameras"; + - public async getCameraById + - (token: string | null = null, cameraId: number): Promise + + - public async updateCamera + - (token: string | null = null, cameraId: number, jsonData: CameraData): Promise + + - public async startCameraStream + - (token: string | null = null, cameraId: number): Promise + + - public async stopCameraStream + - (token: string | null = null, cameraId: number): Promise + + private readonly superRule: string = "/rules"; + - public async updateRule + - (token: string | null = null, rules: RuleData[]): Promise + + private readonly superEvents: string = "/event/events"; + - public async delEvents + - (token: string | null = null, eventIds: number[]): Promise<{ id: number, success: boolean, message?: string }[]> + + - public async getEventById + - (id: number, token: string | null = null): Promise + +``` + + + + + +- 摄像实例 + +``` +const cameraId = 1; // 示例摄像头 ID +const token = "your-auth-token"; +const cameraJson = { + "id": 1, + "name": "421枪机", + "uri": "rtsp://admin:1234qwer@192.168.28.102:554/Streaming/Channels/202", + "mode": "on", + "status": "online", + "detect_params": { "threshold": 0.5 }, + "default_params": {}, + "should_push": false, + "config_params": {}, + "sampling": false, + "note": {}, + "snapshot": "http://127.0.0.1:8000/media/cameras/1/snapshot.jpg", + "remote_id": -1, + "raw_address": "0b8342ec52e62d01dd3273f583d326ec", + "ip": "admin:1234qwer@192.168.28.102:554", + "port": 8082, + "rules": [] // 包含的 rules 会被排除 +}; +await apiInstance.updateCamera(token, cameraId, cameraJson); + +``` + + + +- 规则实例 + +``` +const cameraId = 1; // 示例摄像头 ID +const token = "your-auth-token"; +const cameraJson = { + "id": 1, + "name": "421枪机", + "uri": "rtsp://admin:1234qwer@192.168.28.102:554/Streaming/Channels/202", + "mode": "on", + "status": "online", + "detect_params": { "threshold": 0.5 }, + "default_params": {}, + "should_push": false, + "config_params": {}, + "sampling": false, + "note": {}, + "snapshot": "http://127.0.0.1:8000/media/cameras/1/snapshot.jpg", + "remote_id": -1, + "raw_address": "0b8342ec52e62d01dd3273f583d326ec", + "ip": "admin:1234qwer@192.168.28.102:554", + "port": 8082, + "rules": [] // 包含的 rules 会被排除 +}; +await apiInstance.updateCamera(token, cameraId, cameraJson); + +``` + + + +``` +单条camera实例部分内容: +{ + "id": 1, + "name": "421枪机", + + "mode": "on", + + + "rules": [{规则1json数据},{规则2json数据}] +} + +单条rule实例部分: + { + "id": 3, + "camera": 1, + "name": "人员逗留", + "mode": "schedule", + "algo": "personnel_staying", + "params": {}, + "params_base": "", + "unique_id": "7ce99a51-0ee4-4251-a754-bd897d671303", + "event_types": { + "personnel_staying": "人员逗留" + }, + "schedule": { + "type": "weekly", + "time_slots": [ + [ + 5, + 1415 + ] + ], + "week_day": "Monday" + } + } +``` + + + +- 时间段规则设置 + +> 添加时间段schedule的设置,组件显示开始时间点和结束时间,数据格式化方式为将代表累计分钟的数字转换为时间点,每60分钟为1小时,数字范围在0到1440,实例[56,65]代表0:56到1:05,组件显示将数字组转换为时间段,请求时将组件时间段显示为数字组。 +> +> 时间段结束时间点不能大于开始时间点,多时间段添加不能有重合部分。 +> +> 设置规则,只有在rule.mode为schedule时才可设置时间段,携带参数日期类型和多个时间段数字组合,在未保存切换rule。mode模式时间段不会因为隐藏组件而清空数值,始终显示开始设定好的默认值,当提交时处于时间段模式至少得设置一个时间段数组否则无法保存,若处在schedule情况下保存,携带参数请求,若rule.mode处于on或者off,代表没有时间段设置,那么schedule下的所有参数及时原来有值,也需要伴随请求清空默认值,具体如下: + + + +> 1.在rule.mode为schedule模式添加时间段time_slots的组件,添加时间段类型,输入框值默认为daily,不可修改 +> +> 2.开始时间和结束时间的组件,时间通过分钟数转换为格式化的时间(小时:分钟),有删除按钮可删除临时的时间段设置,使用数字范围(0-1440),单个时间点代表0点到设置的时间点累计的分钟数字 +> +> 3.时间段的校验,结束时间不能小于开始时间,多时间段之间不能有重叠。 +> +> 4.如果 rule.mode 为 schedule,则必须包含至少一个时间段,如果 rule.mode 是 on 或 off,清空所有时间段和时间段类型 + + + + + +- 静态数据实例 + +``` +"camera": { + "id": 1, + "name": "421枪机", + "mode": "on", + "status": "online", + "rules": [ + { + "id": 1, + + "camera": 1, + "name": "入侵test2", + "mode": "schedule", + "algo": "intrude", + "schedule": { + "type": "daily", + "time_slots": [ + [ + 118, + 898 + ], + [ + 0, + 60 + ] + ] + }, + }, + { + "id": 3, + "camera": 1, + "name": "人员逗留", + "mode": "schedule", + "algo": "personnel_staying", + "schedule": { + "type": "daily", + "time_slots": [ + [ + 180, + 420 + ] + ] + }, + } + ], + "snapshot": "http://192.168.28.33:8000/media/cameras/1/snapshot.jpg", + } +``` + + + + + + + +## 日志 + +### 2024.11.21 + +- 本地打包 +- 服务器页面备份 +- 服务器迭代,覆盖 +- 版本提交云端 + +- 更新内容 + - 1.弹窗仅图片显示(AlertManagement,LeftBottom,App) + - 2.通道点播(Setting,Channel) + - 摄像列表 + - 搜索/在线状态过滤 + - 3.通道规则设置(Channel,CameraRules) + - 规则开关 + - 时间设置 +- 下阶段 + - 1.弹窗模式按钮 + - 交互式开关(已有,点击响应) + - 响应式开关(###) + - 2.光影效果 + - 声音联动(提示音) + - 交互(首页) + - 3.用户设定不可更改项 + - 测试调整(遗留代码冗余调整) + + + + + +### 2024.11.22 + +- 弹窗模式开关 + - 交互式开关(点击触发弹窗) + - 响应式开关(触发对话框) +- 弹窗现实 + - 格式化类型 + - 格式化时间 + - 现实时间 + - 弹窗布局调整 +- 控制台日志处理(部分) + + + + + +### 2024.11.26 + +- 对话框大图显示 +- 删除告警按钮 +- 主页定时刷新 +- 布局滚动查看调整 +- 用户删除限制 + + + +### 2024.12.3 + +- 告警设置 + - 消息队列设置 + - 声音设置 + - 弹窗动画添加 + - 弹窗队列添加,错误重试机制(2s*5次) + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SScode.md b/SScode.md new file mode 100644 index 0000000..0508ab9 --- /dev/null +++ b/SScode.md @@ -0,0 +1,220 @@ +## 分批请求函数 + +- 入参方式 + - 1. (页码 + 页面大小) + - 2. (偏移量 + 页面大小) + +### 1》页码 + 页面大小 + +``` +const fetchCameras = async () => { + try { + const token = localStorage.getItem('alertToken'); + const limit = 20; // 每次请求 20 条数据 + let offset = 0; // 从 0 开始 + let allCameras = []; + + // 第一次请求,用于获取总数 + const firstResponse = await apiInstance.getCameras(limit, offset, token); + cameraCount.value = firstResponse.count; + allCameras = firstResponse.results; + + // 根据总数继续请求剩余的数据 + const total = cameraCount.value; + while (offset + limit < total) { + offset += limit; + const response = await apiInstance.getCameras(limit, offset, token); + allCameras = allCameras.concat(response.results); + } + cameras.value = allCameras; + } catch (error) { + console.error('Error fetching cameras:', error); + } +}; +``` + + + +### 2》 偏移量 + 页面大小 + +- .length + +``` +const fetchEvents = async () => { + try { + const token = localStorage.getItem('alertToken'); + const limit = 2; + let currentPage = 1; + let allEvents = []; + let total = 0; + + // 先请求一次以获取总条数 + const firstResponse = await apiInstance.getEvents(token, limit, currentPage); + total = firstResponse.totalItems; // 总条数 + allEvents = firstResponse.tableData; // 第一次请求的数据 + + // 使用 while 循环来请求后续的数据 + while (allEvents.length < total) { + currentPage += 1; + const offset = (currentPage - 1) * limit; + const response = await apiInstance.getEvents(token, limit, currentPage); + + allEvents = allEvents.concat(response.tableData); // 追加数据 + } + + tableData.value = allEvents; // 将所有数据赋值给 tableData + } catch (error) { + console.error("Error fetching events data:", error); + } +}; +``` + + + +- offset + limit计算(初稿备份) + +``` +const fetchEvents = async () => { + try { + const token = localStorage.getItem('alertToken'); + const limit = 2000; + let currentPage = 1; + let allEvents = []; + let total = 0; + let offset = (currentPage - 1) * limit; + + // 先请求一次以获取总条数 + const firstResponse = await apiInstance.getEvents(token, limit, currentPage); + total = firstResponse.totalItems; // 总条数 + allEvents = firstResponse.tableData; // 第一次请求的数据 + + // 使用 while 循环来请求后续的数据 + while (offset+limit < total) { + currentPage += 1; + offset = (currentPage - 1) * limit; + const response = await apiInstance.getEvents(token, limit, currentPage); + + allEvents = allEvents.concat(response.tableData); // 追加数据 + } + + tableData.value = allEvents; // 将所有数据赋值给 tableData + } catch (error) { + console.error("Error fetching events data:", error); + } +}; +``` + + + +## 数组并行处理和排序 + +- 更改前(分两个数组直接存) + +``` +const fetchCameras = async () => { + try { + // 获取摄像头数据 + const token = localStorage.getItem('alertToken'); + const limit = 20; + let offset = 0; + let allCameras = []; + + // 获取所有摄像头信息 + const firstResponse = await apiInstance.getCameras(limit, offset, token); + const cameraCount = firstResponse.count; + allCameras = firstResponse.results; + + while (offset + limit < cameraCount) { + offset += limit; + const response = await apiInstance.getCameras(limit, offset, token); + allCameras = allCameras.concat(response.results); + } + + // 提取摄像头名称和对应的告警数量 + const cameraNames = []; + const cameraCounts = []; + + for (const camera of allCameras) { + cameraNames.push(camera.name); + + // 获取该摄像头的告警数量 + const eventsResponse = await apiInstance.getEventsByParams(token, 20, 1, null, null, null, camera.id); + const count = eventsResponse.count || 0; // 确保即使没有事件也返回 0 + cameraCounts.push(count); + } + + // 更新图表的 Y 轴标签和系列数据 + option.value.yAxis[0].data = cameraNames; + option.value.yAxis[1].data = cameraCounts.map(count => `${count}`); + option.value.series[0].data = cameraCounts; + option.value.series[1].data = cameraCounts; + + // 设置图表选项并启动轮播 + myChart.setOption(option.value); + startMoveDataZoom(); // 重新启动轮播效果 + } catch (error) { + console.error("Error fetching cameras or events:", error); + } +}; + +``` + + + +- 更改后 + +``` +const fetchCameras = async () => { + try { + // 获取摄像头数据 + const token = localStorage.getItem('alertToken'); + const limit = 20; + let offset = 0; + let allCameras = []; + + // 获取所有摄像头信息 + const firstResponse = await apiInstance.getCameras(limit, offset, token); + const cameraCount = firstResponse.count; + allCameras = firstResponse.results; + + while (offset + limit < cameraCount) { + offset += limit; + const response = await apiInstance.getCameras(limit, offset, token); + allCameras = allCameras.concat(response.results); + } + + // 提取摄像头名称和对应的告警数量并存储在对象数组中 + const cameraData = []; + + for (const camera of allCameras) { + // 获取该摄像头的告警数量 + const eventsResponse = await apiInstance.getEventsByParams(token, 20, 1, null, null, null, camera.id); + const count = eventsResponse.count || 0; // 确保即使没有事件也返回 0 + cameraData.push({ name: camera.name, count }); // 将数据存储为对象以便排序 + } + + // 按照告警数量降序排序 + cameraData.sort((a, b) => b.count - a.count); + + // 提取排序后的名称和数量 + const cameraNames = cameraData.map(item => item.name); + const cameraCounts = cameraData.map(item => item.count); + + // 更新图表的 Y 轴标签和系列数据 + option.value.yAxis[0].data = cameraNames; + option.value.yAxis[1].data = cameraCounts.map(count => `${count}`); + option.value.series[0].data = cameraCounts; + option.value.series[1].data = cameraCounts; + + // 设置图表选项并启动轮播 + myChart.setOption(option.value); + startMoveDataZoom(); // 重新启动轮播效果 + } catch (error) { + console.error("Error fetching cameras or events:", error); + } +}; + +``` + + + diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..4ee2041 --- /dev/null +++ b/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..83251f0 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + 告警管理 + + + +
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a3ffa64 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3362 @@ +{ + "name": "local_alert", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "local_alert", + "version": "0.0.0", + "dependencies": { + "@jiaminghi/data-view": "^2.10.0", + "@kjgl77/datav-vue3": "^1.7.4", + "ant-design-vue": "^4.2.6", + "axios": "^1.7.3", + "dayjs": "^1.11.13", + "echarts": "^5.5.1", + "element-plus": "^2.7.8", + "exceljs": "^4.4.0", + "file-saver": "^2.0.5", + "jsmpeg": "^1.0.0", + "json2csv": "^5.0.7", + "lodash": "^4.17.21", + "mitt": "^3.0.1", + "papaparse": "^5.4.1", + "pinia": "^2.2.6", + "vue": "^3.4.29", + "vue-router": "^4.4.0" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.4", + "@types/node": "^20.14.5", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/tsconfig": "^0.5.1", + "less": "^4.2.0", + "less-loader": "^12.2.0", + "npm-run-all2": "^6.2.0", + "typescript": "~5.4.0", + "vite": "^5.3.1", + "vue-tsc": "^2.0.21" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" + }, + "node_modules/@ant-design/icons-vue": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", + "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmmirror.com/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@floating-ui/core": { + "version": "1.6.7", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", + "dependencies": { + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==" + }, + "node_modules/@jiaminghi/bezier-curve": { + "version": "0.0.9", + "resolved": "https://registry.npmmirror.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz", + "integrity": "sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==", + "dependencies": { + "@babel/runtime": "^7.5.5" + } + }, + "node_modules/@jiaminghi/c-render": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz", + "integrity": "sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@jiaminghi/bezier-curve": "*", + "@jiaminghi/color": "*", + "@jiaminghi/transition": "*" + } + }, + "node_modules/@jiaminghi/charts": { + "version": "0.2.18", + "resolved": "https://registry.npmmirror.com/@jiaminghi/charts/-/charts-0.2.18.tgz", + "integrity": "sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@jiaminghi/c-render": "^0.4.3" + } + }, + "node_modules/@jiaminghi/color": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@jiaminghi/color/-/color-1.1.3.tgz", + "integrity": "sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg==" + }, + "node_modules/@jiaminghi/data-view": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/@jiaminghi/data-view/-/data-view-2.10.0.tgz", + "integrity": "sha512-Cud2MTiMcqc5k2KWabR/svuVQmXHANqURo+yj40370/LdI/gyUJ6LG203hWXEnT1nMCeiv/SLVmxv3PXLScCeA==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@jiaminghi/charts": "*" + } + }, + "node_modules/@jiaminghi/transition": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/@jiaminghi/transition/-/transition-1.1.11.tgz", + "integrity": "sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==", + "dependencies": { + "@babel/runtime": "^7.5.5" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@kjgl77/datav-vue3": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@kjgl77/datav-vue3/-/datav-vue3-1.7.4.tgz", + "integrity": "sha512-zYVTVKkklUxwtiNKS1qPBilm4rTW+WItfp0zVpaRAI8wgXkLSPbDR9xPq2+UcU/Jft7/DVdMfBp709E2ResuPQ==", + "dependencies": { + "@jiaminghi/c-render": "^0.4.3", + "@jiaminghi/charts": "^0.2.18", + "@jiaminghi/color": "^1.1.3", + "@vueuse/core": "^10.11.1" + } + }, + "node_modules/@kjgl77/datav-vue3/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + }, + "node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@kjgl77/datav-vue3/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@tsconfig/node20": { + "version": "20.1.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.14.15", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.14.15.tgz", + "integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz", + "integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.0-alpha.18", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz", + "integrity": "sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.0-alpha.18" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.0-alpha.18", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz", + "integrity": "sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.0-alpha.18", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.0-alpha.18.tgz", + "integrity": "sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.0-alpha.18", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.3", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==" + }, + "node_modules/@vue/language-core": { + "version": "2.0.29", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.0.29.tgz", + "integrity": "sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "~2.4.0-alpha.18", + "@vue/compiler-dom": "^3.4.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.4.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" + }, + "node_modules/@vue/tsconfig": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.5.1.tgz", + "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "dev": true + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ant-design-vue": { + "version": "4.2.6", + "resolved": "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz", + "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^7.0.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.5.0", + "@emotion/hash": "^0.9.0", + "@emotion/unitless": "^0.8.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "csstype": "^3.1.1", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "stylis": "^4.1.3", + "throttle-debounce": "^5.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.39.0", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==" + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/echarts": { + "version": "5.5.1", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz", + "integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.0" + } + }, + "node_modules/element-plus": { + "version": "2.7.8", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.8.tgz", + "integrity": "sha512-h6dx2XihAbQaud0v+6O7Fy0b0G3YNplNVH7QnK3csTcvQd4y4raiyMRQpf9EKbRbTMdNrFsqAZrs9ok9DMcJHg==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsmpeg": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/jsmpeg/-/jsmpeg-1.0.0.tgz", + "integrity": "sha512-wlBKWVJ93NRJaCfrJ1KAgpMvZBLzpZxH3wnC1Yj7DudMDa/5hHeL1HfvW48ndR8GlI4irrqCXuOGhgayP9EbHw==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json2csv": { + "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/json2csv/-/json2csv-5.0.7.tgz", + "integrity": "sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dependencies": { + "commander": "^6.1.0", + "jsonparse": "^1.3.1", + "lodash.get": "^4.4.2" + }, + "bin": { + "json2csv": "bin/json2csv.js" + }, + "engines": { + "node": ">= 10", + "npm": ">= 6.13.0" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.2.0", + "resolved": "https://registry.npmmirror.com/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.13", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.13.tgz", + "integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==" + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-run-all2": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/npm-run-all2/-/npm-run-all2-6.2.2.tgz", + "integrity": "sha512-Q+alQAGIW7ZhKcxLt8GcSi3h3ryheD6xnmXahkMRVM5LYmajcUrSITm8h+OPC9RYWMV2GR0Q1ntTUCfxaNoOJw==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.3", + "memorystream": "^0.3.1", + "minimatch": "^9.0.0", + "pidtree": "^0.6.0", + "read-package-json-fast": "^3.0.2", + "shell-quote": "^1.7.3" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^14.18.0 || ^16.13.0 || >=18.0.0", + "npm": ">= 8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinia": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.2.6.tgz", + "integrity": "sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA==", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.5.11" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rollup": { + "version": "4.20.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "optional": true + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.0.tgz", + "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.40", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "dev": true + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.0.tgz", + "integrity": "sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==", + "dependencies": { + "@vue/devtools-api": "^6.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.0.29", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.0.29.tgz", + "integrity": "sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==", + "dev": true, + "dependencies": { + "@volar/typescript": "~2.4.0-alpha.18", + "@vue/language-core": "2.0.29", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zrender": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz", + "integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9c148f3 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "local_alert", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force" + }, + "dependencies": { + "@jiaminghi/data-view": "^2.10.0", + "@kjgl77/datav-vue3": "^1.7.4", + "ant-design-vue": "^4.2.6", + "axios": "^1.7.3", + "dayjs": "^1.11.13", + "echarts": "^5.5.1", + "element-plus": "^2.7.8", + "exceljs": "^4.4.0", + "file-saver": "^2.0.5", + "jsmpeg": "^1.0.0", + "json2csv": "^5.0.7", + "lodash": "^4.17.21", + "mitt": "^3.0.1", + "papaparse": "^5.4.1", + "pinia": "^2.2.6", + "vue": "^3.4.29", + "vue-router": "^4.4.0" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.4", + "@types/node": "^20.14.5", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/tsconfig": "^0.5.1", + "less": "^4.2.0", + "less-loader": "^12.2.0", + "npm-run-all2": "^6.2.0", + "typescript": "~5.4.0", + "vite": "^5.3.1", + "vue-tsc": "^2.0.21" + } +} diff --git a/public/bg05.png b/public/bg05.png new file mode 100644 index 0000000..e5fb25b Binary files /dev/null and b/public/bg05.png differ diff --git a/public/icon.ico b/public/icon.ico new file mode 100644 index 0000000..56563ea Binary files /dev/null and b/public/icon.ico differ diff --git a/public/login-bg.png b/public/login-bg.png new file mode 100644 index 0000000..3d4fe94 Binary files /dev/null and b/public/login-bg.png differ diff --git a/public/static/jsmpeg-master/LICENSE b/public/static/jsmpeg-master/LICENSE new file mode 100644 index 0000000..896c374 --- /dev/null +++ b/public/static/jsmpeg-master/LICENSE @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2017 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/public/static/jsmpeg-master/README.md b/public/static/jsmpeg-master/README.md new file mode 100644 index 0000000..b09f5c0 --- /dev/null +++ b/public/static/jsmpeg-master/README.md @@ -0,0 +1,253 @@ +# JSMpeg – MPEG1 Video & MP2 Audio Decoder in JavaScript + +JSMpeg is a Video Player written in JavaScript. It consists of an MPEG-TS demuxer, MPEG1 video & MP2 audio decoders, WebGL & Canvas2D renderers and WebAudio sound output. JSMpeg can load static videos via Ajax and allows low latency streaming (~50ms) via WebSockets. + +JSMpeg can decode 720p Video at 30fps on an iPhone 5S, works in any modern browser (Chrome, Firefox, Safari, Edge) and comes in at just 20kb gzipped. + +Using it can be as simple as this: +```html + +
+``` + +Some more info and demos: [jsmpeg.com](http://jsmpeg.com/) + + +## Usage + +A JSMpeg video player can either be created in HTML using the CSS class `jsmpeg` for the container: + +```html +
+``` + +or by directly calling the `JSMpeg.Player()` constructor in JavaScript: + +```javascript +var player = new JSMpeg.Player(url [, options]); +``` + +Note that using the HTML Element (internally `JSMpeg.VideoElement`) provides some features on top of `JSMpeg.Player`. Namely a SVG pause/play button and the ability to "unlock" audio on iOS devices. + +The `url` argument accepts a URL to an MPEG .ts file or a WebSocket server (ws://...). + +The `options` argument supports the following properties: + +- `canvas` – the HTML Canvas elment to use for video rendering. If none is given, the renderer will create its own Canvas element. +- `loop` – whether to loop the video (static files only). Default `true`. +- `autoplay` - whether to start playing immediately (static files only). Default `false`. +- `audio` - whether to decode audio. Default `true`. +- `video` - whether to decode video. Default `true`. +- `poster` – URL to an image to use as the poster to show before the video plays. +- `pauseWhenHidden` – whether to pause playback when the tab is inactive. Default `true`. Note that browsers usually throttle JS in inactive tabs anyway. +- `disableGl` - whether to disable WebGL and always use the Canvas2D renderer. Default `false`. +- `disableWebAssembly` - whether to disable WebAssembly and always use JavaScript decoders. Default `false`. +- `preserveDrawingBuffer` – whether the WebGL context is created with `preserveDrawingBuffer` - necessary for "screenshots" via `canvas.toDataURL()`. Default `false`. +- `progressive` - whether to load data in chunks (static files only). When enabled, playback can begin before the whole source has been completely loaded. Default `true`. +- `throttled` - when using `progressive`, whether to defer loading chunks when they're not needed for playback yet. Default `true`. +- `chunkSize` - when using `progressive`, the chunk size in bytes to load at a time. Default `1024*1024` (1mb). +- `decodeFirstFrame` - whether to decode and display the first frame of the video. Useful to set up the Canvas size and use the frame as the "poster" image. This has no effect when using `autoplay` or streaming sources. Default `true`. +- `maxAudioLag` – when streaming, the maximum enqueued audio length in seconds. +- `videoBufferSize` – when streaming, size in bytes for the video decode buffer. Default 512*1024 (512kb). You may have to increase this for very high bitrates. +- `audioBufferSize` – when streaming, size in bytes for the audio decode buffer. Default 128*1024 (128kb). You may have to increase this for very high bitrates. +- `onVideoDecode(decoder, time)` – A callback that is called after each decoded and rendered video frame +- `onAudioDecode(decoder, time)` – A callback that is called after each decoded audio frame +- `onPlay(player)` – A callback that is called whenever playback starts +- `onPause(player)` – A callback that is called whenever playback paused (e.g. when .pause() is called or the source has ended) +- `onEnded(player)` – A callback that is called when playback has reached the end of the source (only called when `loop` is `false`). +- `onStalled(player)` – A callback that is called whenever there's not enough data for playback +- `onSourceEstablished(source)` – A callback that is called when source has first received data +- `onSourceCompleted(source)` – A callback that is called when the source has received all data + + +All options except from `canvas` can also be used with the HTML Element through `data-` attributes. E.g. to specify looping and autoplay in JavaScript: + +```javascript +var player = new JSMpeg.Player('video.ts' {loop: true, autoplay: true}); +``` + +or HTML +```html +
+``` + +Note that `camelCased` options have to be hyphenated when used as data attributes. E.g. `decodeFirstFrame: true` becomes `data-decode-first-frame="true"` for the HTML element. + + +## JSMpeg.Player API + +A `JSMpeg.Player` instance supports the following methods and properties: + +- `.play()` – start playback +- `.pause()` – pause playback +- `.stop()` – stop playback and seek to the beginning +- `.nextFrame()` – advance playback by one video frame. This does not decode audio. Returns `true` on success, `false` when there's not enough data. +- `.volume` – get or set the audio volume (0-1) +- `.currentTime` – get or set the current playback position in seconds +- `.paused` – read only, wether playback is paused +- `.destroy()` – stops playback, disconnects the source and cleans up WebGL and WebAudio state. The player can not be used afterwards. If the player created the canvas element it is removed from the document. + + +## Encoding Video/Audio for JSMpeg + +JSMpeg only supports playback of MPEG-TS containers with the MPEG1 Video Codec and the MP2 Audio Codec. The Video Decoder does not handle B-Frames correctly (though no modern encoder seems to use these by default anyway) and the width of the video has to be a multiple of 2. + +You can encode a suitable video using [ffmpeg](https://ffmpeg.org/) like this: + +```sh +ffmpeg -i in.mp4 -f mpegts -codec:v mpeg1video -codec:a mp2 -b 0 out.ts +``` + +You can also control the video size (`-s`), framerate (`-r`), video bitrate (`-b:v`), audio bitrate (`-b:a`), number of audio channels (`-ac`), sampling rate (`-ar`) and much more. Please refer to the ffmpeg documentation for the details. + +Comprehensive example: +```sh +ffmpeg -i in.mp4 -f mpegts \ + -codec:v mpeg1video -s 960x540 -b:v 1500k -r 30 -bf 0 \ + -codec:a mp2 -ar 44100 -ac 1 -b:a 128k \ + out.ts +``` + + +## Performance Considerations + +While JSMpeg can handle 720p video at 30fps even on an iPhone 5S, keep in mind that MPEG1 is not as efficient as modern codecs. MPEG1 needs quite a bit of bandwidth for HD video. 720p begins to look okay-ish at 2 Mbits/s (that's 250kb/s). Also, the higher the bitrate, the more work JavaScript has to do to decode it. + +This should not be a problem for static files, or if you're only streaming within your local WiFi. If you don't need to support mobile devices, 1080p at 10mbit/s works just fine (if your encoder can keep up). For everything else I would advise you to use 540p (960x540) at 2Mbit/s max. + +Here is a performance comparison with multiple resolutions and features en-/disabled. Test this on your target devices to get a feel for what you can get away with. + +https://jsmpeg.com/perf.html + + +## Streaming via WebSockets + +JSMpeg can connect to a WebSocket server that sends out binary MPEG-TS data. When streaming, JSMpeg tries to keep latency as low as possible - it immediately decodes everything it has, ignoring video and audio timestamps altogether. To keep everything in sync (and latency low), audio data should be interleaved between video frames very frequently (`-muxdelay` in ffmpeg). + +A separate, buffered streaming mode, where JSMpeg pre-loads a few seconds of data and presents everything with exact timing and audio/video sync is conceivable, but currently not implemented. + +The internal buffers for video and audio are fairly small (512kb and 128kb respectively) and JSMpeg will discard old (even unplayed) data to make room for newly arriving data without much fuzz. This could introduce decoding artifacts when there's a network congestion, but ensures that latency is kept at a minimum. If necessary You can increase the `videoBufferSize` and `audioBufferSize` through the options. + +JSMpeg comes with a tiny WebSocket "relay", written in Node.js. This server accepts an MPEG-TS source over HTTP and serves it via WebSocket to all connecting Browsers. The incoming HTTP stream can be generated using [ffmpeg](https://ffmpeg.org/), [gstreamer](https://gstreamer.freedesktop.org/) or by other means. + +The split between the source and the WebSocket relay is necessary, because ffmpeg doesn't speak the WebSocket protocol. However, this split also allows you to install the WebSocket relay on a public server and share your stream on the Internet (typically NAT in your router prevents the public Internet from connecting _into_ your local network). + +In short, it works like this: + +1. run the websocket-relay.js +2. run ffmpeg, send output to the relay's HTTP port +3. connect JSMpeg in the browser to the relay's Websocket port + + +## Example Setup for Streaming: Raspberry Pi Live Webcam + +For this example, ffmpeg and the WebSocket relay run on the same system. This allows you to view the stream in your local network, but not on the public internet. + +This example assumes that your webcam is compatible with Video4Linux2 and appears as `/dev/video0` in the filesystem. Most USB webcams support the UVC standard and should work just fine. The onboard Raspberry Camera can be made available as V4L2 device by loading a kernel module: `sudo modprobe bcm2835-v4l2`. + + +1) Install ffmpeg (See [How to install ffmpeg on Debian / Raspbian](http://superuser.com/questions/286675/how-to-install-ffmpeg-on-debian)). Using ffmpeg, we can capture the webcam video & audio and encode it into MPEG1/MP2. + +2) Install Node.js and npm (See [Installing Node.js on Debian and Ubuntu based Linux distributions](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) for newer versions). The Websocket relay is written in Node.js + +3) Install http-server. We will use this to serve the static files (view-stream.html, jsmpeg.min.js), so that we can view the website with the video in our browser. Any other webserver would work as well (nginx, apache, etc.): +`sudo npm -g install http-server` + +4) Install git and clone this repository (or just download it as ZIP and unpack) +``` +sudo apt-get install git +git clone https://github.com/phoboslab/jsmpeg.git +``` + +5) Change into the jsmpeg/ directory +`cd jsmpeg/` + +6) Install the Node.js Websocket Library: +`npm install ws` + +7) Start the Websocket relay. Provide a password and a port for the incomming HTTP video stream and a Websocket port that we can connect to in the browser: +`node websocket-relay.js supersecret 8081 8082` + +8) In a new terminal window (still in the `jsmpeg/` directory, start the `http-server` so we can serve the view-stream.html to the browser: +`http-server` + +9) Open the streaming website in your browser. The `http-server` will tell you the ip (usually `192.168.[...]`) and port (usually `8080`) where it's running on: +`http://192.168.[...]:8080/view-stream.html` + +10) In a third terminal window, start ffmpeg to capture the webcam video and send it to the Websocket relay. Provide the password and port (from step 7) in the destination URL: +``` +ffmpeg \ + -f v4l2 \ + -framerate 25 -video_size 640x480 -i /dev/video0 \ + -f mpegts \ + -codec:v mpeg1video -s 640x480 -b:v 1000k -bf 0 \ + http://localhost:8081/supersecret +``` + +You should now see a live webcam image in your browser. + +If ffmpeg failed to open the input video, it's likely that your webcam does not support the given resolution, format or framerate. To get a list of compatible modes run: + +`ffmpeg -f v4l2 -list_formats all -i /dev/video0` + + +To add the webcam audio, just call ffmpeg with two separate inputs. + +``` +ffmpeg \ + -f v4l2 \ + -framerate 25 -video_size 640x480 -i /dev/video0 \ + -f alsa \ + -ar 44100 -c 2 -i hw:0 \ + -f mpegts \ + -codec:v mpeg1video -s 640x480 -b:v 1000k -bf 0 \ + -codec:a mp2 -b:a 128k \ + -muxdelay 0.001 \ + http://localhost:8081/supersecret +``` + +Note the `muxdelay` argument. This should reduce lag, but doesn't always work when streaming video and audio - see remarks below. + + +## Some remarks about ffmpeg muxing and latency + +Adding an audio stream to the MPEG-TS can sometimes introduce considerable latency. I especially found this to be a problem on linux using ALSA and V4L2 (using AVFoundation on macOS worked just fine). However, there is a simple workaround: just run two instances of ffmpeg in parallel. One for audio, one for video. Send both outputs to the same Websocket relay. Thanks to the simplicity of the MPEG-TS format, proper "muxing" of the two streams happens automatically in the relay. + +``` +ffmpeg \ + -f v4l2 \ + -framerate 25 -video_size 640x480 -i /dev/video0 \ + -f mpegts \ + -codec:v mpeg1video -s 640x480 -b:v 1000k -bf 0 \ + -muxdelay 0.001 \ + http://localhost:8081/supersecret + +# In a second terminal +ffmpeg \ + -f alsa \ + -ar 44100 -c 2 -i hw:0 \ + -f mpegts \ + -codec:a mp2 -b:a 128k \ + -muxdelay 0.001 \ + http://localhost:8081/supersecret +``` +In my tests, USB Webcams introduce about ~180ms of latency and there seems to be nothing we can do about it. The Raspberry Pi however has a [camera module](https://www.raspberrypi.org/products/camera-module-v2/) that provides lower latency video capture. + +To capture webcam input on Windows or macOS using ffmpeg, see the [ffmpeg Capture/Webcam Wiki](https://trac.ffmpeg.org/wiki/Capture/Webcam). + + +## JSMpeg Architecture and Internals + +This library was built in a fairly modular fashion while keeping overhead at a minimum. Implementing new Demuxers, Decoders, Outputs (Renderers, Audio Devices) or Sources should be possible without changing any other parts. However, you would still need to subclass the `JSMpeg.Player` in order to use any new modules. + +Have a look a the [jsmpeg.js source](https://github.com/phoboslab/jsmpeg/blob/master/src/jsmpeg.js) for an overview of how the modules interconnect and what APIs they should provide. I also wrote a blog post about some of JSMpeg's internals: [Decode It Like It's 1999](http://phoboslab.org/log/2017/02/decode-it-like-its-1999). + +Using parts of the library without creating a full player should also be fairly straightforward. E.g. you can create a stand-alone instance of the `JSMpeg.Decoder.MPEG1Video` class, `.connect()` a renderer, `.write()` some data to it and `.decode()` a frame, without touching JSMpeg's other parts. + + +## Previous Version + +The JSMpeg version currently living in this repo is a complete rewrite of the original jsmpeg library that was just able to decode raw mpeg1video. If you're looking for the old version, see the [v0.2 tag](https://github.com/phoboslab/jsmpeg/releases/tag/v0.2). + + diff --git a/public/static/jsmpeg-master/build.sh b/public/static/jsmpeg-master/build.sh new file mode 100644 index 0000000..b7bb711 --- /dev/null +++ b/public/static/jsmpeg-master/build.sh @@ -0,0 +1,115 @@ +#!/bin/sh + + +# Build the .wasm Module first + +# Since we're compiling a side module here, so that we can load it without the +# runtime cruft, we have to explicitly compile in support for malloc and +# friends. +# Note memcpy, memmove and memset are explicitly exported, otherwise they will +# be eliminated by the SIDE_MODULE=2 setting - not sure why that happens. + +# This NEEDS to be compiled with emscripten 1.38.47. Newer versions mess with +# malloc and friends and need some more glue code for side modules that I +# haven't quite worked out yet. If you have any idea how to build a SIDE_MODULE +# (or STANDALONE_WASM - as seems to be the new deal) with support for malloc, +# please let me know or file a PR. + +# To install the correct version, issue the following in your emsdk directory: +# ./emsdk install 1.38.47 +# ./emsdk activate 1.38.47 +# source ./emsdk_env.sh + +# The $EMSCRIPTEN_LIB var needs to point to the correct directory within the sdk +# that has emmalloc.cpp. This is usually $EMSDK/fastcomp/emscripten/system/lib +# but it might differ per system. I don't know. +# There used to be an $EMSCRIPTEN var set by the emsdk_env script that pointed +# to the correct directory, but this seems to have gone now. + +# In conclusion, emscripten encapsulates everything that I hate about native +# development :/ + +EMSCRIPTEN_LIB=$EMSDK/fastcomp/emscripten/system/lib + +emcc \ + src/wasm/mpeg1.c \ + src/wasm/mp2.c \ + src/wasm/buffer.c \ + $EMSCRIPTEN_LIB/emmalloc.cpp \ + $EMSCRIPTEN_LIB/libc/musl/src/string/memcpy.c \ + $EMSCRIPTEN_LIB/libc/musl/src/string/memmove.c \ + $EMSCRIPTEN_LIB/libc/musl/src/string/memset.c \ + -s WASM=1 \ + -s SIDE_MODULE=2 \ + -s TOTAL_STACK=5242880\ + -s USE_PTHREADS=0 \ + -s LEGALIZE_JS_FFI=0\ + -s NO_FILESYSTEM=1 \ + -s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE="[]" \ + -s "EXPORTED_FUNCTIONS=[ + '_memcpy', + '_memmove', + '_memset', + '_mpeg1_decoder_create', + '_mpeg1_decoder_destroy', + '_mpeg1_decoder_get_write_ptr', + '_mpeg1_decoder_get_index', + '_mpeg1_decoder_set_index', + '_mpeg1_decoder_did_write', + '_mpeg1_decoder_has_sequence_header', + '_mpeg1_decoder_get_frame_rate', + '_mpeg1_decoder_get_coded_size', + '_mpeg1_decoder_get_width', + '_mpeg1_decoder_get_height', + '_mpeg1_decoder_get_y_ptr', + '_mpeg1_decoder_get_cr_ptr', + '_mpeg1_decoder_get_cb_ptr', + '_mpeg1_decoder_decode', + '_mp2_decoder_create', + '_mp2_decoder_destroy', + '_mp2_decoder_get_write_ptr', + '_mp2_decoder_get_index', + '_mp2_decoder_set_index', + '_mp2_decoder_did_write', + '_mp2_decoder_get_left_channel_ptr', + '_mp2_decoder_get_right_channel_ptr', + '_mp2_decoder_get_sample_rate', + '_mp2_decoder_decode']" \ + -O3 \ + -o jsmpeg.wasm + + +# Concat all .js sources +cat \ + src/jsmpeg.js \ + src/video-element.js \ + src/player.js \ + src/buffer.js \ + src/ajax.js \ + src/fetch.js \ + src/ajax-progressive.js \ + src/websocket.js \ + src/ts.js \ + src/decoder.js \ + src/mpeg1.js \ + src/mpeg1-wasm.js \ + src/mp2.js \ + src/mp2-wasm.js \ + src/webgl.js \ + src/canvas2d.js \ + src/webaudio.js \ + src/wasm-module.js \ + > jsmpeg.js + +# Append the .wasm module to the .js source as base64 string +echo "JSMpeg.WASM_BINARY_INLINED='$(base64 -w 0 jsmpeg.wasm)';" \ + >> jsmpeg.js + + +# Minify +uglifyjs jsmpeg.js -o jsmpeg.min.js + +# Cleanup +rm jsmpeg.js +rm jsmpeg.wasm + diff --git a/public/static/jsmpeg-master/jsmpeg.min.js b/public/static/jsmpeg-master/jsmpeg.min.js new file mode 100644 index 0000000..5c15a55 --- /dev/null +++ b/public/static/jsmpeg-master/jsmpeg.min.js @@ -0,0 +1 @@ +var JSMpeg={Player:null,VideoElement:null,BitBuffer:null,Source:{},Demuxer:{},Decoder:{},Renderer:{},AudioOutput:{},Now:function(){return window.performance?window.performance.now()/1e3:Date.now()/1e3},CreateVideoElements:function(){var elements=document.querySelectorAll(".jsmpeg");for(var i=0;i'+''+''+"";VideoElement.UNMUTE_BUTTON=''+''+''+''+''+""+"";return VideoElement}();JSMpeg.Player=function(){"use strict";var Player=function(url,options){this.options=options||{};if(options.source){this.source=new options.source(url,options);options.streaming=!!this.source.streaming}else if(url.match(/^wss?:\/\//)){this.source=new JSMpeg.Source.WebSocket(url,options);options.streaming=true}else if(options.progressive!==false){this.source=new JSMpeg.Source.AjaxProgressive(url,options);options.streaming=false}else{this.source=new JSMpeg.Source.Ajax(url,options);options.streaming=false}this.maxAudioLag=options.maxAudioLag||.25;this.loop=options.loop!==false;this.autoplay=!!options.autoplay||options.streaming;this.demuxer=new JSMpeg.Demuxer.TS(options);this.source.connect(this.demuxer);if(!options.disableWebAssembly&&JSMpeg.WASMModule.IsSupported()){this.wasmModule=JSMpeg.WASMModule.GetModule();options.wasmModule=this.wasmModule}if(options.video!==false){this.video=options.wasmModule?new JSMpeg.Decoder.MPEG1VideoWASM(options):new JSMpeg.Decoder.MPEG1Video(options);this.renderer=!options.disableGl&&JSMpeg.Renderer.WebGL.IsSupported()?new JSMpeg.Renderer.WebGL(options):new JSMpeg.Renderer.Canvas2D(options);this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.VIDEO_1,this.video);this.video.connect(this.renderer)}if(options.audio!==false&&JSMpeg.AudioOutput.WebAudio.IsSupported()){this.audio=options.wasmModule?new JSMpeg.Decoder.MP2AudioWASM(options):new JSMpeg.Decoder.MP2Audio(options);this.audioOut=new JSMpeg.AudioOutput.WebAudio(options);this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.AUDIO_1,this.audio);this.audio.connect(this.audioOut)}Object.defineProperty(this,"currentTime",{get:this.getCurrentTime,set:this.setCurrentTime});Object.defineProperty(this,"volume",{get:this.getVolume,set:this.setVolume});this.paused=true;this.unpauseOnShow=false;if(options.pauseWhenHidden!==false){document.addEventListener("visibilitychange",this.showHide.bind(this))}if(this.wasmModule){if(this.wasmModule.ready){this.startLoading()}else if(JSMpeg.WASM_BINARY_INLINED){var wasm=JSMpeg.Base64ToArrayBuffer(JSMpeg.WASM_BINARY_INLINED);this.wasmModule.loadFromBuffer(wasm,this.startLoading.bind(this))}else{this.wasmModule.loadFromFile("jsmpeg.wasm",this.startLoading.bind(this))}}else{this.startLoading()}};Player.prototype.startLoading=function(){this.source.start();if(this.autoplay){this.play()}};Player.prototype.showHide=function(ev){if(document.visibilityState==="hidden"){this.unpauseOnShow=this.wantsToPlay;this.pause()}else if(this.unpauseOnShow){this.play()}};Player.prototype.play=function(ev){if(this.animationId){return}this.animationId=requestAnimationFrame(this.update.bind(this));this.wantsToPlay=true;this.paused=false};Player.prototype.pause=function(ev){if(this.paused){return}cancelAnimationFrame(this.animationId);this.animationId=null;this.wantsToPlay=false;this.isPlaying=false;this.paused=true;if(this.audio&&this.audio.canPlay){this.audioOut.stop();this.seek(this.currentTime)}if(this.options.onPause){this.options.onPause(this)}};Player.prototype.getVolume=function(){return this.audioOut?this.audioOut.volume:0};Player.prototype.setVolume=function(volume){if(this.audioOut){this.audioOut.volume=volume}};Player.prototype.stop=function(ev){this.pause();this.seek(0);if(this.video&&this.options.decodeFirstFrame!==false){this.video.decode()}};Player.prototype.destroy=function(){this.pause();this.source.destroy();this.video&&this.video.destroy();this.renderer&&this.renderer.destroy();this.audio&&this.audio.destroy();this.audioOut&&this.audioOut.destroy()};Player.prototype.seek=function(time){var startOffset=this.audio&&this.audio.canPlay?this.audio.startTime:this.video.startTime;if(this.video){this.video.seek(time+startOffset)}if(this.audio){this.audio.seek(time+startOffset)}this.startTime=JSMpeg.Now()-time};Player.prototype.getCurrentTime=function(){return this.audio&&this.audio.canPlay?this.audio.currentTime-this.audio.startTime:this.video.currentTime-this.video.startTime};Player.prototype.setCurrentTime=function(time){this.seek(time)};Player.prototype.update=function(){this.animationId=requestAnimationFrame(this.update.bind(this));if(!this.source.established){if(this.renderer){this.renderer.renderProgress(this.source.progress)}return}if(!this.isPlaying){this.isPlaying=true;this.startTime=JSMpeg.Now()-this.currentTime;if(this.options.onPlay){this.options.onPlay(this)}}if(this.options.streaming){this.updateForStreaming()}else{this.updateForStaticFile()}};Player.prototype.updateForStreaming=function(){if(this.video){this.video.decode()}if(this.audio){var decoded=false;do{if(this.audioOut.enqueuedTime>this.maxAudioLag){this.audioOut.resetEnqueuedTime();this.audioOut.enabled=false}decoded=this.audio.decode()}while(decoded);this.audioOut.enabled=true}};Player.prototype.nextFrame=function(){if(this.source.established&&this.video){return this.video.decode()}return false};Player.prototype.updateForStaticFile=function(){var notEnoughData=false,headroom=0;if(this.audio&&this.audio.canPlay){while(!notEnoughData&&this.audio.decodedTime-this.audio.currentTime<.25){notEnoughData=!this.audio.decode()}if(this.video&&this.video.currentTime0){if(lateTime>frameTime*2){this.startTime+=lateTime}notEnoughData=!this.video.decode()}headroom=this.demuxer.currentTime-targetTime}this.source.resume(headroom);if(notEnoughData&&this.source.completed){if(this.loop){this.seek(0)}else{this.pause();if(this.options.onEnded){this.options.onEnded(this)}}}else if(notEnoughData&&this.options.onStalled){this.options.onStalled(this)}};return Player}();JSMpeg.BitBuffer=function(){"use strict";var BitBuffer=function(bufferOrLength,mode){if(typeof bufferOrLength==="object"){this.bytes=bufferOrLength instanceof Uint8Array?bufferOrLength:new Uint8Array(bufferOrLength);this.byteLength=this.bytes.length}else{this.bytes=new Uint8Array(bufferOrLength||1024*1024);this.byteLength=0}this.mode=mode||BitBuffer.MODE.EXPAND;this.index=0};BitBuffer.prototype.resize=function(size){var newBytes=new Uint8Array(size);if(this.byteLength!==0){this.byteLength=Math.min(this.byteLength,size);newBytes.set(this.bytes,0,this.byteLength)}this.bytes=newBytes;this.index=Math.min(this.index,this.byteLength<<3)};BitBuffer.prototype.evict=function(sizeNeeded){var bytePos=this.index>>3,available=this.bytes.length-this.byteLength;if(this.index===this.byteLength<<3||sizeNeeded>available+bytePos){this.byteLength=0;this.index=0;return}else if(bytePos===0){return}if(this.bytes.copyWithin){this.bytes.copyWithin(0,bytePos,this.byteLength)}else{this.bytes.set(this.bytes.subarray(bytePos,this.byteLength))}this.byteLength=this.byteLength-bytePos;this.index-=bytePos<<3;return};BitBuffer.prototype.write=function(buffers){var isArrayOfBuffers=typeof buffers[0]==="object",totalLength=0,available=this.bytes.length-this.byteLength;if(isArrayOfBuffers){var totalLength=0;for(var i=0;iavailable){if(this.mode===BitBuffer.MODE.EXPAND){var newSize=Math.max(this.bytes.length*2,totalLength-available);this.resize(newSize)}else{this.evict(totalLength)}}if(isArrayOfBuffers){for(var i=0;i>3;i>3;return i>=this.byteLength||this.bytes[i]==0&&this.bytes[i+1]==0&&this.bytes[i+2]==1};BitBuffer.prototype.peek=function(count){var offset=this.index;var value=0;while(count){var currentByte=this.bytes[offset>>3],remaining=8-(offset&7),read=remaining>8-read;value=value<>shift;offset+=read;count-=read}return value};BitBuffer.prototype.read=function(count){var value=this.peek(count);this.index+=count;return value};BitBuffer.prototype.skip=function(count){return this.index+=count};BitBuffer.prototype.rewind=function(count){this.index=Math.max(this.index-count,0)};BitBuffer.prototype.has=function(count){return(this.byteLength<<3)-this.index>=count};BitBuffer.MODE={EVICT:1,EXPAND:2};return BitBuffer}();JSMpeg.Source.Ajax=function(){"use strict";var AjaxSource=function(url,options){this.url=url;this.destination=null;this.request=null;this.streaming=false;this.completed=false;this.established=false;this.progress=0;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};AjaxSource.prototype.connect=function(destination){this.destination=destination};AjaxSource.prototype.start=function(){this.request=new XMLHttpRequest;this.request.onreadystatechange=function(){if(this.request.readyState===this.request.DONE&&this.request.status===200){this.onLoad(this.request.response)}}.bind(this);this.request.onprogress=this.onProgress.bind(this);this.request.open("GET",this.url);this.request.responseType="arraybuffer";this.request.send()};AjaxSource.prototype.resume=function(secondsHeadroom){};AjaxSource.prototype.destroy=function(){this.request.abort()};AjaxSource.prototype.onProgress=function(ev){this.progress=ev.loaded/ev.total};AjaxSource.prototype.onLoad=function(data){this.established=true;this.completed=true;this.progress=1;if(this.onEstablishedCallback){this.onEstablishedCallback(this)}if(this.onCompletedCallback){this.onCompletedCallback(this)}if(this.destination){this.destination.write(data)}};return AjaxSource}();JSMpeg.Source.Fetch=function(){"use strict";var FetchSource=function(url,options){this.url=url;this.destination=null;this.request=null;this.streaming=true;this.completed=false;this.established=false;this.progress=0;this.aborted=false;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};FetchSource.prototype.connect=function(destination){this.destination=destination};FetchSource.prototype.start=function(){var params={method:"GET",headers:new Headers,cache:"default"};self.fetch(this.url,params).then(function(res){if(res.ok&&(res.status>=200&&res.status<=299)){this.progress=1;this.established=true;return this.pump(res.body.getReader())}else{}}.bind(this)).catch(function(err){throw err})};FetchSource.prototype.pump=function(reader){return reader.read().then(function(result){if(result.done){this.completed=true}else{if(this.aborted){return reader.cancel()}if(this.destination){this.destination.write(result.value.buffer)}return this.pump(reader)}}.bind(this)).catch(function(err){throw err})};FetchSource.prototype.resume=function(secondsHeadroom){};FetchSource.prototype.abort=function(){this.aborted=true};return FetchSource}();JSMpeg.Source.AjaxProgressive=function(){"use strict";var AjaxProgressiveSource=function(url,options){this.url=url;this.destination=null;this.request=null;this.streaming=false;this.completed=false;this.established=false;this.progress=0;this.fileSize=0;this.loadedSize=0;this.chunkSize=options.chunkSize||1024*1024;this.isLoading=false;this.loadStartTime=0;this.throttled=options.throttled!==false;this.aborted=false;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};AjaxProgressiveSource.prototype.connect=function(destination){this.destination=destination};AjaxProgressiveSource.prototype.start=function(){this.request=new XMLHttpRequest;this.request.onreadystatechange=function(){if(this.request.readyState===this.request.DONE){this.fileSize=parseInt(this.request.getResponseHeader("Content-Length"));this.loadNextChunk()}}.bind(this);this.request.onprogress=this.onProgress.bind(this);this.request.open("HEAD",this.url);this.request.send()};AjaxProgressiveSource.prototype.resume=function(secondsHeadroom){if(this.isLoading||!this.throttled){return}var worstCaseLoadingTime=this.loadTime*8+2;if(worstCaseLoadingTime>secondsHeadroom){this.loadNextChunk()}};AjaxProgressiveSource.prototype.destroy=function(){this.request.abort();this.aborted=true};AjaxProgressiveSource.prototype.loadNextChunk=function(){var start=this.loadedSize,end=Math.min(this.loadedSize+this.chunkSize-1,this.fileSize-1);if(start>=this.fileSize||this.aborted){this.completed=true;if(this.onCompletedCallback){this.onCompletedCallback(this)}return}this.isLoading=true;this.loadStartTime=JSMpeg.Now();this.request=new XMLHttpRequest;this.request.onreadystatechange=function(){if(this.request.readyState===this.request.DONE&&this.request.status>=200&&this.request.status<300){this.onChunkLoad(this.request.response)}else if(this.request.readyState===this.request.DONE){if(this.loadFails++<3){this.loadNextChunk()}}}.bind(this);if(start===0){this.request.onprogress=this.onProgress.bind(this)}this.request.open("GET",this.url+"?"+start+"-"+end);this.request.setRequestHeader("Range","bytes="+start+"-"+end);this.request.responseType="arraybuffer";this.request.send()};AjaxProgressiveSource.prototype.onProgress=function(ev){this.progress=ev.loaded/ev.total};AjaxProgressiveSource.prototype.onChunkLoad=function(data){var isFirstChunk=!this.established;this.established=true;this.progress=1;this.loadedSize+=data.byteLength;this.loadFails=0;this.isLoading=false;if(isFirstChunk&&this.onEstablishedCallback){this.onEstablishedCallback(this)}if(this.destination){this.destination.write(data)}this.loadTime=JSMpeg.Now()-this.loadStartTime;if(!this.throttled){this.loadNextChunk()}};return AjaxProgressiveSource}();JSMpeg.Source.WebSocket=function(){"use strict";var WSSource=function(url,options){this.url=url;this.options=options;this.socket=null;this.streaming=true;this.callbacks={connect:[],data:[]};this.destination=null;this.reconnectInterval=options.reconnectInterval!==undefined?options.reconnectInterval:5;this.shouldAttemptReconnect=!!this.reconnectInterval;this.completed=false;this.established=false;this.progress=0;this.reconnectTimeoutId=0;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};WSSource.prototype.connect=function(destination){this.destination=destination};WSSource.prototype.destroy=function(){clearTimeout(this.reconnectTimeoutId);this.shouldAttemptReconnect=false;this.socket.close()};WSSource.prototype.start=function(){this.shouldAttemptReconnect=!!this.reconnectInterval;this.progress=0;this.established=false;if(this.options.protocols){this.socket=new WebSocket(this.url,this.options.protocols)}else{this.socket=new WebSocket(this.url)}this.socket.binaryType="arraybuffer";this.socket.onmessage=this.onMessage.bind(this);this.socket.onopen=this.onOpen.bind(this);this.socket.onerror=this.onClose.bind(this);this.socket.onclose=this.onClose.bind(this)};WSSource.prototype.resume=function(secondsHeadroom){};WSSource.prototype.onOpen=function(){this.progress=1};WSSource.prototype.onClose=function(){if(this.shouldAttemptReconnect){clearTimeout(this.reconnectTimeoutId);this.reconnectTimeoutId=setTimeout(function(){this.start()}.bind(this),this.reconnectInterval*1e3)}};WSSource.prototype.onMessage=function(ev){var isFirstChunk=!this.established;this.established=true;if(isFirstChunk&&this.onEstablishedCallback){this.onEstablishedCallback(this)}if(this.destination){this.destination.write(ev.data)}};return WSSource}();JSMpeg.Demuxer.TS=function(){"use strict";var TS=function(options){this.bits=null;this.leftoverBytes=null;this.guessVideoFrameEnd=true;this.pidsToStreamIds={};this.pesPacketInfo={};this.startTime=0;this.currentTime=0};TS.prototype.connect=function(streamId,destination){this.pesPacketInfo[streamId]={destination:destination,currentLength:0,totalLength:0,pts:0,buffers:[]}};TS.prototype.write=function(buffer){if(this.leftoverBytes){var totalLength=buffer.byteLength+this.leftoverBytes.byteLength;this.bits=new JSMpeg.BitBuffer(totalLength);this.bits.write([this.leftoverBytes,buffer])}else{this.bits=new JSMpeg.BitBuffer(buffer)}while(this.bits.has(188<<3)&&this.parsePacket()){}var leftoverCount=this.bits.byteLength-(this.bits.index>>3);this.leftoverBytes=leftoverCount>0?this.bits.bytes.subarray(this.bits.index>>3):null};TS.prototype.parsePacket=function(){if(this.bits.read(8)!==71){if(!this.resync()){return false}}var end=(this.bits.index>>3)+187;var transportError=this.bits.read(1),payloadStart=this.bits.read(1),transportPriority=this.bits.read(1),pid=this.bits.read(13),transportScrambling=this.bits.read(2),adaptationField=this.bits.read(2),continuityCounter=this.bits.read(4);var streamId=this.pidsToStreamIds[pid];if(payloadStart&&streamId){var pi=this.pesPacketInfo[streamId];if(pi&&pi.currentLength){this.packetComplete(pi)}}if(adaptationField&1){if(adaptationField&2){var adaptationFieldLength=this.bits.read(8);this.bits.skip(adaptationFieldLength<<3)}if(payloadStart&&this.bits.nextBytesAreStartCode()){this.bits.skip(24);streamId=this.bits.read(8);this.pidsToStreamIds[pid]=streamId;var packetLength=this.bits.read(16);this.bits.skip(8);var ptsDtsFlag=this.bits.read(2);this.bits.skip(6);var headerLength=this.bits.read(8);var payloadBeginIndex=this.bits.index+(headerLength<<3);var pi=this.pesPacketInfo[streamId];if(pi){var pts=0;if(ptsDtsFlag&2){this.bits.skip(4);var p32_30=this.bits.read(3);this.bits.skip(1);var p29_15=this.bits.read(15);this.bits.skip(1);var p14_0=this.bits.read(15);this.bits.skip(1);pts=(p32_30*1073741824+p29_15*32768+p14_0)/9e4;this.currentTime=pts;if(this.startTime===-1){this.startTime=pts}}var payloadLength=packetLength?packetLength-headerLength-3:0;this.packetStart(pi,pts,payloadLength)}this.bits.index=payloadBeginIndex}if(streamId){var pi=this.pesPacketInfo[streamId];if(pi){var start=this.bits.index>>3;var complete=this.packetAddData(pi,start,end);var hasPadding=!payloadStart&&adaptationField&2;if(complete||this.guessVideoFrameEnd&&hasPadding){this.packetComplete(pi)}}}}this.bits.index=end<<3;return true};TS.prototype.resync=function(){if(!this.bits.has(188*6<<3)){return false}var byteIndex=this.bits.index>>3;for(var i=0;i<187;i++){if(this.bits.bytes[byteIndex+i]===71){var foundSync=true;for(var j=1;j<5;j++){if(this.bits.bytes[byteIndex+i+188*j]!==71){foundSync=false;break}}if(foundSync){this.bits.index=byteIndex+i+1<<3;return true}}}console.warn("JSMpeg: Possible garbage data. Skipping.");this.bits.skip(187<<3);return false};TS.prototype.packetStart=function(pi,pts,payloadLength){pi.totalLength=payloadLength;pi.currentLength=0;pi.pts=pts};TS.prototype.packetAddData=function(pi,start,end){pi.buffers.push(this.bits.bytes.subarray(start,end));pi.currentLength+=end-start;var complete=pi.totalLength!==0&&pi.currentLength>=pi.totalLength;return complete};TS.prototype.packetComplete=function(pi){pi.destination.write(pi.pts,pi.buffers);pi.totalLength=0;pi.currentLength=0;pi.buffers=[]};TS.STREAM={PACK_HEADER:186,SYSTEM_HEADER:187,PROGRAM_MAP:188,PRIVATE_1:189,PADDING:190,PRIVATE_2:191,AUDIO_1:192,VIDEO_1:224,DIRECTORY:255};return TS}();JSMpeg.Decoder.Base=function(){"use strict";var BaseDecoder=function(options){this.destination=null;this.canPlay=false;this.collectTimestamps=!options.streaming;this.bytesWritten=0;this.timestamps=[];this.timestampIndex=0;this.startTime=0;this.decodedTime=0;Object.defineProperty(this,"currentTime",{get:this.getCurrentTime})};BaseDecoder.prototype.destroy=function(){};BaseDecoder.prototype.connect=function(destination){this.destination=destination};BaseDecoder.prototype.bufferGetIndex=function(){return this.bits.index};BaseDecoder.prototype.bufferSetIndex=function(index){this.bits.index=index};BaseDecoder.prototype.bufferWrite=function(buffers){return this.bits.write(buffers)};BaseDecoder.prototype.write=function(pts,buffers){if(this.collectTimestamps){if(this.timestamps.length===0){this.startTime=pts;this.decodedTime=pts}this.timestamps.push({index:this.bytesWritten<<3,time:pts})}this.bytesWritten+=this.bufferWrite(buffers);this.canPlay=true};BaseDecoder.prototype.seek=function(time){if(!this.collectTimestamps){return}this.timestampIndex=0;for(var i=0;itime){break}this.timestampIndex=i}var ts=this.timestamps[this.timestampIndex];if(ts){this.bufferSetIndex(ts.index);this.decodedTime=ts.time}else{this.bufferSetIndex(0);this.decodedTime=this.startTime}};BaseDecoder.prototype.decode=function(){this.advanceDecodedTime(0)};BaseDecoder.prototype.advanceDecodedTime=function(seconds){if(this.collectTimestamps){var newTimestampIndex=-1;var currentIndex=this.bufferGetIndex();for(var i=this.timestampIndex;icurrentIndex){break}newTimestampIndex=i}if(newTimestampIndex!==-1&&newTimestampIndex!==this.timestampIndex){this.timestampIndex=newTimestampIndex;this.decodedTime=this.timestamps[this.timestampIndex].time;return}}this.decodedTime+=seconds};BaseDecoder.prototype.getCurrentTime=function(){return this.decodedTime};return BaseDecoder}();JSMpeg.Decoder.MPEG1Video=function(){"use strict";var MPEG1=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onVideoDecode;var bufferSize=options.videoBufferSize||512*1024;var bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.bits=new JSMpeg.BitBuffer(bufferSize,bufferMode);this.customIntraQuantMatrix=new Uint8Array(64);this.customNonIntraQuantMatrix=new Uint8Array(64);this.blockData=new Int32Array(64);this.currentFrame=0;this.decodeFirstFrame=options.decodeFirstFrame!==false};MPEG1.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MPEG1.prototype.constructor=MPEG1;MPEG1.prototype.write=function(pts,buffers){JSMpeg.Decoder.Base.prototype.write.call(this,pts,buffers);if(!this.hasSequenceHeader){if(this.bits.findStartCode(MPEG1.START.SEQUENCE)===-1){return false}this.decodeSequenceHeader();if(this.decodeFirstFrame){this.decode()}}};MPEG1.prototype.decode=function(){var startTime=JSMpeg.Now();if(!this.hasSequenceHeader){return false}if(this.bits.findStartCode(MPEG1.START.PICTURE)===-1){var bufferedBytes=this.bits.byteLength-(this.bits.index>>3);return false}this.decodePicture();this.advanceDecodedTime(1/this.frameRate);var elapsedTime=JSMpeg.Now()-startTime;if(this.onDecodeCallback){this.onDecodeCallback(this,elapsedTime)}return true};MPEG1.prototype.readHuffman=function(codeTable){var state=0;do{state=codeTable[state+this.bits.read(1)]}while(state>=0&&codeTable[state]!==0);return codeTable[state+2]};MPEG1.prototype.frameRate=30;MPEG1.prototype.decodeSequenceHeader=function(){var newWidth=this.bits.read(12),newHeight=this.bits.read(12);this.bits.skip(4);this.frameRate=MPEG1.PICTURE_RATE[this.bits.read(4)];this.bits.skip(18+1+10+1);if(newWidth!==this.width||newHeight!==this.height){this.width=newWidth;this.height=newHeight;this.initBuffers();if(this.destination){this.destination.resize(newWidth,newHeight)}}if(this.bits.read(1)){for(var i=0;i<64;i++){this.customIntraQuantMatrix[MPEG1.ZIG_ZAG[i]]=this.bits.read(8)}this.intraQuantMatrix=this.customIntraQuantMatrix}if(this.bits.read(1)){for(var i=0;i<64;i++){var idx=MPEG1.ZIG_ZAG[i];this.customNonIntraQuantMatrix[idx]=this.bits.read(8)}this.nonIntraQuantMatrix=this.customNonIntraQuantMatrix}this.hasSequenceHeader=true};MPEG1.prototype.initBuffers=function(){this.intraQuantMatrix=MPEG1.DEFAULT_INTRA_QUANT_MATRIX;this.nonIntraQuantMatrix=MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX;this.mbWidth=this.width+15>>4;this.mbHeight=this.height+15>>4;this.mbSize=this.mbWidth*this.mbHeight;this.codedWidth=this.mbWidth<<4;this.codedHeight=this.mbHeight<<4;this.codedSize=this.codedWidth*this.codedHeight;this.halfWidth=this.mbWidth<<3;this.halfHeight=this.mbHeight<<3;this.currentY=new Uint8ClampedArray(this.codedSize);this.currentY32=new Uint32Array(this.currentY.buffer);this.currentCr=new Uint8ClampedArray(this.codedSize>>2);this.currentCr32=new Uint32Array(this.currentCr.buffer);this.currentCb=new Uint8ClampedArray(this.codedSize>>2);this.currentCb32=new Uint32Array(this.currentCb.buffer);this.forwardY=new Uint8ClampedArray(this.codedSize);this.forwardY32=new Uint32Array(this.forwardY.buffer);this.forwardCr=new Uint8ClampedArray(this.codedSize>>2);this.forwardCr32=new Uint32Array(this.forwardCr.buffer);this.forwardCb=new Uint8ClampedArray(this.codedSize>>2);this.forwardCb32=new Uint32Array(this.forwardCb.buffer)};MPEG1.prototype.currentY=null;MPEG1.prototype.currentCr=null;MPEG1.prototype.currentCb=null;MPEG1.prototype.pictureType=0;MPEG1.prototype.forwardY=null;MPEG1.prototype.forwardCr=null;MPEG1.prototype.forwardCb=null;MPEG1.prototype.fullPelForward=false;MPEG1.prototype.forwardFCode=0;MPEG1.prototype.forwardRSize=0;MPEG1.prototype.forwardF=0;MPEG1.prototype.decodePicture=function(skipOutput){this.currentFrame++;this.bits.skip(10);this.pictureType=this.bits.read(3);this.bits.skip(16);if(this.pictureType<=0||this.pictureType>=MPEG1.PICTURE_TYPE.B){return}if(this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){this.fullPelForward=this.bits.read(1);this.forwardFCode=this.bits.read(3);if(this.forwardFCode===0){return}this.forwardRSize=this.forwardFCode-1;this.forwardF=1<=MPEG1.START.SLICE_FIRST&&code<=MPEG1.START.SLICE_LAST){this.decodeSlice(code&255);code=this.bits.findNextStartCode()}if(code!==-1){this.bits.rewind(32)}if(this.destination){this.destination.render(this.currentY,this.currentCr,this.currentCb,true)}if(this.pictureType===MPEG1.PICTURE_TYPE.INTRA||this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){var tmpY=this.forwardY,tmpY32=this.forwardY32,tmpCr=this.forwardCr,tmpCr32=this.forwardCr32,tmpCb=this.forwardCb,tmpCb32=this.forwardCb32;this.forwardY=this.currentY;this.forwardY32=this.currentY32;this.forwardCr=this.currentCr;this.forwardCr32=this.currentCr32;this.forwardCb=this.currentCb;this.forwardCb32=this.currentCb32;this.currentY=tmpY;this.currentY32=tmpY32;this.currentCr=tmpCr;this.currentCr32=tmpCr32;this.currentCb=tmpCb;this.currentCb32=tmpCb32}};MPEG1.prototype.quantizerScale=0;MPEG1.prototype.sliceBegin=false;MPEG1.prototype.decodeSlice=function(slice){this.sliceBegin=true;this.macroblockAddress=(slice-1)*this.mbWidth-1;this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0;this.dcPredictorY=128;this.dcPredictorCr=128;this.dcPredictorCb=128;this.quantizerScale=this.bits.read(5);while(this.bits.read(1)){this.bits.skip(8)}do{this.decodeMacroblock()}while(!this.bits.nextBytesAreStartCode())};MPEG1.prototype.macroblockAddress=0;MPEG1.prototype.mbRow=0;MPEG1.prototype.mbCol=0;MPEG1.prototype.macroblockType=0;MPEG1.prototype.macroblockIntra=false;MPEG1.prototype.macroblockMotFw=false;MPEG1.prototype.motionFwH=0;MPEG1.prototype.motionFwV=0;MPEG1.prototype.motionFwHPrev=0;MPEG1.prototype.motionFwVPrev=0;MPEG1.prototype.decodeMacroblock=function(){var increment=0,t=this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);while(t===34){t=this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT)}while(t===35){increment+=33;t=this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT)}increment+=t;if(this.sliceBegin){this.sliceBegin=false;this.macroblockAddress+=increment}else{if(this.macroblockAddress+increment>=this.mbSize){return}if(increment>1){this.dcPredictorY=128;this.dcPredictorCr=128;this.dcPredictorCb=128;if(this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0}}while(increment>1){this.macroblockAddress++;this.mbRow=this.macroblockAddress/this.mbWidth|0;this.mbCol=this.macroblockAddress%this.mbWidth;this.copyMacroblock(this.motionFwH,this.motionFwV,this.forwardY,this.forwardCr,this.forwardCb);increment--}this.macroblockAddress++}this.mbRow=this.macroblockAddress/this.mbWidth|0;this.mbCol=this.macroblockAddress%this.mbWidth;var mbTable=MPEG1.MACROBLOCK_TYPE[this.pictureType];this.macroblockType=this.readHuffman(mbTable);this.macroblockIntra=this.macroblockType&1;this.macroblockMotFw=this.macroblockType&8;if((this.macroblockType&16)!==0){this.quantizerScale=this.bits.read(5)}if(this.macroblockIntra){this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0}else{this.dcPredictorY=128;this.dcPredictorCr=128;this.dcPredictorCb=128;this.decodeMotionVectors();this.copyMacroblock(this.motionFwH,this.motionFwV,this.forwardY,this.forwardCr,this.forwardCb)}var cbp=(this.macroblockType&2)!==0?this.readHuffman(MPEG1.CODE_BLOCK_PATTERN):this.macroblockIntra?63:0;for(var block=0,mask=32;block<6;block++){if((cbp&mask)!==0){this.decodeBlock(block)}mask>>=1}};MPEG1.prototype.decodeMotionVectors=function(){var code,d,r=0;if(this.macroblockMotFw){code=this.readHuffman(MPEG1.MOTION);if(code!==0&&this.forwardF!==1){r=this.bits.read(this.forwardRSize);d=(Math.abs(code)-1<(this.forwardF<<4)-1){this.motionFwHPrev-=this.forwardF<<5}else if(this.motionFwHPrev<-this.forwardF<<4){this.motionFwHPrev+=this.forwardF<<5}this.motionFwH=this.motionFwHPrev;if(this.fullPelForward){this.motionFwH<<=1}code=this.readHuffman(MPEG1.MOTION);if(code!==0&&this.forwardF!==1){r=this.bits.read(this.forwardRSize);d=(Math.abs(code)-1<(this.forwardF<<4)-1){this.motionFwVPrev-=this.forwardF<<5}else if(this.motionFwVPrev<-this.forwardF<<4){this.motionFwVPrev+=this.forwardF<<5}this.motionFwV=this.motionFwVPrev;if(this.fullPelForward){this.motionFwV<<=1}}else if(this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0}};MPEG1.prototype.copyMacroblock=function(motionH,motionV,sY,sCr,sCb){var width,scan,H,V,oddH,oddV,src,dest,last;var dY=this.currentY32,dCb=this.currentCb32,dCr=this.currentCr32;width=this.codedWidth;scan=width-16;H=motionH>>1;V=motionV>>1;oddH=(motionH&1)===1;oddV=(motionV&1)===1;src=((this.mbRow<<4)+V)*width+(this.mbCol<<4)+H;dest=this.mbRow*width+this.mbCol<<2;last=dest+(width<<2);var x,y1,y2,y;if(oddH){if(oddV){while(dest>2&255;y1=sY[src]+sY[src+width];src++;y|=y1+y2+2<<6&65280;y2=sY[src]+sY[src+width];src++;y|=y1+y2+2<<14&16711680;y1=sY[src]+sY[src+width];src++;y|=y1+y2+2<<22&4278190080;dY[dest++]=y}dest+=scan>>2;src+=scan-1}}else{while(dest>1&255;y1=sY[src++];y|=y1+y2+1<<7&65280;y2=sY[src++];y|=y1+y2+1<<15&16711680;y1=sY[src++];y|=y1+y2+1<<23&4278190080;dY[dest++]=y}dest+=scan>>2;src+=scan-1}}}else{if(oddV){while(dest>1&255;src++;y|=sY[src]+sY[src+width]+1<<7&65280;src++;y|=sY[src]+sY[src+width]+1<<15&16711680;src++;y|=sY[src]+sY[src+width]+1<<23&4278190080;src++;dY[dest++]=y}dest+=scan>>2;src+=scan}}else{while(dest>2;src+=scan}}}width=this.halfWidth;scan=width-8;H=motionH/2>>1;V=motionV/2>>1;oddH=(motionH/2&1)===1;oddV=(motionV/2&1)===1;src=((this.mbRow<<3)+V)*width+(this.mbCol<<3)+H;dest=this.mbRow*width+this.mbCol<<1;last=dest+(width<<1);var cr1,cr2,cr,cb1,cb2,cb;if(oddH){if(oddV){while(dest>2&255;cb=cb1+cb2+2>>2&255;cr1=sCr[src]+sCr[src+width];cb1=sCb[src]+sCb[src+width];src++;cr|=cr1+cr2+2<<6&65280;cb|=cb1+cb2+2<<6&65280;cr2=sCr[src]+sCr[src+width];cb2=sCb[src]+sCb[src+width];src++;cr|=cr1+cr2+2<<14&16711680;cb|=cb1+cb2+2<<14&16711680;cr1=sCr[src]+sCr[src+width];cb1=sCb[src]+sCb[src+width];src++;cr|=cr1+cr2+2<<22&4278190080;cb|=cb1+cb2+2<<22&4278190080;dCr[dest]=cr;dCb[dest]=cb;dest++}dest+=scan>>2;src+=scan-1}}else{while(dest>1&255;cb=cb1+cb2+1>>1&255;cr1=sCr[src];cb1=sCb[src++];cr|=cr1+cr2+1<<7&65280;cb|=cb1+cb2+1<<7&65280;cr2=sCr[src];cb2=sCb[src++];cr|=cr1+cr2+1<<15&16711680;cb|=cb1+cb2+1<<15&16711680;cr1=sCr[src];cb1=sCb[src++];cr|=cr1+cr2+1<<23&4278190080;cb|=cb1+cb2+1<<23&4278190080;dCr[dest]=cr;dCb[dest]=cb;dest++}dest+=scan>>2;src+=scan-1}}}else{if(oddV){while(dest>1&255;cb=sCb[src]+sCb[src+width]+1>>1&255;src++;cr|=sCr[src]+sCr[src+width]+1<<7&65280;cb|=sCb[src]+sCb[src+width]+1<<7&65280;src++;cr|=sCr[src]+sCr[src+width]+1<<15&16711680;cb|=sCb[src]+sCb[src+width]+1<<15&16711680;src++;cr|=sCr[src]+sCr[src+width]+1<<23&4278190080;cb|=sCb[src]+sCb[src+width]+1<<23&4278190080;src++;dCr[dest]=cr;dCb[dest]=cb;dest++}dest+=scan>>2;src+=scan}}else{while(dest>2;src+=scan}}}};MPEG1.prototype.dcPredictorY=0;MPEG1.prototype.dcPredictorCr=0;MPEG1.prototype.dcPredictorCb=0;MPEG1.prototype.blockData=null;MPEG1.prototype.decodeBlock=function(block){var n=0,quantMatrix;if(this.macroblockIntra){var predictor,dctSize;if(block<4){predictor=this.dcPredictorY;dctSize=this.readHuffman(MPEG1.DCT_DC_SIZE_LUMINANCE)}else{predictor=block===4?this.dcPredictorCr:this.dcPredictorCb;dctSize=this.readHuffman(MPEG1.DCT_DC_SIZE_CHROMINANCE)}if(dctSize>0){var differential=this.bits.read(dctSize);if((differential&1<0&&this.bits.read(1)===0){break}if(coeff===65535){run=this.bits.read(6);level=this.bits.read(8);if(level===0){level=this.bits.read(8)}else if(level===128){level=this.bits.read(8)-256}else if(level>128){level=level-256}}else{run=coeff>>8;level=coeff&255;if(this.bits.read(1)){level=-level}}n+=run;var dezigZagged=MPEG1.ZIG_ZAG[n];n++;level<<=1;if(!this.macroblockIntra){level+=level<0?-1:1}level=level*this.quantizerScale*quantMatrix[dezigZagged]>>4;if((level&1)===0){level-=level>0?1:-1}if(level>2047){level=2047}else if(level<-2048){level=-2048}this.blockData[dezigZagged]=level*MPEG1.PREMULTIPLIER_MATRIX[dezigZagged]}var destArray,destIndex,scan;if(block<4){destArray=this.currentY;scan=this.codedWidth-8;destIndex=this.mbRow*this.codedWidth+this.mbCol<<4;if((block&1)!==0){destIndex+=8}if((block&2)!==0){destIndex+=this.codedWidth<<3}}else{destArray=block===4?this.currentCb:this.currentCr;scan=(this.codedWidth>>1)-8;destIndex=(this.mbRow*this.codedWidth<<2)+(this.mbCol<<3)}if(this.macroblockIntra){if(n===1){MPEG1.CopyValueToDestination(this.blockData[0]+128>>8,destArray,destIndex,scan);this.blockData[0]=0}else{MPEG1.IDCT(this.blockData);MPEG1.CopyBlockToDestination(this.blockData,destArray,destIndex,scan);JSMpeg.Fill(this.blockData,0)}}else{if(n===1){MPEG1.AddValueToDestination(this.blockData[0]+128>>8,destArray,destIndex,scan);this.blockData[0]=0}else{MPEG1.IDCT(this.blockData);MPEG1.AddBlockToDestination(this.blockData,destArray,destIndex,scan);JSMpeg.Fill(this.blockData,0)}}n=0};MPEG1.CopyBlockToDestination=function(block,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]=block[n+0];dest[index+1]=block[n+1];dest[index+2]=block[n+2];dest[index+3]=block[n+3];dest[index+4]=block[n+4];dest[index+5]=block[n+5];dest[index+6]=block[n+6];dest[index+7]=block[n+7]}};MPEG1.AddBlockToDestination=function(block,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]+=block[n+0];dest[index+1]+=block[n+1];dest[index+2]+=block[n+2];dest[index+3]+=block[n+3];dest[index+4]+=block[n+4];dest[index+5]+=block[n+5];dest[index+6]+=block[n+6];dest[index+7]+=block[n+7]}};MPEG1.CopyValueToDestination=function(value,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]=value;dest[index+1]=value;dest[index+2]=value;dest[index+3]=value;dest[index+4]=value;dest[index+5]=value;dest[index+6]=value;dest[index+7]=value}};MPEG1.AddValueToDestination=function(value,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]+=value;dest[index+1]+=value;dest[index+2]+=value;dest[index+3]+=value;dest[index+4]+=value;dest[index+5]+=value;dest[index+6]+=value;dest[index+7]+=value}};MPEG1.IDCT=function(block){var b1,b3,b4,b6,b7,tmp1,tmp2,m0,x0,x1,x2,x3,x4,y3,y4,y5,y6,y7;for(var i=0;i<8;++i){b1=block[4*8+i];b3=block[2*8+i]+block[6*8+i];b4=block[5*8+i]-block[3*8+i];tmp1=block[1*8+i]+block[7*8+i];tmp2=block[3*8+i]+block[5*8+i];b6=block[1*8+i]-block[7*8+i];b7=tmp1+tmp2;m0=block[0*8+i];x4=(b6*473-b4*196+128>>8)-b7;x0=x4-((tmp1-tmp2)*362+128>>8);x1=m0-b1;x2=((block[2*8+i]-block[6*8+i])*362+128>>8)-b3;x3=m0+b1;y3=x1+x2;y4=x3+b3;y5=x1-x2;y6=x3-b3;y7=-x0-(b4*473+b6*196+128>>8);block[0*8+i]=b7+y4;block[1*8+i]=x4+y3;block[2*8+i]=y5-x0;block[3*8+i]=y6-y7;block[4*8+i]=y6+y7;block[5*8+i]=x0+y5;block[6*8+i]=y3-x4;block[7*8+i]=y4-b7}for(var i=0;i<64;i+=8){b1=block[4+i];b3=block[2+i]+block[6+i];b4=block[5+i]-block[3+i];tmp1=block[1+i]+block[7+i];tmp2=block[3+i]+block[5+i];b6=block[1+i]-block[7+i];b7=tmp1+tmp2;m0=block[0+i];x4=(b6*473-b4*196+128>>8)-b7;x0=x4-((tmp1-tmp2)*362+128>>8);x1=m0-b1;x2=((block[2+i]-block[6+i])*362+128>>8)-b3;x3=m0+b1;y3=x1+x2;y4=x3+b3;y5=x1-x2;y6=x3-b3;y7=-x0-(b4*473+b6*196+128>>8);block[0+i]=b7+y4+128>>8;block[1+i]=x4+y3+128>>8;block[2+i]=y5-x0+128>>8;block[3+i]=y6-y7+128>>8;block[4+i]=y6+y7+128>>8;block[5+i]=x0+y5+128>>8;block[6+i]=y3-x4+128>>8;block[7+i]=y4-b7+128>>8}};MPEG1.PICTURE_RATE=[0,23.976,24,25,29.97,30,50,59.94,60,0,0,0,0,0,0,0];MPEG1.ZIG_ZAG=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]);MPEG1.DEFAULT_INTRA_QUANT_MATRIX=new Uint8Array([8,16,19,22,26,27,29,34,16,16,22,24,27,29,34,37,19,22,26,27,29,34,34,38,22,22,26,27,29,34,37,40,22,26,27,29,32,35,40,48,26,27,29,32,35,40,48,58,26,27,29,34,38,46,56,69,27,29,35,38,46,56,69,83]);MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX=new Uint8Array([16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16]);MPEG1.PREMULTIPLIER_MATRIX=new Uint8Array([32,44,42,38,32,25,17,9,44,62,58,52,44,35,24,12,42,58,55,49,42,33,23,12,38,52,49,44,38,30,20,10,32,44,42,38,32,25,17,9,25,35,33,30,25,20,14,7,17,24,23,20,17,14,9,5,9,12,12,10,9,7,5,2]);MPEG1.MACROBLOCK_ADDRESS_INCREMENT=new Int16Array([1*3,2*3,0,3*3,4*3,0,0,0,1,5*3,6*3,0,7*3,8*3,0,9*3,10*3,0,11*3,12*3,0,0,0,3,0,0,2,13*3,14*3,0,15*3,16*3,0,0,0,5,0,0,4,17*3,18*3,0,19*3,20*3,0,0,0,7,0,0,6,21*3,22*3,0,23*3,24*3,0,25*3,26*3,0,27*3,28*3,0,-1,29*3,0,-1,30*3,0,31*3,32*3,0,33*3,34*3,0,35*3,36*3,0,37*3,38*3,0,0,0,9,0,0,8,39*3,40*3,0,41*3,42*3,0,43*3,44*3,0,45*3,46*3,0,0,0,15,0,0,14,0,0,13,0,0,12,0,0,11,0,0,10,47*3,-1,0,-1,48*3,0,49*3,50*3,0,51*3,52*3,0,53*3,54*3,0,55*3,56*3,0,57*3,58*3,0,59*3,60*3,0,61*3,-1,0,-1,62*3,0,63*3,64*3,0,65*3,66*3,0,67*3,68*3,0,69*3,70*3,0,71*3,72*3,0,73*3,74*3,0,0,0,21,0,0,20,0,0,19,0,0,18,0,0,17,0,0,16,0,0,35,0,0,34,0,0,33,0,0,32,0,0,31,0,0,30,0,0,29,0,0,28,0,0,27,0,0,26,0,0,25,0,0,24,0,0,23,0,0,22]);MPEG1.MACROBLOCK_TYPE_INTRA=new Int8Array([1*3,2*3,0,-1,3*3,0,0,0,1,0,0,17]);MPEG1.MACROBLOCK_TYPE_PREDICTIVE=new Int8Array([1*3,2*3,0,3*3,4*3,0,0,0,10,5*3,6*3,0,0,0,2,7*3,8*3,0,0,0,8,9*3,10*3,0,11*3,12*3,0,-1,13*3,0,0,0,18,0,0,26,0,0,1,0,0,17]);MPEG1.MACROBLOCK_TYPE_B=new Int8Array([1*3,2*3,0,3*3,5*3,0,4*3,6*3,0,8*3,7*3,0,0,0,12,9*3,10*3,0,0,0,14,13*3,14*3,0,12*3,11*3,0,0,0,4,0,0,6,18*3,16*3,0,15*3,17*3,0,0,0,8,0,0,10,-1,19*3,0,0,0,1,20*3,21*3,0,0,0,30,0,0,17,0,0,22,0,0,26]);MPEG1.MACROBLOCK_TYPE=[null,MPEG1.MACROBLOCK_TYPE_INTRA,MPEG1.MACROBLOCK_TYPE_PREDICTIVE,MPEG1.MACROBLOCK_TYPE_B];MPEG1.CODE_BLOCK_PATTERN=new Int16Array([2*3,1*3,0,3*3,6*3,0,4*3,5*3,0,8*3,11*3,0,12*3,13*3,0,9*3,7*3,0,10*3,14*3,0,20*3,19*3,0,18*3,16*3,0,23*3,17*3,0,27*3,25*3,0,21*3,28*3,0,15*3,22*3,0,24*3,26*3,0,0,0,60,35*3,40*3,0,44*3,48*3,0,38*3,36*3,0,42*3,47*3,0,29*3,31*3,0,39*3,32*3,0,0,0,32,45*3,46*3,0,33*3,41*3,0,43*3,34*3,0,0,0,4,30*3,37*3,0,0,0,8,0,0,16,0,0,44,50*3,56*3,0,0,0,28,0,0,52,0,0,62,61*3,59*3,0,52*3,60*3,0,0,0,1,55*3,54*3,0,0,0,61,0,0,56,57*3,58*3,0,0,0,2,0,0,40,51*3,62*3,0,0,0,48,64*3,63*3,0,49*3,53*3,0,0,0,20,0,0,12,80*3,83*3,0,0,0,63,77*3,75*3,0,65*3,73*3,0,84*3,66*3,0,0,0,24,0,0,36,0,0,3,69*3,87*3,0,81*3,79*3,0,68*3,71*3,0,70*3,78*3,0,67*3,76*3,0,72*3,74*3,0,86*3,85*3,0,88*3,82*3,0,-1,94*3,0,95*3,97*3,0,0,0,33,0,0,9,106*3,110*3,0,102*3,116*3,0,0,0,5,0,0,10,93*3,89*3,0,0,0,6,0,0,18,0,0,17,0,0,34,113*3,119*3,0,103*3,104*3,0,90*3,92*3,0,109*3,107*3,0,117*3,118*3,0,101*3,99*3,0,98*3,96*3,0,100*3,91*3,0,114*3,115*3,0,105*3,108*3,0,112*3,111*3,0,121*3,125*3,0,0,0,41,0,0,14,0,0,21,124*3,122*3,0,120*3,123*3,0,0,0,11,0,0,19,0,0,7,0,0,35,0,0,13,0,0,50,0,0,49,0,0,58,0,0,37,0,0,25,0,0,45,0,0,57,0,0,26,0,0,29,0,0,38,0,0,53,0,0,23,0,0,43,0,0,46,0,0,42,0,0,22,0,0,54,0,0,51,0,0,15,0,0,30,0,0,39,0,0,47,0,0,55,0,0,27,0,0,59,0,0,31]);MPEG1.MOTION=new Int16Array([1*3,2*3,0,4*3,3*3,0,0,0,0,6*3,5*3,0,8*3,7*3,0,0,0,-1,0,0,1,9*3,10*3,0,12*3,11*3,0,0,0,2,0,0,-2,14*3,15*3,0,16*3,13*3,0,20*3,18*3,0,0,0,3,0,0,-3,17*3,19*3,0,-1,23*3,0,27*3,25*3,0,26*3,21*3,0,24*3,22*3,0,32*3,28*3,0,29*3,31*3,0,-1,33*3,0,36*3,35*3,0,0,0,-4,30*3,34*3,0,0,0,4,0,0,-7,0,0,5,37*3,41*3,0,0,0,-5,0,0,7,38*3,40*3,0,42*3,39*3,0,0,0,-6,0,0,6,51*3,54*3,0,50*3,49*3,0,45*3,46*3,0,52*3,47*3,0,43*3,53*3,0,44*3,48*3,0,0,0,10,0,0,9,0,0,8,0,0,-8,57*3,66*3,0,0,0,-9,60*3,64*3,0,56*3,61*3,0,55*3,62*3,0,58*3,63*3,0,0,0,-10,59*3,65*3,0,0,0,12,0,0,16,0,0,13,0,0,14,0,0,11,0,0,15,0,0,-16,0,0,-12,0,0,-14,0,0,-15,0,0,-11,0,0,-13]);MPEG1.DCT_DC_SIZE_LUMINANCE=new Int8Array([2*3,1*3,0,6*3,5*3,0,3*3,4*3,0,0,0,1,0,0,2,9*3,8*3,0,7*3,10*3,0,0,0,0,12*3,11*3,0,0,0,4,0,0,3,13*3,14*3,0,0,0,5,0,0,6,16*3,15*3,0,17*3,-1,0,0,0,7,0,0,8]);MPEG1.DCT_DC_SIZE_CHROMINANCE=new Int8Array([2*3,1*3,0,4*3,3*3,0,6*3,5*3,0,8*3,7*3,0,0,0,2,0,0,1,0,0,0,10*3,9*3,0,0,0,3,12*3,11*3,0,0,0,4,14*3,13*3,0,0,0,5,16*3,15*3,0,0,0,6,17*3,-1,0,0,0,7,0,0,8]);MPEG1.DCT_COEFF=new Int32Array([1*3,2*3,0,4*3,3*3,0,0,0,1,7*3,8*3,0,6*3,5*3,0,13*3,9*3,0,11*3,10*3,0,14*3,12*3,0,0,0,257,20*3,22*3,0,18*3,21*3,0,16*3,19*3,0,0,0,513,17*3,15*3,0,0,0,2,0,0,3,27*3,25*3,0,29*3,31*3,0,24*3,26*3,0,32*3,30*3,0,0,0,1025,23*3,28*3,0,0,0,769,0,0,258,0,0,1793,0,0,65535,0,0,1537,37*3,36*3,0,0,0,1281,35*3,34*3,0,39*3,38*3,0,33*3,42*3,0,40*3,41*3,0,52*3,50*3,0,54*3,53*3,0,48*3,49*3,0,43*3,45*3,0,46*3,44*3,0,0,0,2049,0,0,4,0,0,514,0,0,2305,51*3,47*3,0,55*3,57*3,0,60*3,56*3,0,59*3,58*3,0,61*3,62*3,0,0,0,2561,0,0,3329,0,0,6,0,0,259,0,0,5,0,0,770,0,0,2817,0,0,3073,76*3,75*3,0,67*3,70*3,0,73*3,71*3,0,78*3,74*3,0,72*3,77*3,0,69*3,64*3,0,68*3,63*3,0,66*3,65*3,0,81*3,87*3,0,91*3,80*3,0,82*3,79*3,0,83*3,86*3,0,93*3,92*3,0,84*3,85*3,0,90*3,94*3,0,88*3,89*3,0,0,0,515,0,0,260,0,0,7,0,0,1026,0,0,1282,0,0,4097,0,0,3841,0,0,3585,105*3,107*3,0,111*3,114*3,0,104*3,97*3,0,125*3,119*3,0,96*3,98*3,0,-1,123*3,0,95*3,101*3,0,106*3,121*3,0,99*3,102*3,0,113*3,103*3,0,112*3,116*3,0,110*3,100*3,0,124*3,115*3,0,117*3,122*3,0,109*3,118*3,0,120*3,108*3,0,127*3,136*3,0,139*3,140*3,0,130*3,126*3,0,145*3,146*3,0,128*3,129*3,0,0,0,2050,132*3,134*3,0,155*3,154*3,0,0,0,8,137*3,133*3,0,143*3,144*3,0,151*3,138*3,0,142*3,141*3,0,0,0,10,0,0,9,0,0,11,0,0,5377,0,0,1538,0,0,771,0,0,5121,0,0,1794,0,0,4353,0,0,4609,0,0,4865,148*3,152*3,0,0,0,1027,153*3,150*3,0,0,0,261,131*3,135*3,0,0,0,516,149*3,147*3,0,172*3,173*3,0,162*3,158*3,0,170*3,161*3,0,168*3,166*3,0,157*3,179*3,0,169*3,167*3,0,174*3,171*3,0,178*3,177*3,0,156*3,159*3,0,164*3,165*3,0,183*3,182*3,0,175*3,176*3,0,0,0,263,0,0,2562,0,0,2306,0,0,5633,0,0,5889,0,0,6401,0,0,6145,0,0,1283,0,0,772,0,0,13,0,0,12,0,0,14,0,0,15,0,0,517,0,0,6657,0,0,262,180*3,181*3,0,160*3,163*3,0,196*3,199*3,0,0,0,27,203*3,185*3,0,202*3,201*3,0,0,0,19,0,0,22,197*3,207*3,0,0,0,18,191*3,192*3,0,188*3,190*3,0,0,0,20,184*3,194*3,0,0,0,21,186*3,193*3,0,0,0,23,204*3,198*3,0,0,0,25,0,0,24,200*3,205*3,0,0,0,31,0,0,30,0,0,28,0,0,29,0,0,26,0,0,17,0,0,16,189*3,206*3,0,187*3,195*3,0,218*3,211*3,0,0,0,37,215*3,216*3,0,0,0,36,210*3,212*3,0,0,0,34,213*3,209*3,0,221*3,222*3,0,219*3,208*3,0,217*3,214*3,0,223*3,220*3,0,0,0,35,0,0,267,0,0,40,0,0,268,0,0,266,0,0,32,0,0,264,0,0,265,0,0,38,0,0,269,0,0,270,0,0,33,0,0,39,0,0,7937,0,0,6913,0,0,7681,0,0,4098,0,0,7425,0,0,7169,0,0,271,0,0,274,0,0,273,0,0,272,0,0,1539,0,0,2818,0,0,3586,0,0,3330,0,0,3074,0,0,3842]);MPEG1.PICTURE_TYPE={INTRA:1,PREDICTIVE:2,B:3};MPEG1.START={SEQUENCE:179,SLICE_FIRST:1,SLICE_LAST:175,PICTURE:0,EXTENSION:181,USER_DATA:178};return MPEG1}();JSMpeg.Decoder.MPEG1VideoWASM=function(){"use strict";var MPEG1WASM=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onVideoDecode;this.module=options.wasmModule;this.bufferSize=options.videoBufferSize||512*1024;this.bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.decodeFirstFrame=options.decodeFirstFrame!==false;this.hasSequenceHeader=false};MPEG1WASM.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MPEG1WASM.prototype.constructor=MPEG1WASM;MPEG1WASM.prototype.initializeWasmDecoder=function(){if(!this.module.instance){console.warn("JSMpeg: WASM module not compiled yet");return}this.instance=this.module.instance;this.functions=this.module.instance.exports;this.decoder=this.functions._mpeg1_decoder_create(this.bufferSize,this.bufferMode)};MPEG1WASM.prototype.destroy=function(){if(!this.decoder){return}this.functions._mpeg1_decoder_destroy(this.decoder)};MPEG1WASM.prototype.bufferGetIndex=function(){if(!this.decoder){return}return this.functions._mpeg1_decoder_get_index(this.decoder)};MPEG1WASM.prototype.bufferSetIndex=function(index){if(!this.decoder){return}this.functions._mpeg1_decoder_set_index(this.decoder,index)};MPEG1WASM.prototype.bufferWrite=function(buffers){if(!this.decoder){this.initializeWasmDecoder()}var totalLength=0;for(var i=0;i>2));var dcb=this.instance.heapU8.subarray(ptrCb,ptrCb+(this.codedSize>>2));this.destination.render(dy,dcr,dcb,false)}this.advanceDecodedTime(1/this.frameRate);var elapsedTime=JSMpeg.Now()-startTime;if(this.onDecodeCallback){this.onDecodeCallback(this,elapsedTime)}return true};return MPEG1WASM}();JSMpeg.Decoder.MP2Audio=function(){"use strict";var MP2=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onAudioDecode;var bufferSize=options.audioBufferSize||128*1024;var bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.bits=new JSMpeg.BitBuffer(bufferSize,bufferMode);this.left=new Float32Array(1152);this.right=new Float32Array(1152);this.sampleRate=44100;this.D=new Float32Array(1024);this.D.set(MP2.SYNTHESIS_WINDOW,0);this.D.set(MP2.SYNTHESIS_WINDOW,512);this.V=[new Float32Array(1024),new Float32Array(1024)];this.U=new Int32Array(32);this.VPos=0;this.allocation=[new Array(32),new Array(32)];this.scaleFactorInfo=[new Uint8Array(32),new Uint8Array(32)];this.scaleFactor=[new Array(32),new Array(32)];this.sample=[new Array(32),new Array(32)];for(var j=0;j<2;j++){for(var i=0;i<32;i++){this.scaleFactor[j][i]=[0,0,0];this.sample[j][i]=[0,0,0]}}};MP2.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MP2.prototype.constructor=MP2;MP2.prototype.decode=function(){var startTime=JSMpeg.Now();var pos=this.bits.index>>3;if(pos>=this.bits.byteLength){return false}var decoded=this.decodeFrame(this.left,this.right);this.bits.index=pos+decoded<<3;if(!decoded){return false}if(this.destination){this.destination.play(this.sampleRate,this.left,this.right)}this.advanceDecodedTime(this.left.length/this.sampleRate);var elapsedTime=JSMpeg.Now()-startTime;if(this.onDecodeCallback){this.onDecodeCallback(this,elapsedTime)}return true};MP2.prototype.getCurrentTime=function(){var enqueuedTime=this.destination?this.destination.enqueuedTime:0;return this.decodedTime-enqueuedTime};MP2.prototype.decodeFrame=function(left,right){var sync=this.bits.read(11),version=this.bits.read(2),layer=this.bits.read(2),hasCRC=!this.bits.read(1);if(sync!==MP2.FRAME_SYNC||version!==MP2.VERSION.MPEG_1||layer!==MP2.LAYER.II){return 0}var bitrateIndex=this.bits.read(4)-1;if(bitrateIndex>13){return 0}var sampleRateIndex=this.bits.read(2);var sampleRate=MP2.SAMPLE_RATE[sampleRateIndex];if(sampleRateIndex===3){return 0}if(version===MP2.VERSION.MPEG_2){sampleRateIndex+=4;bitrateIndex+=14}var padding=this.bits.read(1),privat=this.bits.read(1),mode=this.bits.read(2);var bound=0;if(mode===MP2.MODE.JOINT_STEREO){bound=this.bits.read(2)+1<<2}else{this.bits.skip(2);bound=mode===MP2.MODE.MONO?0:32}this.bits.skip(4);if(hasCRC){this.bits.skip(16)}var bitrate=MP2.BIT_RATE[bitrateIndex],sampleRate=MP2.SAMPLE_RATE[sampleRateIndex],frameSize=144e3*bitrate/sampleRate+padding|0;var tab3=0;var sblimit=0;if(version===MP2.VERSION.MPEG_2){tab3=2;sblimit=30}else{var tab1=mode===MP2.MODE.MONO?0:1;var tab2=MP2.QUANT_LUT_STEP_1[tab1][bitrateIndex];tab3=MP2.QUANT_LUT_STEP_2[tab2][sampleRateIndex];sblimit=tab3&63;tab3>>=6}if(bound>sblimit){bound=sblimit}for(var sb=0;sb>1);var vIndex=this.VPos%128>>1;while(vIndex<1024){for(var i=0;i<32;++i){this.U[i]+=this.D[dIndex++]*this.V[ch][vIndex++]}vIndex+=128-32;dIndex+=64-32}vIndex=128-32+1024-vIndex;dIndex-=512-32;while(vIndex<1024){for(var i=0;i<32;++i){this.U[i]+=this.D[dIndex++]*this.V[ch][vIndex++]}vIndex+=128-32;dIndex+=64-32}var outChannel=ch===0?left:right;for(var j=0;j<32;j++){outChannel[outPos+j]=this.U[j]/2147418112}}outPos+=32}}}this.sampleRate=sampleRate;return frameSize};MP2.prototype.readAllocation=function(sb,tab3){var tab4=MP2.QUANT_LUT_STEP_3[tab3][sb];var qtab=MP2.QUANT_LUT_STEP4[tab4&15][this.bits.read(tab4>>4)];return qtab?MP2.QUANT_TAB[qtab-1]:0};MP2.prototype.readSamples=function(ch,sb,part){var q=this.allocation[ch][sb],sf=this.scaleFactor[ch][sb][part],sample=this.sample[ch][sb],val=0;if(!q){sample[0]=sample[1]=sample[2]=0;return}if(sf===63){sf=0}else{var shift=sf/3|0;sf=MP2.SCALEFACTOR_BASE[sf%3]+(1<>1)>>shift}var adj=q.levels;if(q.group){val=this.bits.read(q.bits);sample[0]=val%adj;val=val/adj|0;sample[1]=val%adj;sample[2]=val/adj|0}else{sample[0]=this.bits.read(q.bits);sample[1]=this.bits.read(q.bits);sample[2]=this.bits.read(q.bits)}var scale=65536/(adj+1)|0;adj=(adj+1>>1)-1;val=(adj-sample[0])*scale;sample[0]=val*(sf>>12)+(val*(sf&4095)+2048>>12)>>12;val=(adj-sample[1])*scale;sample[1]=val*(sf>>12)+(val*(sf&4095)+2048>>12)>>12;val=(adj-sample[2])*scale;sample[2]=val*(sf>>12)+(val*(sf&4095)+2048>>12)>>12};MP2.MatrixTransform=function(s,ss,d,dp){var t01,t02,t03,t04,t05,t06,t07,t08,t09,t10,t11,t12,t13,t14,t15,t16,t17,t18,t19,t20,t21,t22,t23,t24,t25,t26,t27,t28,t29,t30,t31,t32,t33;t01=s[0][ss]+s[31][ss];t02=(s[0][ss]-s[31][ss])*.500602998235;t03=s[1][ss]+s[30][ss];t04=(s[1][ss]-s[30][ss])*.505470959898;t05=s[2][ss]+s[29][ss];t06=(s[2][ss]-s[29][ss])*.515447309923;t07=s[3][ss]+s[28][ss];t08=(s[3][ss]-s[28][ss])*.53104259109;t09=s[4][ss]+s[27][ss];t10=(s[4][ss]-s[27][ss])*.553103896034;t11=s[5][ss]+s[26][ss];t12=(s[5][ss]-s[26][ss])*.582934968206;t13=s[6][ss]+s[25][ss];t14=(s[6][ss]-s[25][ss])*.622504123036;t15=s[7][ss]+s[24][ss];t16=(s[7][ss]-s[24][ss])*.674808341455;t17=s[8][ss]+s[23][ss];t18=(s[8][ss]-s[23][ss])*.744536271002;t19=s[9][ss]+s[22][ss];t20=(s[9][ss]-s[22][ss])*.839349645416;t21=s[10][ss]+s[21][ss];t22=(s[10][ss]-s[21][ss])*.972568237862;t23=s[11][ss]+s[20][ss];t24=(s[11][ss]-s[20][ss])*1.16943993343;t25=s[12][ss]+s[19][ss];t26=(s[12][ss]-s[19][ss])*1.48416461631;t27=s[13][ss]+s[18][ss];t28=(s[13][ss]-s[18][ss])*2.05778100995;t29=s[14][ss]+s[17][ss];t30=(s[14][ss]-s[17][ss])*3.40760841847;t31=s[15][ss]+s[16][ss];t32=(s[15][ss]-s[16][ss])*10.1900081235;t33=t01+t31;t31=(t01-t31)*.502419286188;t01=t03+t29;t29=(t03-t29)*.52249861494;t03=t05+t27;t27=(t05-t27)*.566944034816;t05=t07+t25;t25=(t07-t25)*.64682178336;t07=t09+t23;t23=(t09-t23)*.788154623451;t09=t11+t21;t21=(t11-t21)*1.06067768599;t11=t13+t19;t19=(t13-t19)*1.72244709824;t13=t15+t17;t17=(t15-t17)*5.10114861869;t15=t33+t13;t13=(t33-t13)*.509795579104;t33=t01+t11;t01=(t01-t11)*.601344886935;t11=t03+t09;t09=(t03-t09)*.899976223136;t03=t05+t07;t07=(t05-t07)*2.56291544774;t05=t15+t03;t15=(t15-t03)*.541196100146;t03=t33+t11;t11=(t33-t11)*1.30656296488;t33=t05+t03;t05=(t05-t03)*.707106781187;t03=t15+t11;t15=(t15-t11)*.707106781187;t03+=t15;t11=t13+t07;t13=(t13-t07)*.541196100146;t07=t01+t09;t09=(t01-t09)*1.30656296488;t01=t11+t07;t07=(t11-t07)*.707106781187;t11=t13+t09;t13=(t13-t09)*.707106781187;t11+=t13;t01+=t11;t11+=t07;t07+=t13;t09=t31+t17;t31=(t31-t17)*.509795579104;t17=t29+t19;t29=(t29-t19)*.601344886935;t19=t27+t21;t21=(t27-t21)*.899976223136;t27=t25+t23;t23=(t25-t23)*2.56291544774;t25=t09+t27;t09=(t09-t27)*.541196100146;t27=t17+t19;t19=(t17-t19)*1.30656296488;t17=t25+t27;t27=(t25-t27)*.707106781187;t25=t09+t19;t19=(t09-t19)*.707106781187;t25+=t19;t09=t31+t23;t31=(t31-t23)*.541196100146;t23=t29+t21;t21=(t29-t21)*1.30656296488;t29=t09+t23;t23=(t09-t23)*.707106781187;t09=t31+t21;t31=(t31-t21)*.707106781187;t09+=t31;t29+=t09;t09+=t23;t23+=t31;t17+=t29;t29+=t25;t25+=t09;t09+=t27;t27+=t23;t23+=t19;t19+=t31;t21=t02+t32;t02=(t02-t32)*.502419286188;t32=t04+t30;t04=(t04-t30)*.52249861494;t30=t06+t28;t28=(t06-t28)*.566944034816;t06=t08+t26;t08=(t08-t26)*.64682178336;t26=t10+t24;t10=(t10-t24)*.788154623451;t24=t12+t22;t22=(t12-t22)*1.06067768599;t12=t14+t20;t20=(t14-t20)*1.72244709824;t14=t16+t18;t16=(t16-t18)*5.10114861869;t18=t21+t14;t14=(t21-t14)*.509795579104;t21=t32+t12;t32=(t32-t12)*.601344886935;t12=t30+t24;t24=(t30-t24)*.899976223136;t30=t06+t26;t26=(t06-t26)*2.56291544774;t06=t18+t30;t18=(t18-t30)*.541196100146;t30=t21+t12;t12=(t21-t12)*1.30656296488;t21=t06+t30;t30=(t06-t30)*.707106781187;t06=t18+t12;t12=(t18-t12)*.707106781187;t06+=t12;t18=t14+t26;t26=(t14-t26)*.541196100146;t14=t32+t24;t24=(t32-t24)*1.30656296488;t32=t18+t14;t14=(t18-t14)*.707106781187;t18=t26+t24;t24=(t26-t24)*.707106781187;t18+=t24;t32+=t18;t18+=t14;t26=t14+t24;t14=t02+t16;t02=(t02-t16)*.509795579104;t16=t04+t20;t04=(t04-t20)*.601344886935;t20=t28+t22;t22=(t28-t22)*.899976223136;t28=t08+t10;t10=(t08-t10)*2.56291544774;t08=t14+t28;t14=(t14-t28)*.541196100146;t28=t16+t20;t20=(t16-t20)*1.30656296488;t16=t08+t28;t28=(t08-t28)*.707106781187;t08=t14+t20;t20=(t14-t20)*.707106781187;t08+=t20;t14=t02+t10;t02=(t02-t10)*.541196100146;t10=t04+t22;t22=(t04-t22)*1.30656296488;t04=t14+t10;t10=(t14-t10)*.707106781187;t14=t02+t22;t02=(t02-t22)*.707106781187;t14+=t02;t04+=t14;t14+=t10;t10+=t02;t16+=t04;t04+=t08;t08+=t14;t14+=t28;t28+=t10;t10+=t20;t20+=t02;t21+=t16;t16+=t32;t32+=t04;t04+=t06;t06+=t08;t08+=t18;t18+=t14;t14+=t30;t30+=t28;t28+=t26;t26+=t10;t10+=t12;t12+=t20;t20+=t24;t24+=t02;d[dp+48]=-t33;d[dp+49]=d[dp+47]=-t21;d[dp+50]=d[dp+46]=-t17;d[dp+51]=d[dp+45]=-t16;d[dp+52]=d[dp+44]=-t01;d[dp+53]=d[dp+43]=-t32;d[dp+54]=d[dp+42]=-t29;d[dp+55]=d[dp+41]=-t04;d[dp+56]=d[dp+40]=-t03;d[dp+57]=d[dp+39]=-t06;d[dp+58]=d[dp+38]=-t25;d[dp+59]=d[dp+37]=-t08;d[dp+60]=d[dp+36]=-t11;d[dp+61]=d[dp+35]=-t18;d[dp+62]=d[dp+34]=-t09;d[dp+63]=d[dp+33]=-t14;d[dp+32]=-t05;d[dp+0]=t05;d[dp+31]=-t30;d[dp+1]=t30;d[dp+30]=-t27;d[dp+2]=t27;d[dp+29]=-t28;d[dp+3]=t28;d[dp+28]=-t07;d[dp+4]=t07;d[dp+27]=-t26;d[dp+5]=t26;d[dp+26]=-t23;d[dp+6]=t23;d[dp+25]=-t10;d[dp+7]=t10;d[dp+24]=-t15;d[dp+8]=t15;d[dp+23]=-t12;d[dp+9]=t12;d[dp+22]=-t19;d[dp+10]=t19;d[dp+21]=-t20;d[dp+11]=t20;d[dp+20]=-t13;d[dp+12]=t13;d[dp+19]=-t24;d[dp+13]=t24;d[dp+18]=-t31;d[dp+14]=t31;d[dp+17]=-t02;d[dp+15]=t02;d[dp+16]=0};MP2.FRAME_SYNC=2047;MP2.VERSION={MPEG_2_5:0,MPEG_2:2,MPEG_1:3};MP2.LAYER={III:1,II:2,I:3};MP2.MODE={STEREO:0,JOINT_STEREO:1,DUAL_CHANNEL:2,MONO:3};MP2.SAMPLE_RATE=new Uint16Array([44100,48e3,32e3,0,22050,24e3,16e3,0]);MP2.BIT_RATE=new Uint16Array([32,48,56,64,80,96,112,128,160,192,224,256,320,384,8,16,24,32,40,48,56,64,80,96,112,128,144,160]);MP2.SCALEFACTOR_BASE=new Uint32Array([33554432,26632170,21137968]);MP2.SYNTHESIS_WINDOW=new Float32Array([0,-.5,-.5,-.5,-.5,-.5,-.5,-1,-1,-1,-1,-1.5,-1.5,-2,-2,-2.5,-2.5,-3,-3.5,-3.5,-4,-4.5,-5,-5.5,-6.5,-7,-8,-8.5,-9.5,-10.5,-12,-13,-14.5,-15.5,-17.5,-19,-20.5,-22.5,-24.5,-26.5,-29,-31.5,-34,-36.5,-39.5,-42.5,-45.5,-48.5,-52,-55.5,-58.5,-62.5,-66,-69.5,-73.5,-77,-80.5,-84.5,-88,-91.5,-95,-98,-101,-104,106.5,109,111,112.5,113.5,114,114,113.5,112,110.5,107.5,104,100,94.5,88.5,81.5,73,63.5,53,41.5,28.5,14.5,-1,-18,-36,-55.5,-76.5,-98.5,-122,-147,-173.5,-200.5,-229.5,-259.5,-290.5,-322.5,-355.5,-389.5,-424,-459.5,-495.5,-532,-568.5,-605,-641.5,-678,-714,-749,-783.5,-817,-849,-879.5,-908.5,-935,-959.5,-981,-1000.5,-1016,-1028.5,-1037.5,-1042.5,-1043.5,-1040,-1031.5,1018.5,1e3,976,946.5,911,869.5,822,767.5,707,640,565.5,485,397,302.5,201,92.5,-22.5,-144,-272.5,-407,-547.5,-694,-846,-1003,-1165,-1331.5,-1502,-1675.5,-1852.5,-2031.5,-2212.5,-2394,-2576.5,-2758.5,-2939.5,-3118.5,-3294.5,-3467.5,-3635.5,-3798.5,-3955,-4104.5,-4245.5,-4377.5,-4499,-4609.5,-4708,-4792.5,-4863.5,-4919,-4958,-4979.5,-4983,-4967.5,-4931.5,-4875,-4796,-4694.5,-4569.5,-4420,-4246,-4046,-3820,-3567,3287,2979.5,2644,2280.5,1888,1467.5,1018.5,541,35,-499,-1061,-1650,-2266.5,-2909,-3577,-4270,-4987.5,-5727.5,-6490,-7274,-8077.5,-8899.5,-9739,-10594.5,-11464.5,-12347,-13241,-14144.5,-15056,-15973.5,-16895.5,-17820,-18744.5,-19668,-20588,-21503,-22410.5,-23308.5,-24195,-25068.5,-25926.5,-26767,-27589,-28389,-29166.5,-29919,-30644.5,-31342,-32009.5,-32645,-33247,-33814.5,-34346,-34839.5,-35295,-35710,-36084.5,-36417.5,-36707.5,-36954,-37156.5,-37315,-37428,-37496,37519,37496,37428,37315,37156.5,36954,36707.5,36417.5,36084.5,35710,35295,34839.5,34346,33814.5,33247,32645,32009.5,31342,30644.5,29919,29166.5,28389,27589,26767,25926.5,25068.5,24195,23308.5,22410.5,21503,20588,19668,18744.5,17820,16895.5,15973.5,15056,14144.5,13241,12347,11464.5,10594.5,9739,8899.5,8077.5,7274,6490,5727.5,4987.5,4270,3577,2909,2266.5,1650,1061,499,-35,-541,-1018.5,-1467.5,-1888,-2280.5,-2644,-2979.5,3287,3567,3820,4046,4246,4420,4569.5,4694.5,4796,4875,4931.5,4967.5,4983,4979.5,4958,4919,4863.5,4792.5,4708,4609.5,4499,4377.5,4245.5,4104.5,3955,3798.5,3635.5,3467.5,3294.5,3118.5,2939.5,2758.5,2576.5,2394,2212.5,2031.5,1852.5,1675.5,1502,1331.5,1165,1003,846,694,547.5,407,272.5,144,22.5,-92.5,-201,-302.5,-397,-485,-565.5,-640,-707,-767.5,-822,-869.5,-911,-946.5,-976,-1e3,1018.5,1031.5,1040,1043.5,1042.5,1037.5,1028.5,1016,1000.5,981,959.5,935,908.5,879.5,849,817,783.5,749,714,678,641.5,605,568.5,532,495.5,459.5,424,389.5,355.5,322.5,290.5,259.5,229.5,200.5,173.5,147,122,98.5,76.5,55.5,36,18,1,-14.5,-28.5,-41.5,-53,-63.5,-73,-81.5,-88.5,-94.5,-100,-104,-107.5,-110.5,-112,-113.5,-114,-114,-113.5,-112.5,-111,-109,106.5,104,101,98,95,91.5,88,84.5,80.5,77,73.5,69.5,66,62.5,58.5,55.5,52,48.5,45.5,42.5,39.5,36.5,34,31.5,29,26.5,24.5,22.5,20.5,19,17.5,15.5,14.5,13,12,10.5,9.5,8.5,8,7,6.5,5.5,5,4.5,4,3.5,3.5,3,2.5,2.5,2,2,1.5,1.5,1,1,1,1,.5,.5,.5,.5,.5,.5]);MP2.QUANT_LUT_STEP_1=[[0,0,1,1,1,2,2,2,2,2,2,2,2,2],[0,0,0,0,0,0,1,1,1,2,2,2,2,2]];MP2.QUANT_TAB={A:27|64,B:30|64,C:8,D:12};MP2.QUANT_LUT_STEP_2=[[MP2.QUANT_TAB.C,MP2.QUANT_TAB.C,MP2.QUANT_TAB.D],[MP2.QUANT_TAB.A,MP2.QUANT_TAB.A,MP2.QUANT_TAB.A],[MP2.QUANT_TAB.B,MP2.QUANT_TAB.A,MP2.QUANT_TAB.B]];MP2.QUANT_LUT_STEP_3=[[68,68,52,52,52,52,52,52,52,52,52,52],[67,67,67,66,66,66,66,66,66,66,66,49,49,49,49,49,49,49,49,49,49,49,49,32,32,32,32,32,32,32],[69,69,69,69,52,52,52,52,52,52,52,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36]];MP2.QUANT_LUT_STEP4=[[0,1,2,17],[0,1,2,3,4,5,6,17],[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17],[0,1,3,5,6,7,8,9,10,11,12,13,14,15,16,17],[0,1,2,4,5,6,7,8,9,10,11,12,13,14,15,17],[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]];MP2.QUANT_TAB=[{levels:3,group:1,bits:5},{levels:5,group:1,bits:7},{levels:7,group:0,bits:3},{levels:9,group:1,bits:10},{levels:15,group:0,bits:4},{levels:31,group:0,bits:5},{levels:63,group:0,bits:6},{levels:127,group:0,bits:7},{levels:255,group:0,bits:8},{levels:511,group:0,bits:9},{levels:1023,group:0,bits:10},{levels:2047,group:0,bits:11},{levels:4095,group:0,bits:12},{levels:8191,group:0,bits:13},{levels:16383,group:0,bits:14},{levels:32767,group:0,bits:15},{levels:65535,group:0,bits:16}];return MP2}();JSMpeg.Decoder.MP2AudioWASM=function(){"use strict";var MP2WASM=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onAudioDecode;this.module=options.wasmModule;this.bufferSize=options.audioBufferSize||128*1024;this.bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.sampleRate=0};MP2WASM.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MP2WASM.prototype.constructor=MP2WASM;MP2WASM.prototype.initializeWasmDecoder=function(){if(!this.module.instance){console.warn("JSMpeg: WASM module not compiled yet");return}this.instance=this.module.instance;this.functions=this.module.instance.exports;this.decoder=this.functions._mp2_decoder_create(this.bufferSize,this.bufferMode)};MP2WASM.prototype.destroy=function(){if(!this.decoder){return}this.functions._mp2_decoder_destroy(this.decoder)};MP2WASM.prototype.bufferGetIndex=function(){if(!this.decoder){return}return this.functions._mp2_decoder_get_index(this.decoder)};MP2WASM.prototype.bufferSetIndex=function(index){if(!this.decoder){return}this.functions._mp2_decoder_set_index(this.decoder,index)};MP2WASM.prototype.bufferWrite=function(buffers){if(!this.decoder){this.initializeWasmDecoder()}var totalLength=0;for(var i=0;i>4<<4;this.gl.viewport(0,0,codedWidth,this.height)};WebGLRenderer.prototype.createTexture=function(index,name){var gl=this.gl;var texture=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,texture);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);gl.uniform1i(gl.getUniformLocation(this.program,name),index);return texture};WebGLRenderer.prototype.createProgram=function(vsh,fsh){var gl=this.gl;var program=gl.createProgram();gl.attachShader(program,this.compileShader(gl.VERTEX_SHADER,vsh));gl.attachShader(program,this.compileShader(gl.FRAGMENT_SHADER,fsh));gl.linkProgram(program);gl.useProgram(program);return program};WebGLRenderer.prototype.compileShader=function(type,source){var gl=this.gl;var shader=gl.createShader(type);gl.shaderSource(shader,source);gl.compileShader(shader);if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){throw new Error(gl.getShaderInfoLog(shader))}return shader};WebGLRenderer.prototype.allowsClampedTextureData=function(){var gl=this.gl;var texture=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,texture);gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,1,1,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,new Uint8ClampedArray([0]));return gl.getError()===0};WebGLRenderer.prototype.renderProgress=function(progress){var gl=this.gl;gl.useProgram(this.loadingProgram);var loc=gl.getUniformLocation(this.loadingProgram,"progress");gl.uniform1f(loc,progress);gl.drawArrays(gl.TRIANGLE_STRIP,0,4)};WebGLRenderer.prototype.render=function(y,cb,cr,isClampedArray){if(!this.enabled){return}var gl=this.gl;var w=this.width+15>>4<<4,h=this.height,w2=w>>1,h2=h>>1;if(isClampedArray&&this.shouldCreateUnclampedViews){y=new Uint8Array(y.buffer),cb=new Uint8Array(cb.buffer),cr=new Uint8Array(cr.buffer)}gl.useProgram(this.program);this.updateTexture(gl.TEXTURE0,this.textureY,w,h,y);this.updateTexture(gl.TEXTURE1,this.textureCb,w2,h2,cb);this.updateTexture(gl.TEXTURE2,this.textureCr,w2,h2,cr);gl.drawArrays(gl.TRIANGLE_STRIP,0,4)};WebGLRenderer.prototype.updateTexture=function(unit,texture,w,h,data){var gl=this.gl;gl.activeTexture(unit);gl.bindTexture(gl.TEXTURE_2D,texture);if(this.hasTextureData[unit]){gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,w,h,gl.LUMINANCE,gl.UNSIGNED_BYTE,data)}else{this.hasTextureData[unit]=true;gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,w,h,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,data)}};WebGLRenderer.prototype.deleteTexture=function(unit,texture){var gl=this.gl;gl.activeTexture(unit);gl.bindTexture(gl.TEXTURE_2D,null);gl.deleteTexture(texture)};WebGLRenderer.IsSupported=function(){try{if(!window.WebGLRenderingContext){return false}var canvas=document.createElement("canvas");return!!(canvas.getContext("webgl")||canvas.getContext("experimental-webgl"))}catch(err){return false}};WebGLRenderer.SHADER={FRAGMENT_YCRCB_TO_RGBA:["precision mediump float;","uniform sampler2D textureY;","uniform sampler2D textureCb;","uniform sampler2D textureCr;","varying vec2 texCoord;","mat4 rec601 = mat4(","1.16438, 0.00000, 1.59603, -0.87079,","1.16438, -0.39176, -0.81297, 0.52959,","1.16438, 2.01723, 0.00000, -1.08139,","0, 0, 0, 1",");","void main() {","float y = texture2D(textureY, texCoord).r;","float cb = texture2D(textureCb, texCoord).r;","float cr = texture2D(textureCr, texCoord).r;","gl_FragColor = vec4(y, cr, cb, 1.0) * rec601;","}"].join("\n"),FRAGMENT_LOADING:["precision mediump float;","uniform float progress;","varying vec2 texCoord;","void main() {","float c = ceil(progress-(1.0-texCoord.y));","gl_FragColor = vec4(c,c,c,1);","}"].join("\n"),VERTEX_IDENTITY:["attribute vec2 vertex;","varying vec2 texCoord;","void main() {","texCoord = vertex;","gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);","}"].join("\n")};return WebGLRenderer}();JSMpeg.Renderer.Canvas2D=function(){"use strict";var CanvasRenderer=function(options){if(options.canvas){this.canvas=options.canvas;this.ownsCanvasElement=false}else{this.canvas=document.createElement("canvas");this.ownsCanvasElement=true}this.width=this.canvas.width;this.height=this.canvas.height;this.enabled=true;this.context=this.canvas.getContext("2d")};CanvasRenderer.prototype.destroy=function(){if(this.ownsCanvasElement){this.canvas.remove()}};CanvasRenderer.prototype.resize=function(width,height){this.width=width|0;this.height=height|0;this.canvas.width=this.width;this.canvas.height=this.height;this.imageData=this.context.getImageData(0,0,this.width,this.height);JSMpeg.Fill(this.imageData.data,255)};CanvasRenderer.prototype.renderProgress=function(progress){var w=this.canvas.width,h=this.canvas.height,ctx=this.context;ctx.fillStyle="#222";ctx.fillRect(0,0,w,h);ctx.fillStyle="#fff";ctx.fillRect(0,h-h*progress,w,h*progress)};CanvasRenderer.prototype.render=function(y,cb,cr){this.YCbCrToRGBA(y,cb,cr,this.imageData.data);this.context.putImageData(this.imageData,0,0)};CanvasRenderer.prototype.YCbCrToRGBA=function(y,cb,cr,rgba){if(!this.enabled){return}var w=this.width+15>>4<<4,w2=w>>1;var yIndex1=0,yIndex2=w,yNext2Lines=w+(w-this.width);var cIndex=0,cNextLine=w2-(this.width>>1);var rgbaIndex1=0,rgbaIndex2=this.width*4,rgbaNext2Lines=this.width*4;var cols=this.width>>1,rows=this.height>>1;var ccb,ccr,r,g,b;for(var row=0;row>8)-179;g=(ccr*88>>8)-44+(ccb*183>>8)-91;b=ccr+(ccr*198>>8)-227;var y1=y[yIndex1++];var y2=y[yIndex1++];rgba[rgbaIndex1]=y1+r;rgba[rgbaIndex1+1]=y1-g;rgba[rgbaIndex1+2]=y1+b;rgba[rgbaIndex1+4]=y2+r;rgba[rgbaIndex1+5]=y2-g;rgba[rgbaIndex1+6]=y2+b;rgbaIndex1+=8;var y3=y[yIndex2++];var y4=y[yIndex2++];rgba[rgbaIndex2]=y3+r;rgba[rgbaIndex2+1]=y3-g;rgba[rgbaIndex2+2]=y3+b;rgba[rgbaIndex2+4]=y4+r;rgba[rgbaIndex2+5]=y4-g;rgba[rgbaIndex2+6]=y4+b;rgbaIndex2+=8}yIndex1+=yNext2Lines;yIndex2+=yNext2Lines;rgbaIndex1+=rgbaNext2Lines;rgbaIndex2+=rgbaNext2Lines;cIndex+=cNextLine}};return CanvasRenderer}();JSMpeg.AudioOutput.WebAudio=function(){"use strict";var WebAudioOut=function(options){this.context=WebAudioOut.CachedContext=WebAudioOut.CachedContext||new(window.AudioContext||window.webkitAudioContext);this.gain=this.context.createGain();this.destination=this.gain;this.gain.connect(this.context.destination);this.context._connections=(this.context._connections||0)+1;this.startTime=0;this.buffer=null;this.wallclockStartTime=0;this.volume=1;this.enabled=true;this.unlocked=!WebAudioOut.NeedsUnlocking();Object.defineProperty(this,"enqueuedTime",{get:this.getEnqueuedTime})};WebAudioOut.prototype.destroy=function(){this.gain.disconnect();this.context._connections--;if(this.context._connections===0){this.context.close();WebAudioOut.CachedContext=null}};WebAudioOut.prototype.play=function(sampleRate,left,right){if(!this.enabled){return}if(!this.unlocked){var ts=JSMpeg.Now();if(this.wallclockStartTimethis.memory.buffer.byteLength){var bytesNeeded=this.brk-this.memory.buffer.byteLength;var pagesNeeded=Math.ceil(bytesNeeded/this.pageSize);this.memory.grow(pagesNeeded);this.createHeapViews()}return previousBrk};WASM.prototype.c_abort=function(size){console.warn("JSMPeg: WASM abort",arguments)};WASM.prototype.c_assertFail=function(size){console.warn("JSMPeg: WASM ___assert_fail",arguments)};WASM.prototype.readDylinkSection=function(buffer){var bytes=new Uint8Array(buffer);var next=0;var readVarUint=function(){var ret=0;var mul=1;while(1){var byte=bytes[next++];ret+=(byte&127)*mul;mul*=128;if(!(byte&128)){return ret}}};var matchNextBytes=function(expected){for(var i=0;i secondsHeadroom) { + this.loadNextChunk(); + } +}; + +AjaxProgressiveSource.prototype.destroy = function() { + this.request.abort(); + this.aborted = true; +}; + +AjaxProgressiveSource.prototype.loadNextChunk = function() { + var start = this.loadedSize, + end = Math.min(this.loadedSize + this.chunkSize-1, this.fileSize-1); + + if (start >= this.fileSize || this.aborted) { + this.completed = true; + if (this.onCompletedCallback) { + this.onCompletedCallback(this); + } + return; + } + + this.isLoading = true; + this.loadStartTime = JSMpeg.Now(); + this.request = new XMLHttpRequest(); + + this.request.onreadystatechange = function() { + if ( + this.request.readyState === this.request.DONE && + this.request.status >= 200 && this.request.status < 300 + ) { + this.onChunkLoad(this.request.response); + } + else if (this.request.readyState === this.request.DONE) { + // Retry? + if (this.loadFails++ < 3) { + this.loadNextChunk(); + } + } + }.bind(this); + + if (start === 0) { + this.request.onprogress = this.onProgress.bind(this); + } + + this.request.open('GET', this.url+'?'+start+"-"+end); + this.request.setRequestHeader("Range", "bytes="+start+"-"+end); + this.request.responseType = "arraybuffer"; + this.request.send(); +}; + +AjaxProgressiveSource.prototype.onProgress = function(ev) { + this.progress = (ev.loaded / ev.total); +}; + +AjaxProgressiveSource.prototype.onChunkLoad = function(data) { + var isFirstChunk = !this.established; + this.established = true; + this.progress = 1; + + this.loadedSize += data.byteLength; + this.loadFails = 0; + this.isLoading = false; + + if (isFirstChunk && this.onEstablishedCallback) { + this.onEstablishedCallback(this); + } + + if (this.destination) { + this.destination.write(data); + } + + this.loadTime = JSMpeg.Now() - this.loadStartTime; + if (!this.throttled) { + this.loadNextChunk(); + } +}; + +return AjaxProgressiveSource; + +})(); + + diff --git a/public/static/jsmpeg-master/src/ajax.js b/public/static/jsmpeg-master/src/ajax.js new file mode 100644 index 0000000..14ddb59 --- /dev/null +++ b/public/static/jsmpeg-master/src/ajax.js @@ -0,0 +1,72 @@ +JSMpeg.Source.Ajax = (function(){ "use strict"; + +var AjaxSource = function(url, options) { + this.url = url; + this.destination = null; + this.request = null; + this.streaming = false; + + this.completed = false; + this.established = false; + this.progress = 0; + + this.onEstablishedCallback = options.onSourceEstablished; + this.onCompletedCallback = options.onSourceCompleted; +}; + +AjaxSource.prototype.connect = function(destination) { + this.destination = destination; +}; + +AjaxSource.prototype.start = function() { + this.request = new XMLHttpRequest(); + + this.request.onreadystatechange = function() { + if ( + this.request.readyState === this.request.DONE && + this.request.status === 200 + ) { + this.onLoad(this.request.response); + } + }.bind(this); + + this.request.onprogress = this.onProgress.bind(this); + this.request.open('GET', this.url); + this.request.responseType = "arraybuffer"; + this.request.send(); +}; + +AjaxSource.prototype.resume = function(secondsHeadroom) { + // Nothing to do here +}; + +AjaxSource.prototype.destroy = function() { + this.request.abort(); +}; + +AjaxSource.prototype.onProgress = function(ev) { + this.progress = (ev.loaded / ev.total); +}; + +AjaxSource.prototype.onLoad = function(data) { + this.established = true; + this.completed = true; + this.progress = 1; + + if (this.onEstablishedCallback) { + this.onEstablishedCallback(this); + } + if (this.onCompletedCallback) { + this.onCompletedCallback(this); + } + + if (this.destination) { + this.destination.write(data); + } +}; + +return AjaxSource; + +})(); + + diff --git a/public/static/jsmpeg-master/src/buffer.js b/public/static/jsmpeg-master/src/buffer.js new file mode 100644 index 0000000..c8f9a6d --- /dev/null +++ b/public/static/jsmpeg-master/src/buffer.js @@ -0,0 +1,198 @@ +JSMpeg.BitBuffer = (function(){ "use strict"; + +var BitBuffer = function(bufferOrLength, mode) { + if (typeof(bufferOrLength) === 'object') { + this.bytes = (bufferOrLength instanceof Uint8Array) + ? bufferOrLength + : new Uint8Array(bufferOrLength); + + this.byteLength = this.bytes.length; + } + else { + this.bytes = new Uint8Array(bufferOrLength || 1024*1024); + this.byteLength = 0; + } + + this.mode = mode || BitBuffer.MODE.EXPAND; + this.index = 0; +}; + +BitBuffer.prototype.resize = function(size) { + var newBytes = new Uint8Array(size); + if (this.byteLength !== 0) { + this.byteLength = Math.min(this.byteLength, size); + newBytes.set(this.bytes, 0, this.byteLength); + } + this.bytes = newBytes; + this.index = Math.min(this.index, this.byteLength << 3); +}; + +BitBuffer.prototype.evict = function(sizeNeeded) { + var bytePos = this.index >> 3, + available = this.bytes.length - this.byteLength; + + // If the current index is the write position, we can simply reset both + // to 0. Also reset (and throw away yet unread data) if we won't be able + // to fit the new data in even after a normal eviction. + if ( + this.index === this.byteLength << 3 || + sizeNeeded > available + bytePos // emergency evac + ) { + this.byteLength = 0; + this.index = 0; + return; + } + else if (bytePos === 0) { + // Nothing read yet - we can't evict anything + return; + } + + // Some browsers don't support copyWithin() yet - we may have to do + // it manually using set and a subarray + if (this.bytes.copyWithin) { + this.bytes.copyWithin(0, bytePos, this.byteLength); + } + else { + this.bytes.set(this.bytes.subarray(bytePos, this.byteLength)); + } + + this.byteLength = this.byteLength - bytePos; + this.index -= bytePos << 3; + return; +}; + +BitBuffer.prototype.write = function(buffers) { + var isArrayOfBuffers = (typeof(buffers[0]) === 'object'), + totalLength = 0, + available = this.bytes.length - this.byteLength; + + // Calculate total byte length + if (isArrayOfBuffers) { + var totalLength = 0; + for (var i = 0; i < buffers.length; i++) { + totalLength += buffers[i].byteLength; + } + } + else { + totalLength = buffers.byteLength; + } + + // Do we need to resize or evict? + if (totalLength > available) { + if (this.mode === BitBuffer.MODE.EXPAND) { + var newSize = Math.max( + this.bytes.length * 2, + totalLength - available + ); + this.resize(newSize) + } + else { + this.evict(totalLength); + } + } + + if (isArrayOfBuffers) { + for (var i = 0; i < buffers.length; i++) { + this.appendSingleBuffer(buffers[i]); + } + } + else { + this.appendSingleBuffer(buffers); + } + + return totalLength; +}; + +BitBuffer.prototype.appendSingleBuffer = function(buffer) { + buffer = buffer instanceof Uint8Array + ? buffer + : new Uint8Array(buffer); + + this.bytes.set(buffer, this.byteLength); + this.byteLength += buffer.length; +}; + +BitBuffer.prototype.findNextStartCode = function() { + for (var i = (this.index+7 >> 3); i < this.byteLength; i++) { + if( + this.bytes[i] == 0x00 && + this.bytes[i+1] == 0x00 && + this.bytes[i+2] == 0x01 + ) { + this.index = (i+4) << 3; + return this.bytes[i+3]; + } + } + this.index = (this.byteLength << 3); + return -1; +}; + +BitBuffer.prototype.findStartCode = function(code) { + var current = 0; + while (true) { + current = this.findNextStartCode(); + if (current === code || current === -1) { + return current; + } + } + return -1; +}; + +BitBuffer.prototype.nextBytesAreStartCode = function() { + var i = (this.index+7 >> 3); + return ( + i >= this.byteLength || ( + this.bytes[i] == 0x00 && + this.bytes[i+1] == 0x00 && + this.bytes[i+2] == 0x01 + ) + ); +}; + +BitBuffer.prototype.peek = function(count) { + var offset = this.index; + var value = 0; + while (count) { + var currentByte = this.bytes[offset >> 3], + remaining = 8 - (offset & 7), // remaining bits in byte + read = remaining < count ? remaining : count, // bits in this run + shift = remaining - read, + mask = (0xff >> (8-read)); + + value = (value << read) | ((currentByte & (mask << shift)) >> shift); + + offset += read; + count -= read; + } + + return value; +} + +BitBuffer.prototype.read = function(count) { + var value = this.peek(count); + this.index += count; + return value; +}; + +BitBuffer.prototype.skip = function(count) { + return (this.index += count); +}; + +BitBuffer.prototype.rewind = function(count) { + this.index = Math.max(this.index - count, 0); +}; + +BitBuffer.prototype.has = function(count) { + return ((this.byteLength << 3) - this.index) >= count; +}; + +BitBuffer.MODE = { + EVICT: 1, + EXPAND: 2 +}; + +return BitBuffer; + +})(); + + diff --git a/public/static/jsmpeg-master/src/canvas2d.js b/public/static/jsmpeg-master/src/canvas2d.js new file mode 100644 index 0000000..fd85cfb --- /dev/null +++ b/public/static/jsmpeg-master/src/canvas2d.js @@ -0,0 +1,128 @@ +JSMpeg.Renderer.Canvas2D = (function(){ "use strict"; + +var CanvasRenderer = function(options) { + if (options.canvas) { + this.canvas = options.canvas; + this.ownsCanvasElement = false; + } + else { + this.canvas = document.createElement('canvas'); + this.ownsCanvasElement = true; + } + this.width = this.canvas.width; + this.height = this.canvas.height; + this.enabled = true; + + this.context = this.canvas.getContext('2d'); +}; + +CanvasRenderer.prototype.destroy = function() { + if (this.ownsCanvasElement) { + this.canvas.remove(); + } +}; + +CanvasRenderer.prototype.resize = function(width, height) { + this.width = width|0; + this.height = height|0; + + this.canvas.width = this.width; + this.canvas.height = this.height; + + this.imageData = this.context.getImageData(0, 0, this.width, this.height); + JSMpeg.Fill(this.imageData.data, 255); +}; + +CanvasRenderer.prototype.renderProgress = function(progress) { + var + w = this.canvas.width, + h = this.canvas.height, + ctx = this.context; + + ctx.fillStyle = '#222'; + ctx.fillRect(0, 0, w, h); + ctx.fillStyle = '#fff'; + ctx.fillRect(0, h - h * progress, w, h * progress); +}; + +CanvasRenderer.prototype.render = function(y, cb, cr) { + this.YCbCrToRGBA(y, cb, cr, this.imageData.data); + this.context.putImageData(this.imageData, 0, 0); +}; + +CanvasRenderer.prototype.YCbCrToRGBA = function(y, cb, cr, rgba) { + if (!this.enabled) { + return; + } + + // Chroma values are the same for each block of 4 pixels, so we proccess + // 2 lines at a time, 2 neighboring pixels each. + // I wish we could use 32bit writes to the RGBA buffer instead of writing + // each byte separately, but we need the automatic clamping of the RGBA + // buffer. + + var w = ((this.width + 15) >> 4) << 4, + w2 = w >> 1; + + var yIndex1 = 0, + yIndex2 = w, + yNext2Lines = w + (w - this.width); + + var cIndex = 0, + cNextLine = w2 - (this.width >> 1); + + var rgbaIndex1 = 0, + rgbaIndex2 = this.width * 4, + rgbaNext2Lines = this.width * 4; + + var cols = this.width >> 1, + rows = this.height >> 1; + + var ccb, ccr, r, g, b; + + for (var row = 0; row < rows; row++) { + for (var col = 0; col < cols; col++) { + ccb = cb[cIndex]; + ccr = cr[cIndex]; + cIndex++; + + r = (ccb + ((ccb * 103) >> 8)) - 179; + g = ((ccr * 88) >> 8) - 44 + ((ccb * 183) >> 8) - 91; + b = (ccr + ((ccr * 198) >> 8)) - 227; + + // Line 1 + var y1 = y[yIndex1++]; + var y2 = y[yIndex1++]; + rgba[rgbaIndex1] = y1 + r; + rgba[rgbaIndex1+1] = y1 - g; + rgba[rgbaIndex1+2] = y1 + b; + rgba[rgbaIndex1+4] = y2 + r; + rgba[rgbaIndex1+5] = y2 - g; + rgba[rgbaIndex1+6] = y2 + b; + rgbaIndex1 += 8; + + // Line 2 + var y3 = y[yIndex2++]; + var y4 = y[yIndex2++]; + rgba[rgbaIndex2] = y3 + r; + rgba[rgbaIndex2+1] = y3 - g; + rgba[rgbaIndex2+2] = y3 + b; + rgba[rgbaIndex2+4] = y4 + r; + rgba[rgbaIndex2+5] = y4 - g; + rgba[rgbaIndex2+6] = y4 + b; + rgbaIndex2 += 8; + } + + yIndex1 += yNext2Lines; + yIndex2 += yNext2Lines; + rgbaIndex1 += rgbaNext2Lines; + rgbaIndex2 += rgbaNext2Lines; + cIndex += cNextLine; + } +}; + +return CanvasRenderer; + +})(); + + diff --git a/public/static/jsmpeg-master/src/decoder.js b/public/static/jsmpeg-master/src/decoder.js new file mode 100644 index 0000000..8faf358 --- /dev/null +++ b/public/static/jsmpeg-master/src/decoder.js @@ -0,0 +1,112 @@ +JSMpeg.Decoder.Base = (function(){ "use strict"; + +var BaseDecoder = function(options) { + this.destination = null; + this.canPlay = false; + + this.collectTimestamps = !options.streaming; + this.bytesWritten = 0; + this.timestamps = []; + this.timestampIndex = 0; + + this.startTime = 0; + this.decodedTime = 0; + + Object.defineProperty(this, 'currentTime', {get: this.getCurrentTime}); +}; + +BaseDecoder.prototype.destroy = function() {}; + +BaseDecoder.prototype.connect = function(destination) { + this.destination = destination; +}; + +BaseDecoder.prototype.bufferGetIndex = function() { + return this.bits.index; +}; + +BaseDecoder.prototype.bufferSetIndex = function(index) { + this.bits.index = index; +}; + +BaseDecoder.prototype.bufferWrite = function(buffers) { + return this.bits.write(buffers); +}; + +BaseDecoder.prototype.write = function(pts, buffers) { + if (this.collectTimestamps) { + if (this.timestamps.length === 0) { + this.startTime = pts; + this.decodedTime = pts; + } + this.timestamps.push({index: this.bytesWritten << 3, time: pts}); + } + + this.bytesWritten += this.bufferWrite(buffers); + this.canPlay = true; +}; + +BaseDecoder.prototype.seek = function(time) { + if (!this.collectTimestamps) { + return; + } + + this.timestampIndex = 0; + for (var i = 0; i < this.timestamps.length; i++) { + if (this.timestamps[i].time > time) { + break; + } + this.timestampIndex = i; + } + + var ts = this.timestamps[this.timestampIndex]; + if (ts) { + this.bufferSetIndex(ts.index); + this.decodedTime = ts.time; + } + else { + this.bufferSetIndex(0); + this.decodedTime = this.startTime; + } +}; + +BaseDecoder.prototype.decode = function() { + this.advanceDecodedTime(0); +}; + +BaseDecoder.prototype.advanceDecodedTime = function(seconds) { + if (this.collectTimestamps) { + var newTimestampIndex = -1; + var currentIndex = this.bufferGetIndex(); + for (var i = this.timestampIndex; i < this.timestamps.length; i++) { + if (this.timestamps[i].index > currentIndex) { + break; + } + newTimestampIndex = i; + } + + // Did we find a new PTS, different from the last? If so, we don't have + // to advance the decoded time manually and can instead sync it exactly + // to the PTS. + if ( + newTimestampIndex !== -1 && + newTimestampIndex !== this.timestampIndex + ) { + this.timestampIndex = newTimestampIndex; + this.decodedTime = this.timestamps[this.timestampIndex].time; + return; + } + } + + this.decodedTime += seconds; +}; + +BaseDecoder.prototype.getCurrentTime = function() { + return this.decodedTime; +}; + +return BaseDecoder; + +})(); + + diff --git a/public/static/jsmpeg-master/src/fetch.js b/public/static/jsmpeg-master/src/fetch.js new file mode 100644 index 0000000..f7b79ad --- /dev/null +++ b/public/static/jsmpeg-master/src/fetch.js @@ -0,0 +1,74 @@ +JSMpeg.Source.Fetch = (function(){ "use strict"; + +var FetchSource = function(url, options) { + this.url = url; + this.destination = null; + this.request = null; + this.streaming = true; + + this.completed = false; + this.established = false; + this.progress = 0; + this.aborted = false; + + this.onEstablishedCallback = options.onSourceEstablished; + this.onCompletedCallback = options.onSourceCompleted; +}; + +FetchSource.prototype.connect = function(destination) { + this.destination = destination; +}; + +FetchSource.prototype.start = function() { + var params = { + method: 'GET', + headers: new Headers(), + cache: 'default' + }; + + self.fetch(this.url, params).then(function(res) { + if (res.ok && (res.status >= 200 && res.status <= 299)) { + this.progress = 1; + this.established = true; + return this.pump(res.body.getReader()); + } + else { + //error + } + }.bind(this)).catch(function(err) { + throw(err); + }); +}; + +FetchSource.prototype.pump = function(reader) { + return reader.read().then(function(result) { + if (result.done) { + this.completed = true; + } + else { + if (this.aborted) { + return reader.cancel(); + } + + if (this.destination) { + this.destination.write(result.value.buffer); + } + + return this.pump(reader); + } + }.bind(this)).catch(function(err) { + throw(err); + }); +}; + +FetchSource.prototype.resume = function(secondsHeadroom) { + // Nothing to do here +}; + +FetchSource.prototype.abort = function() { + this.aborted = true; +}; + +return FetchSource; + +})(); \ No newline at end of file diff --git a/public/static/jsmpeg-master/src/jsmpeg.js b/public/static/jsmpeg-master/src/jsmpeg.js new file mode 100644 index 0000000..e11dd0c --- /dev/null +++ b/public/static/jsmpeg-master/src/jsmpeg.js @@ -0,0 +1,122 @@ +/*! jsmpeg v1.0 | (c) Dominic Szablewski | MIT license */ + + +// This sets up the JSMpeg "Namespace". The object is empty apart from the Now() +// utility function and the automatic CreateVideoElements() after DOMReady. +var JSMpeg = { + + // The Player sets up the connections between source, demuxer, decoders, + // renderer and audio output. It ties everything together, is responsible + // of scheduling decoding and provides some convenience methods for + // external users. + Player: null, + + // A Video Element wraps the Player, shows HTML controls to start/pause + // the video and handles Audio unlocking on iOS. VideoElements can be + // created directly in HTML using the
tag. + VideoElement: null, + + // The BitBuffer wraps a Uint8Array and allows reading an arbitrary number + // of bits at a time. On writing, the BitBuffer either expands its + // internal buffer (for static files) or deletes old data (for streaming). + BitBuffer: null, + + // A Source provides raw data from HTTP, a WebSocket connection or any + // other mean. Sources must support the following API: + // .connect(destinationNode) + // .write(buffer) + // .start() - start reading + // .resume(headroom) - continue reading; headroom to play pos in seconds + // .established - boolean, true after connection is established + // .completed - boolean, true if the source is completely loaded + // .progress - float 0-1 + Source: {}, + + // A Demuxer may sit between a Source and a Decoder. It separates the + // incoming raw data into Video, Audio and other Streams. API: + // .connect(streamId, destinationNode) + // .write(buffer) + // .currentTime – float, in seconds + // .startTime - float, in seconds + Demuxer: {}, + + // A Decoder accepts an incoming Stream of raw Audio or Video data, buffers + // it and upon `.decode()` decodes a single frame of data. Video decoders + // call `destinationNode.render(Y, Cr, CB)` with the decoded pixel data; + // Audio decoders call `destinationNode.play(left, right)` with the decoded + // PCM data. API: + // .connect(destinationNode) + // .write(pts, buffer) + // .decode() + // .seek(time) + // .currentTime - float, in seconds + // .startTime - float, in seconds + Decoder: {}, + + // A Renderer accepts raw YCrCb data in 3 separate buffers via the render() + // method. Renderers typically convert the data into the RGBA color space + // and draw it on a Canvas, but other output - such as writing PNGs - would + // be conceivable. API: + // .render(y, cr, cb) - pixel data as Uint8Arrays + // .enabled - wether the renderer does anything upon receiving data + Renderer: {}, + + // Audio Outputs accept raw Stero PCM data in 2 separate buffers via the + // play() method. Outputs typically play the audio on the user's device. + // API: + // .play(sampleRate, left, right) - rate in herz; PCM data as Uint8Arrays + // .stop() + // .enqueuedTime - float, in seconds + // .enabled - wether the output does anything upon receiving data + AudioOutput: {}, + + Now: function() { + return window.performance + ? window.performance.now() / 1000 + : Date.now() / 1000; + }, + + CreateVideoElements: function() { + var elements = document.querySelectorAll('.jsmpeg'); + for (var i = 0; i < elements.length; i++) { + new JSMpeg.VideoElement(elements[i]); + } + }, + + Fill: function(array, value) { + if (array.fill) { + array.fill(value); + } + else { + for (var i = 0; i < array.length; i++) { + array[i] = value; + } + } + }, + + Base64ToArrayBuffer: function(base64) { + var binary = window.atob(base64); + var length = binary.length; + var bytes = new Uint8Array(length); + for (var i = 0; i < length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; + }, + + // The build process may append `JSMpeg.WASM_BINARY_INLINED = base64data;` + // to the minified source. + // If this property is present, jsmpeg will use the inlined binary data + // instead of trying to load a jsmpeg.wasm file via Ajax. + WASM_BINARY_INLINED: null +}; + +// Automatically create players for all found
elements. +if (document.readyState === 'complete') { + JSMpeg.CreateVideoElements(); +} +else { + document.addEventListener('DOMContentLoaded', JSMpeg.CreateVideoElements); +} + + diff --git a/public/static/jsmpeg-master/src/mp2-wasm.js b/public/static/jsmpeg-master/src/mp2-wasm.js new file mode 100644 index 0000000..e951af0 --- /dev/null +++ b/public/static/jsmpeg-master/src/mp2-wasm.js @@ -0,0 +1,124 @@ +JSMpeg.Decoder.MP2AudioWASM = (function(){ "use strict"; + +// Based on kjmp2 by Martin J. Fiedler +// http://keyj.emphy.de/kjmp2/ + +var MP2WASM = function(options) { + JSMpeg.Decoder.Base.call(this, options); + + this.onDecodeCallback = options.onAudioDecode; + this.module = options.wasmModule; + + this.bufferSize = options.audioBufferSize || 128*1024; + this.bufferMode = options.streaming + ? JSMpeg.BitBuffer.MODE.EVICT + : JSMpeg.BitBuffer.MODE.EXPAND; + + this.sampleRate = 0; +}; + +MP2WASM.prototype = Object.create(JSMpeg.Decoder.Base.prototype); +MP2WASM.prototype.constructor = MP2WASM; + +MP2WASM.prototype.initializeWasmDecoder = function() { + if (!this.module.instance) { + console.warn('JSMpeg: WASM module not compiled yet'); + return; + } + this.instance = this.module.instance; + this.functions = this.module.instance.exports; + this.decoder = this.functions._mp2_decoder_create(this.bufferSize, this.bufferMode); +}; + +MP2WASM.prototype.destroy = function() { + if (!this.decoder) { + return; + } + this.functions._mp2_decoder_destroy(this.decoder); +}; + +MP2WASM.prototype.bufferGetIndex = function() { + if (!this.decoder) { + return; + } + return this.functions._mp2_decoder_get_index(this.decoder); +}; + +MP2WASM.prototype.bufferSetIndex = function(index) { + if (!this.decoder) { + return; + } + this.functions._mp2_decoder_set_index(this.decoder, index); +}; + +MP2WASM.prototype.bufferWrite = function(buffers) { + if (!this.decoder) { + this.initializeWasmDecoder(); + } + + var totalLength = 0; + for (var i = 0; i < buffers.length; i++) { + totalLength += buffers[i].length; + } + + var ptr = this.functions._mp2_decoder_get_write_ptr(this.decoder, totalLength); + for (var i = 0; i < buffers.length; i++) { + this.instance.heapU8.set(buffers[i], ptr); + ptr += buffers[i].length; + } + + this.functions._mp2_decoder_did_write(this.decoder, totalLength); + return totalLength; +}; + +MP2WASM.prototype.decode = function() { + var startTime = JSMpeg.Now(); + + if (!this.decoder) { + return false; + } + + var decodedBytes = this.functions._mp2_decoder_decode(this.decoder); + if (decodedBytes === 0) { + return false; + } + + if (!this.sampleRate) { + this.sampleRate = this.functions._mp2_decoder_get_sample_rate(this.decoder); + } + + if (this.destination) { + // Create a Float32 View into the modules output channel data + var leftPtr = this.functions._mp2_decoder_get_left_channel_ptr(this.decoder), + rightPtr = this.functions._mp2_decoder_get_right_channel_ptr(this.decoder); + + var leftOffset = leftPtr / Float32Array.BYTES_PER_ELEMENT, + rightOffset = rightPtr / Float32Array.BYTES_PER_ELEMENT; + + var left = this.instance.heapF32.subarray(leftOffset, leftOffset + MP2WASM.SAMPLES_PER_FRAME), + right = this.instance.heapF32.subarray(rightOffset, rightOffset + MP2WASM.SAMPLES_PER_FRAME); + + this.destination.play(this.sampleRate, left, right); + } + + this.advanceDecodedTime(MP2WASM.SAMPLES_PER_FRAME / this.sampleRate); + + var elapsedTime = JSMpeg.Now() - startTime; + if (this.onDecodeCallback) { + this.onDecodeCallback(this, elapsedTime); + } + return true; +}; + + +MP2WASM.prototype.getCurrentTime = function() { + var enqueuedTime = this.destination ? this.destination.enqueuedTime : 0; + return this.decodedTime - enqueuedTime; +}; + +MP2WASM.SAMPLES_PER_FRAME = 1152; + +return MP2WASM; + +})(); + diff --git a/public/static/jsmpeg-master/src/mp2.js b/public/static/jsmpeg-master/src/mp2.js new file mode 100644 index 0000000..3e1f66b --- /dev/null +++ b/public/static/jsmpeg-master/src/mp2.js @@ -0,0 +1,690 @@ +JSMpeg.Decoder.MP2Audio = (function(){ "use strict"; + +// Based on kjmp2 by Martin J. Fiedler +// http://keyj.emphy.de/kjmp2/ + +var MP2 = function(options) { + JSMpeg.Decoder.Base.call(this, options); + + this.onDecodeCallback = options.onAudioDecode; + + var bufferSize = options.audioBufferSize || 128*1024; + var bufferMode = options.streaming + ? JSMpeg.BitBuffer.MODE.EVICT + : JSMpeg.BitBuffer.MODE.EXPAND; + + this.bits = new JSMpeg.BitBuffer(bufferSize, bufferMode); + + this.left = new Float32Array(1152); + this.right = new Float32Array(1152); + this.sampleRate = 44100; + + this.D = new Float32Array(1024); + this.D.set(MP2.SYNTHESIS_WINDOW, 0); + this.D.set(MP2.SYNTHESIS_WINDOW, 512); + this.V = [new Float32Array(1024), new Float32Array(1024)]; + this.U = new Int32Array(32); + this.VPos = 0; + + this.allocation = [new Array(32), new Array(32)]; + this.scaleFactorInfo = [new Uint8Array(32), new Uint8Array(32)]; + this.scaleFactor = [new Array(32), new Array(32)]; + this.sample = [new Array(32), new Array(32)]; + + for (var j = 0; j < 2; j++) { + for (var i = 0; i < 32; i++) { + this.scaleFactor[j][i] = [0, 0, 0]; + this.sample[j][i] = [0, 0, 0]; + } + } +}; + +MP2.prototype = Object.create(JSMpeg.Decoder.Base.prototype); +MP2.prototype.constructor = MP2; + +MP2.prototype.decode = function() { + var startTime = JSMpeg.Now(); + + var pos = this.bits.index >> 3; + if (pos >= this.bits.byteLength) { + return false; + } + + var decoded = this.decodeFrame(this.left, this.right); + this.bits.index = (pos + decoded) << 3; + if (!decoded) { + return false; + } + + if (this.destination) { + this.destination.play(this.sampleRate, this.left, this.right); + } + + this.advanceDecodedTime(this.left.length / this.sampleRate); + + var elapsedTime = JSMpeg.Now() - startTime; + if (this.onDecodeCallback) { + this.onDecodeCallback(this, elapsedTime); + } + return true; +}; + +MP2.prototype.getCurrentTime = function() { + var enqueuedTime = this.destination ? this.destination.enqueuedTime : 0; + return this.decodedTime - enqueuedTime; +}; + +MP2.prototype.decodeFrame = function(left, right) { + // Check for valid header: syncword OK, MPEG-Audio Layer 2 + var sync = this.bits.read(11), + version = this.bits.read(2), + layer = this.bits.read(2), + hasCRC = !this.bits.read(1); + + if ( + sync !== MP2.FRAME_SYNC || + version !== MP2.VERSION.MPEG_1 || + layer !== MP2.LAYER.II + ) { + return 0; // Invalid header or unsupported version + } + + var bitrateIndex = this.bits.read(4) - 1; + if (bitrateIndex > 13) { + return 0; // Invalid bit rate or 'free format' + } + + var sampleRateIndex = this.bits.read(2); + var sampleRate = MP2.SAMPLE_RATE[sampleRateIndex]; + if (sampleRateIndex === 3) { + return 0; // Invalid sample rate + } + if (version === MP2.VERSION.MPEG_2) { + sampleRateIndex += 4; + bitrateIndex += 14; + } + var padding = this.bits.read(1), + privat = this.bits.read(1), + mode = this.bits.read(2); + + // Parse the mode_extension, set up the stereo bound + var bound = 0; + if (mode === MP2.MODE.JOINT_STEREO) { + bound = (this.bits.read(2) + 1) << 2; + } + else { + this.bits.skip(2); + bound = (mode === MP2.MODE.MONO) ? 0 : 32; + } + + // Discard the last 4 bits of the header and the CRC value, if present + this.bits.skip(4); + if (hasCRC) { + this.bits.skip(16); + } + + // Compute the frame size + var bitrate = MP2.BIT_RATE[bitrateIndex], + sampleRate = MP2.SAMPLE_RATE[sampleRateIndex], + frameSize = ((144000 * bitrate / sampleRate) + padding)|0; + + + // Prepare the quantizer table lookups + var tab3 = 0; + var sblimit = 0; + if (version === MP2.VERSION.MPEG_2) { + // MPEG-2 (LSR) + tab3 = 2; + sblimit = 30; + } + else { + // MPEG-1 + var tab1 = (mode === MP2.MODE.MONO) ? 0 : 1; + var tab2 = MP2.QUANT_LUT_STEP_1[tab1][bitrateIndex]; + tab3 = MP2.QUANT_LUT_STEP_2[tab2][sampleRateIndex]; + sblimit = tab3 & 63; + tab3 >>= 6; + } + + if (bound > sblimit) { + bound = sblimit; + } + + // Read the allocation information + for (var sb = 0; sb < bound; sb++) { + this.allocation[0][sb] = this.readAllocation(sb, tab3); + this.allocation[1][sb] = this.readAllocation(sb, tab3); + } + + for (var sb = bound; sb < sblimit; sb++) { + this.allocation[0][sb] = + this.allocation[1][sb] = + this.readAllocation(sb, tab3); + } + + // Read scale factor selector information + var channels = (mode === MP2.MODE.MONO) ? 1 : 2; + for (var sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < channels; ch++) { + if (this.allocation[ch][sb]) { + this.scaleFactorInfo[ch][sb] = this.bits.read(2); + } + } + if (mode === MP2.MODE.MONO) { + this.scaleFactorInfo[1][sb] = this.scaleFactorInfo[0][sb]; + } + } + + // Read scale factors + for (var sb = 0; sb < sblimit; sb++) { + for (var ch = 0; ch < channels; ch++) { + if (this.allocation[ch][sb]) { + var sf = this.scaleFactor[ch][sb]; + switch (this.scaleFactorInfo[ch][sb]) { + case 0: + sf[0] = this.bits.read(6); + sf[1] = this.bits.read(6); + sf[2] = this.bits.read(6); + break; + case 1: + sf[0] = + sf[1] = this.bits.read(6); + sf[2] = this.bits.read(6); + break; + case 2: + sf[0] = + sf[1] = + sf[2] = this.bits.read(6); + break; + case 3: + sf[0] = this.bits.read(6); + sf[1] = + sf[2] = this.bits.read(6); + break; + } + } + } + if (mode === MP2.MODE.MONO) { + this.scaleFactor[1][sb][0] = this.scaleFactor[0][sb][0]; + this.scaleFactor[1][sb][1] = this.scaleFactor[0][sb][1]; + this.scaleFactor[1][sb][2] = this.scaleFactor[0][sb][2]; + } + } + + // Coefficient input and reconstruction + var outPos = 0; + for (var part = 0; part < 3; part++) { + for (var granule = 0; granule < 4; granule++) { + + // Read the samples + for (var sb = 0; sb < bound; sb++) { + this.readSamples(0, sb, part); + this.readSamples(1, sb, part); + } + for (var sb = bound; sb < sblimit; sb++) { + this.readSamples(0, sb, part); + this.sample[1][sb][0] = this.sample[0][sb][0]; + this.sample[1][sb][1] = this.sample[0][sb][1]; + this.sample[1][sb][2] = this.sample[0][sb][2]; + } + for (var sb = sblimit; sb < 32; sb++) { + this.sample[0][sb][0] = 0; + this.sample[0][sb][1] = 0; + this.sample[0][sb][2] = 0; + this.sample[1][sb][0] = 0; + this.sample[1][sb][1] = 0; + this.sample[1][sb][2] = 0; + } + + // Synthesis loop + for (var p = 0; p < 3; p++) { + // Shifting step + this.VPos = (this.VPos - 64) & 1023; + + for (var ch = 0; ch < 2; ch++) { + MP2.MatrixTransform(this.sample[ch], p, this.V[ch], this.VPos); + + // Build U, windowing, calculate output + JSMpeg.Fill(this.U, 0); + + var dIndex = 512 - (this.VPos >> 1); + var vIndex = (this.VPos % 128) >> 1; + while (vIndex < 1024) { + for (var i = 0; i < 32; ++i) { + this.U[i] += this.D[dIndex++] * this.V[ch][vIndex++]; + } + + vIndex += 128-32; + dIndex += 64-32; + } + + vIndex = (128-32 + 1024) - vIndex; + dIndex -= (512 - 32); + while (vIndex < 1024) { + for (var i = 0; i < 32; ++i) { + this.U[i] += this.D[dIndex++] * this.V[ch][vIndex++]; + } + + vIndex += 128-32; + dIndex += 64-32; + } + + // Output samples + var outChannel = ch === 0 ? left : right; + for (var j = 0; j < 32; j++) { + outChannel[outPos + j] = this.U[j] / 2147418112; + } + } // End of synthesis channel loop + outPos += 32; + } // End of synthesis sub-block loop + + } // Decoding of the granule finished + } + + this.sampleRate = sampleRate; + return frameSize; +}; + +MP2.prototype.readAllocation = function(sb, tab3) { + var tab4 = MP2.QUANT_LUT_STEP_3[tab3][sb]; + var qtab = MP2.QUANT_LUT_STEP4[tab4 & 15][this.bits.read(tab4 >> 4)]; + return qtab ? (MP2.QUANT_TAB[qtab - 1]) : 0; +}; + +MP2.prototype.readSamples = function(ch, sb, part) { + var q = this.allocation[ch][sb], + sf = this.scaleFactor[ch][sb][part], + sample = this.sample[ch][sb], + val = 0; + + if (!q) { + // No bits allocated for this subband + sample[0] = sample[1] = sample[2] = 0; + return; + } + + // Resolve scalefactor + if (sf === 63) { + sf = 0; + } + else { + var shift = (sf / 3)|0; + sf = (MP2.SCALEFACTOR_BASE[sf % 3] + ((1 << shift) >> 1)) >> shift; + } + + // Decode samples + var adj = q.levels; + if (q.group) { + // Decode grouped samples + val = this.bits.read(q.bits); + sample[0] = val % adj; + val = (val / adj)|0; + sample[1] = val % adj; + sample[2] = (val / adj)|0; + } + else { + // Decode direct samples + sample[0] = this.bits.read(q.bits); + sample[1] = this.bits.read(q.bits); + sample[2] = this.bits.read(q.bits); + } + + // Postmultiply samples + var scale = (65536 / (adj + 1))|0; + adj = ((adj + 1) >> 1) - 1; + + val = (adj - sample[0]) * scale; + sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; + + val = (adj - sample[1]) * scale; + sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; + + val = (adj - sample[2]) * scale; + sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; +}; + +MP2.MatrixTransform = function(s, ss, d, dp) { + var t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, + t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, + t25, t26, t27, t28, t29, t30, t31, t32, t33; + + t01 = s[ 0][ss] + s[31][ss]; t02 = (s[ 0][ss] - s[31][ss]) * 0.500602998235; + t03 = s[ 1][ss] + s[30][ss]; t04 = (s[ 1][ss] - s[30][ss]) * 0.505470959898; + t05 = s[ 2][ss] + s[29][ss]; t06 = (s[ 2][ss] - s[29][ss]) * 0.515447309923; + t07 = s[ 3][ss] + s[28][ss]; t08 = (s[ 3][ss] - s[28][ss]) * 0.53104259109; + t09 = s[ 4][ss] + s[27][ss]; t10 = (s[ 4][ss] - s[27][ss]) * 0.553103896034; + t11 = s[ 5][ss] + s[26][ss]; t12 = (s[ 5][ss] - s[26][ss]) * 0.582934968206; + t13 = s[ 6][ss] + s[25][ss]; t14 = (s[ 6][ss] - s[25][ss]) * 0.622504123036; + t15 = s[ 7][ss] + s[24][ss]; t16 = (s[ 7][ss] - s[24][ss]) * 0.674808341455; + t17 = s[ 8][ss] + s[23][ss]; t18 = (s[ 8][ss] - s[23][ss]) * 0.744536271002; + t19 = s[ 9][ss] + s[22][ss]; t20 = (s[ 9][ss] - s[22][ss]) * 0.839349645416; + t21 = s[10][ss] + s[21][ss]; t22 = (s[10][ss] - s[21][ss]) * 0.972568237862; + t23 = s[11][ss] + s[20][ss]; t24 = (s[11][ss] - s[20][ss]) * 1.16943993343; + t25 = s[12][ss] + s[19][ss]; t26 = (s[12][ss] - s[19][ss]) * 1.48416461631; + t27 = s[13][ss] + s[18][ss]; t28 = (s[13][ss] - s[18][ss]) * 2.05778100995; + t29 = s[14][ss] + s[17][ss]; t30 = (s[14][ss] - s[17][ss]) * 3.40760841847; + t31 = s[15][ss] + s[16][ss]; t32 = (s[15][ss] - s[16][ss]) * 10.1900081235; + + t33 = t01 + t31; t31 = (t01 - t31) * 0.502419286188; + t01 = t03 + t29; t29 = (t03 - t29) * 0.52249861494; + t03 = t05 + t27; t27 = (t05 - t27) * 0.566944034816; + t05 = t07 + t25; t25 = (t07 - t25) * 0.64682178336; + t07 = t09 + t23; t23 = (t09 - t23) * 0.788154623451; + t09 = t11 + t21; t21 = (t11 - t21) * 1.06067768599; + t11 = t13 + t19; t19 = (t13 - t19) * 1.72244709824; + t13 = t15 + t17; t17 = (t15 - t17) * 5.10114861869; + t15 = t33 + t13; t13 = (t33 - t13) * 0.509795579104; + t33 = t01 + t11; t01 = (t01 - t11) * 0.601344886935; + t11 = t03 + t09; t09 = (t03 - t09) * 0.899976223136; + t03 = t05 + t07; t07 = (t05 - t07) * 2.56291544774; + t05 = t15 + t03; t15 = (t15 - t03) * 0.541196100146; + t03 = t33 + t11; t11 = (t33 - t11) * 1.30656296488; + t33 = t05 + t03; t05 = (t05 - t03) * 0.707106781187; + t03 = t15 + t11; t15 = (t15 - t11) * 0.707106781187; + t03 += t15; + t11 = t13 + t07; t13 = (t13 - t07) * 0.541196100146; + t07 = t01 + t09; t09 = (t01 - t09) * 1.30656296488; + t01 = t11 + t07; t07 = (t11 - t07) * 0.707106781187; + t11 = t13 + t09; t13 = (t13 - t09) * 0.707106781187; + t11 += t13; t01 += t11; + t11 += t07; t07 += t13; + t09 = t31 + t17; t31 = (t31 - t17) * 0.509795579104; + t17 = t29 + t19; t29 = (t29 - t19) * 0.601344886935; + t19 = t27 + t21; t21 = (t27 - t21) * 0.899976223136; + t27 = t25 + t23; t23 = (t25 - t23) * 2.56291544774; + t25 = t09 + t27; t09 = (t09 - t27) * 0.541196100146; + t27 = t17 + t19; t19 = (t17 - t19) * 1.30656296488; + t17 = t25 + t27; t27 = (t25 - t27) * 0.707106781187; + t25 = t09 + t19; t19 = (t09 - t19) * 0.707106781187; + t25 += t19; + t09 = t31 + t23; t31 = (t31 - t23) * 0.541196100146; + t23 = t29 + t21; t21 = (t29 - t21) * 1.30656296488; + t29 = t09 + t23; t23 = (t09 - t23) * 0.707106781187; + t09 = t31 + t21; t31 = (t31 - t21) * 0.707106781187; + t09 += t31; t29 += t09; t09 += t23; t23 += t31; + t17 += t29; t29 += t25; t25 += t09; t09 += t27; + t27 += t23; t23 += t19; t19 += t31; + t21 = t02 + t32; t02 = (t02 - t32) * 0.502419286188; + t32 = t04 + t30; t04 = (t04 - t30) * 0.52249861494; + t30 = t06 + t28; t28 = (t06 - t28) * 0.566944034816; + t06 = t08 + t26; t08 = (t08 - t26) * 0.64682178336; + t26 = t10 + t24; t10 = (t10 - t24) * 0.788154623451; + t24 = t12 + t22; t22 = (t12 - t22) * 1.06067768599; + t12 = t14 + t20; t20 = (t14 - t20) * 1.72244709824; + t14 = t16 + t18; t16 = (t16 - t18) * 5.10114861869; + t18 = t21 + t14; t14 = (t21 - t14) * 0.509795579104; + t21 = t32 + t12; t32 = (t32 - t12) * 0.601344886935; + t12 = t30 + t24; t24 = (t30 - t24) * 0.899976223136; + t30 = t06 + t26; t26 = (t06 - t26) * 2.56291544774; + t06 = t18 + t30; t18 = (t18 - t30) * 0.541196100146; + t30 = t21 + t12; t12 = (t21 - t12) * 1.30656296488; + t21 = t06 + t30; t30 = (t06 - t30) * 0.707106781187; + t06 = t18 + t12; t12 = (t18 - t12) * 0.707106781187; + t06 += t12; + t18 = t14 + t26; t26 = (t14 - t26) * 0.541196100146; + t14 = t32 + t24; t24 = (t32 - t24) * 1.30656296488; + t32 = t18 + t14; t14 = (t18 - t14) * 0.707106781187; + t18 = t26 + t24; t24 = (t26 - t24) * 0.707106781187; + t18 += t24; t32 += t18; + t18 += t14; t26 = t14 + t24; + t14 = t02 + t16; t02 = (t02 - t16) * 0.509795579104; + t16 = t04 + t20; t04 = (t04 - t20) * 0.601344886935; + t20 = t28 + t22; t22 = (t28 - t22) * 0.899976223136; + t28 = t08 + t10; t10 = (t08 - t10) * 2.56291544774; + t08 = t14 + t28; t14 = (t14 - t28) * 0.541196100146; + t28 = t16 + t20; t20 = (t16 - t20) * 1.30656296488; + t16 = t08 + t28; t28 = (t08 - t28) * 0.707106781187; + t08 = t14 + t20; t20 = (t14 - t20) * 0.707106781187; + t08 += t20; + t14 = t02 + t10; t02 = (t02 - t10) * 0.541196100146; + t10 = t04 + t22; t22 = (t04 - t22) * 1.30656296488; + t04 = t14 + t10; t10 = (t14 - t10) * 0.707106781187; + t14 = t02 + t22; t02 = (t02 - t22) * 0.707106781187; + t14 += t02; t04 += t14; t14 += t10; t10 += t02; + t16 += t04; t04 += t08; t08 += t14; t14 += t28; + t28 += t10; t10 += t20; t20 += t02; t21 += t16; + t16 += t32; t32 += t04; t04 += t06; t06 += t08; + t08 += t18; t18 += t14; t14 += t30; t30 += t28; + t28 += t26; t26 += t10; t10 += t12; t12 += t20; + t20 += t24; t24 += t02; + + d[dp + 48] = -t33; + d[dp + 49] = d[dp + 47] = -t21; + d[dp + 50] = d[dp + 46] = -t17; + d[dp + 51] = d[dp + 45] = -t16; + d[dp + 52] = d[dp + 44] = -t01; + d[dp + 53] = d[dp + 43] = -t32; + d[dp + 54] = d[dp + 42] = -t29; + d[dp + 55] = d[dp + 41] = -t04; + d[dp + 56] = d[dp + 40] = -t03; + d[dp + 57] = d[dp + 39] = -t06; + d[dp + 58] = d[dp + 38] = -t25; + d[dp + 59] = d[dp + 37] = -t08; + d[dp + 60] = d[dp + 36] = -t11; + d[dp + 61] = d[dp + 35] = -t18; + d[dp + 62] = d[dp + 34] = -t09; + d[dp + 63] = d[dp + 33] = -t14; + d[dp + 32] = -t05; + d[dp + 0] = t05; d[dp + 31] = -t30; + d[dp + 1] = t30; d[dp + 30] = -t27; + d[dp + 2] = t27; d[dp + 29] = -t28; + d[dp + 3] = t28; d[dp + 28] = -t07; + d[dp + 4] = t07; d[dp + 27] = -t26; + d[dp + 5] = t26; d[dp + 26] = -t23; + d[dp + 6] = t23; d[dp + 25] = -t10; + d[dp + 7] = t10; d[dp + 24] = -t15; + d[dp + 8] = t15; d[dp + 23] = -t12; + d[dp + 9] = t12; d[dp + 22] = -t19; + d[dp + 10] = t19; d[dp + 21] = -t20; + d[dp + 11] = t20; d[dp + 20] = -t13; + d[dp + 12] = t13; d[dp + 19] = -t24; + d[dp + 13] = t24; d[dp + 18] = -t31; + d[dp + 14] = t31; d[dp + 17] = -t02; + d[dp + 15] = t02; d[dp + 16] = 0.0; +}; + +MP2.FRAME_SYNC = 0x7ff; + +MP2.VERSION = { + MPEG_2_5: 0x0, + MPEG_2: 0x2, + MPEG_1: 0x3 +}; + +MP2.LAYER = { + III: 0x1, + II: 0x2, + I: 0x3 +}; + +MP2.MODE = { + STEREO: 0x0, + JOINT_STEREO: 0x1, + DUAL_CHANNEL: 0x2, + MONO: 0x3 +}; + +MP2.SAMPLE_RATE = new Uint16Array([ + 44100, 48000, 32000, 0, // MPEG-1 + 22050, 24000, 16000, 0 // MPEG-2 +]); + +MP2.BIT_RATE = new Uint16Array([ + 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1 + 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2 +]); + +MP2.SCALEFACTOR_BASE = new Uint32Array([ + 0x02000000, 0x01965FEA, 0x01428A30 +]); + +MP2.SYNTHESIS_WINDOW = new Float32Array([ + 0.0, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -1.0, -1.0, -1.0, -1.0, -1.5, + -1.5, -2.0, -2.0, -2.5, -2.5, -3.0, + -3.5, -3.5, -4.0, -4.5, -5.0, -5.5, + -6.5, -7.0, -8.0, -8.5, -9.5, -10.5, + -12.0, -13.0, -14.5, -15.5, -17.5, -19.0, + -20.5, -22.5, -24.5, -26.5, -29.0, -31.5, + -34.0, -36.5, -39.5, -42.5, -45.5, -48.5, + -52.0, -55.5, -58.5, -62.5, -66.0, -69.5, + -73.5, -77.0, -80.5, -84.5, -88.0, -91.5, + -95.0, -98.0, -101.0, -104.0, 106.5, 109.0, + 111.0, 112.5, 113.5, 114.0, 114.0, 113.5, + 112.0, 110.5, 107.5, 104.0, 100.0, 94.5, + 88.5, 81.5, 73.0, 63.5, 53.0, 41.5, + 28.5, 14.5, -1.0, -18.0, -36.0, -55.5, + -76.5, -98.5, -122.0, -147.0, -173.5, -200.5, + -229.5, -259.5, -290.5, -322.5, -355.5, -389.5, + -424.0, -459.5, -495.5, -532.0, -568.5, -605.0, + -641.5, -678.0, -714.0, -749.0, -783.5, -817.0, + -849.0, -879.5, -908.5, -935.0, -959.5, -981.0, + -1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5, + -1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, + 911.0, 869.5, 822.0, 767.5, 707.0, 640.0, + 565.5, 485.0, 397.0, 302.5, 201.0, 92.5, + -22.5, -144.0, -272.5, -407.0, -547.5, -694.0, + -846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5, + -1852.5, -2031.5, -2212.5, -2394.0, -2576.5, -2758.5, + -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5, + -3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5, + -4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5, + -4983.0, -4967.5, -4931.5, -4875.0, -4796.0, -4694.5, + -4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0, + 3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5, + 1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0, + -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5, + -6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5, + -11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5, + -16895.5, -17820.0, -18744.5, -19668.0, -20588.0, -21503.0, + -22410.5, -23308.5, -24195.0, -25068.5, -25926.5, -26767.0, + -27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0, + -32009.5, -32645.0, -33247.0, -33814.5, -34346.0, -34839.5, + -35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0, + -37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, + 37428.0, 37315.0, 37156.5, 36954.0, 36707.5, 36417.5, + 36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5, + 33247.0, 32645.0, 32009.5, 31342.0, 30644.5, 29919.0, + 29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5, + 24195.0, 23308.5, 22410.5, 21503.0, 20588.0, 19668.0, + 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5, + 13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, + 8077.5, 7274.0, 6490.0, 5727.5, 4987.5, 4270.0, + 3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0, + -35.0, -541.0, -1018.5, -1467.5, -1888.0, -2280.5, + -2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0, + 4246.0, 4420.0, 4569.5, 4694.5, 4796.0, 4875.0, + 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0, + 4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5, + 4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5, + 3294.5, 3118.5, 2939.5, 2758.5, 2576.5, 2394.0, + 2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5, + 1165.0, 1003.0, 846.0, 694.0, 547.5, 407.0, + 272.5, 144.0, 22.5, -92.5, -201.0, -302.5, + -397.0, -485.0, -565.5, -640.0, -707.0, -767.5, + -822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, + 1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5, + 1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0, + 908.5, 879.5, 849.0, 817.0, 783.5, 749.0, + 714.0, 678.0, 641.5, 605.0, 568.5, 532.0, + 495.5, 459.5, 424.0, 389.5, 355.5, 322.5, + 290.5, 259.5, 229.5, 200.5, 173.5, 147.0, + 122.0, 98.5, 76.5, 55.5, 36.0, 18.0, + 1.0, -14.5, -28.5, -41.5, -53.0, -63.5, + -73.0, -81.5, -88.5, -94.5, -100.0, -104.0, + -107.5, -110.5, -112.0, -113.5, -114.0, -114.0, + -113.5, -112.5, -111.0, -109.0, 106.5, 104.0, + 101.0, 98.0, 95.0, 91.5, 88.0, 84.5, + 80.5, 77.0, 73.5, 69.5, 66.0, 62.5, + 58.5, 55.5, 52.0, 48.5, 45.5, 42.5, + 39.5, 36.5, 34.0, 31.5, 29.0, 26.5, + 24.5, 22.5, 20.5, 19.0, 17.5, 15.5, + 14.5, 13.0, 12.0, 10.5, 9.5, 8.5, + 8.0, 7.0, 6.5, 5.5, 5.0, 4.5, + 4.0, 3.5, 3.5, 3.0, 2.5, 2.5, + 2.0, 2.0, 1.5, 1.5, 1.0, 1.0, + 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5 +]); + +// Quantizer lookup, step 1: bitrate classes +MP2.QUANT_LUT_STEP_1 = [ + // 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate + [ 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2], // mono + // 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan + [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2] // stereo +]; + +// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit +MP2.QUANT_TAB = { + A: (27 | 64), // Table 3-B.2a: high-rate, sblimit = 27 + B: (30 | 64), // Table 3-B.2b: high-rate, sblimit = 30 + C: 8, // Table 3-B.2c: low-rate, sblimit = 8 + D: 12 // Table 3-B.2d: low-rate, sblimit = 12 +}; + +MP2.QUANT_LUT_STEP_2 = [ + // 44.1 kHz, 48 kHz, 32 kHz + [MP2.QUANT_TAB.C, MP2.QUANT_TAB.C, MP2.QUANT_TAB.D], // 32 - 48 kbit/sec/ch + [MP2.QUANT_TAB.A, MP2.QUANT_TAB.A, MP2.QUANT_TAB.A], // 56 - 80 kbit/sec/ch + [MP2.QUANT_TAB.B, MP2.QUANT_TAB.A, MP2.QUANT_TAB.B] // 96+ kbit/sec/ch +]; + +// Quantizer lookup, step 3: B2 table, subband -> nbal, row index +// (upper 4 bits: nbal, lower 4 bits: row index) +MP2.QUANT_LUT_STEP_3 = [ + // Low-rate table (3-B.2c and 3-B.2d) + [ + 0x44,0x44, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34 + ], + // High-rate table (3-B.2a and 3-B.2b) + [ + 0x43,0x43,0x43, + 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42, + 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20 + ], + // MPEG-2 LSR table (B.2 in ISO 13818-3) + [ + 0x45,0x45,0x45,0x45, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34, + 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, + 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24 + ] +]; + +// Quantizer lookup, step 4: table row, allocation[] value -> quant table index +MP2.QUANT_LUT_STEP4 = [ + [0, 1, 2, 17], + [0, 1, 2, 3, 4, 5, 6, 17], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17], + [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +]; + +MP2.QUANT_TAB = [ + {levels: 3, group: 1, bits: 5}, // 1 + {levels: 5, group: 1, bits: 7}, // 2 + {levels: 7, group: 0, bits: 3}, // 3 + {levels: 9, group: 1, bits: 10}, // 4 + {levels: 15, group: 0, bits: 4}, // 5 + {levels: 31, group: 0, bits: 5}, // 6 + {levels: 63, group: 0, bits: 6}, // 7 + {levels: 127, group: 0, bits: 7}, // 8 + {levels: 255, group: 0, bits: 8}, // 9 + {levels: 511, group: 0, bits: 9}, // 10 + {levels: 1023, group: 0, bits: 10}, // 11 + {levels: 2047, group: 0, bits: 11}, // 12 + {levels: 4095, group: 0, bits: 12}, // 13 + {levels: 8191, group: 0, bits: 13}, // 14 + {levels: 16383, group: 0, bits: 14}, // 15 + {levels: 32767, group: 0, bits: 15}, // 16 + {levels: 65535, group: 0, bits: 16} // 17 +]; + +return MP2; + +})(); + diff --git a/public/static/jsmpeg-master/src/mpeg1-wasm.js b/public/static/jsmpeg-master/src/mpeg1-wasm.js new file mode 100644 index 0000000..83eaa81 --- /dev/null +++ b/public/static/jsmpeg-master/src/mpeg1-wasm.js @@ -0,0 +1,133 @@ +JSMpeg.Decoder.MPEG1VideoWASM = (function(){ "use strict"; + +var MPEG1WASM = function(options) { + JSMpeg.Decoder.Base.call(this, options); + + this.onDecodeCallback = options.onVideoDecode; + this.module = options.wasmModule; + + this.bufferSize = options.videoBufferSize || 512*1024; + this.bufferMode = options.streaming + ? JSMpeg.BitBuffer.MODE.EVICT + : JSMpeg.BitBuffer.MODE.EXPAND; + + this.decodeFirstFrame = options.decodeFirstFrame !== false; + this.hasSequenceHeader = false; +}; + +MPEG1WASM.prototype = Object.create(JSMpeg.Decoder.Base.prototype); +MPEG1WASM.prototype.constructor = MPEG1WASM; + +MPEG1WASM.prototype.initializeWasmDecoder = function() { + if (!this.module.instance) { + console.warn('JSMpeg: WASM module not compiled yet'); + return; + } + this.instance = this.module.instance; + this.functions = this.module.instance.exports; + this.decoder = this.functions._mpeg1_decoder_create(this.bufferSize, this.bufferMode); +}; + +MPEG1WASM.prototype.destroy = function() { + if (!this.decoder) { + return; + } + this.functions._mpeg1_decoder_destroy(this.decoder); +}; + +MPEG1WASM.prototype.bufferGetIndex = function() { + if (!this.decoder) { + return; + } + return this.functions._mpeg1_decoder_get_index(this.decoder); +}; + +MPEG1WASM.prototype.bufferSetIndex = function(index) { + if (!this.decoder) { + return; + } + this.functions._mpeg1_decoder_set_index(this.decoder, index); +}; + +MPEG1WASM.prototype.bufferWrite = function(buffers) { + if (!this.decoder) { + this.initializeWasmDecoder(); + } + + var totalLength = 0; + for (var i = 0; i < buffers.length; i++) { + totalLength += buffers[i].length; + } + + var ptr = this.functions._mpeg1_decoder_get_write_ptr(this.decoder, totalLength); + for (var i = 0; i < buffers.length; i++) { + this.instance.heapU8.set(buffers[i], ptr); + ptr += buffers[i].length; + } + + this.functions._mpeg1_decoder_did_write(this.decoder, totalLength); + return totalLength; +}; + +MPEG1WASM.prototype.write = function(pts, buffers) { + JSMpeg.Decoder.Base.prototype.write.call(this, pts, buffers); + + if (!this.hasSequenceHeader && this.functions._mpeg1_decoder_has_sequence_header(this.decoder)) { + this.loadSequenceHeader(); + } +}; + +MPEG1WASM.prototype.loadSequenceHeader = function() { + this.hasSequenceHeader = true; + this.frameRate = this.functions._mpeg1_decoder_get_frame_rate(this.decoder); + this.codedSize = this.functions._mpeg1_decoder_get_coded_size(this.decoder); + + if (this.destination) { + var w = this.functions._mpeg1_decoder_get_width(this.decoder); + var h = this.functions._mpeg1_decoder_get_height(this.decoder); + this.destination.resize(w, h); + } + + if (this.decodeFirstFrame) { + this.decode(); + } +}; + +MPEG1WASM.prototype.decode = function() { + var startTime = JSMpeg.Now(); + + if (!this.decoder) { + return false; + } + + var didDecode = this.functions._mpeg1_decoder_decode(this.decoder); + if (!didDecode) { + return false; + } + + // Invoke decode callbacks + if (this.destination) { + var ptrY = this.functions._mpeg1_decoder_get_y_ptr(this.decoder), + ptrCr = this.functions._mpeg1_decoder_get_cr_ptr(this.decoder), + ptrCb = this.functions._mpeg1_decoder_get_cb_ptr(this.decoder); + + var dy = this.instance.heapU8.subarray(ptrY, ptrY + this.codedSize); + var dcr = this.instance.heapU8.subarray(ptrCr, ptrCr + (this.codedSize >> 2)); + var dcb = this.instance.heapU8.subarray(ptrCb, ptrCb + (this.codedSize >> 2)); + + this.destination.render(dy, dcr, dcb, false); + } + + this.advanceDecodedTime(1/this.frameRate); + + var elapsedTime = JSMpeg.Now() - startTime; + if (this.onDecodeCallback) { + this.onDecodeCallback(this, elapsedTime); + } + return true; +}; + +return MPEG1WASM; + +})(); + diff --git a/public/static/jsmpeg-master/src/mpeg1.js b/public/static/jsmpeg-master/src/mpeg1.js new file mode 100644 index 0000000..856c58d --- /dev/null +++ b/public/static/jsmpeg-master/src/mpeg1.js @@ -0,0 +1,1683 @@ +JSMpeg.Decoder.MPEG1Video = (function(){ "use strict"; + +// Inspired by Java MPEG-1 Video Decoder and Player by Zoltan Korandi +// https://sourceforge.net/projects/javampeg1video/ + +var MPEG1 = function(options) { + JSMpeg.Decoder.Base.call(this, options); + + this.onDecodeCallback = options.onVideoDecode; + + var bufferSize = options.videoBufferSize || 512*1024; + var bufferMode = options.streaming + ? JSMpeg.BitBuffer.MODE.EVICT + : JSMpeg.BitBuffer.MODE.EXPAND; + + this.bits = new JSMpeg.BitBuffer(bufferSize, bufferMode); + + this.customIntraQuantMatrix = new Uint8Array(64); + this.customNonIntraQuantMatrix = new Uint8Array(64); + this.blockData = new Int32Array(64); + + this.currentFrame = 0; + this.decodeFirstFrame = options.decodeFirstFrame !== false; +}; + +MPEG1.prototype = Object.create(JSMpeg.Decoder.Base.prototype); +MPEG1.prototype.constructor = MPEG1; + +MPEG1.prototype.write = function(pts, buffers) { + JSMpeg.Decoder.Base.prototype.write.call(this, pts, buffers); + + if (!this.hasSequenceHeader) { + if (this.bits.findStartCode(MPEG1.START.SEQUENCE) === -1) { + return false; + } + this.decodeSequenceHeader(); + + if (this.decodeFirstFrame) { + this.decode(); + } + } +}; + +MPEG1.prototype.decode = function() { + var startTime = JSMpeg.Now(); + + if (!this.hasSequenceHeader) { + return false; + } + + if (this.bits.findStartCode(MPEG1.START.PICTURE) === -1) { + var bufferedBytes = this.bits.byteLength - (this.bits.index >> 3); + return false; + } + + this.decodePicture(); + this.advanceDecodedTime(1/this.frameRate); + + var elapsedTime = JSMpeg.Now() - startTime; + if (this.onDecodeCallback) { + this.onDecodeCallback(this, elapsedTime); + } + return true; +}; + +MPEG1.prototype.readHuffman = function(codeTable) { + var state = 0; + do { + state = codeTable[state + this.bits.read(1)]; + } while (state >= 0 && codeTable[state] !== 0); + return codeTable[state+2]; +}; + + +// Sequence Layer + +MPEG1.prototype.frameRate = 30; +MPEG1.prototype.decodeSequenceHeader = function() { + var newWidth = this.bits.read(12), + newHeight = this.bits.read(12); + + // skip pixel aspect ratio + this.bits.skip(4); + + this.frameRate = MPEG1.PICTURE_RATE[this.bits.read(4)]; + + // skip bitRate, marker, bufferSize and constrained bit + this.bits.skip(18 + 1 + 10 + 1); + + if (newWidth !== this.width || newHeight !== this.height) { + this.width = newWidth; + this.height = newHeight; + + this.initBuffers(); + + if (this.destination) { + this.destination.resize(newWidth, newHeight); + } + } + + if (this.bits.read(1)) { // load custom intra quant matrix? + for (var i = 0; i < 64; i++) { + this.customIntraQuantMatrix[MPEG1.ZIG_ZAG[i]] = this.bits.read(8); + } + this.intraQuantMatrix = this.customIntraQuantMatrix; + } + + if (this.bits.read(1)) { // load custom non intra quant matrix? + for (var i = 0; i < 64; i++) { + var idx = MPEG1.ZIG_ZAG[i]; + this.customNonIntraQuantMatrix[idx] = this.bits.read(8); + } + this.nonIntraQuantMatrix = this.customNonIntraQuantMatrix; + } + + this.hasSequenceHeader = true; +}; + +MPEG1.prototype.initBuffers = function() { + this.intraQuantMatrix = MPEG1.DEFAULT_INTRA_QUANT_MATRIX; + this.nonIntraQuantMatrix = MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX; + + this.mbWidth = (this.width + 15) >> 4; + this.mbHeight = (this.height + 15) >> 4; + this.mbSize = this.mbWidth * this.mbHeight; + + this.codedWidth = this.mbWidth << 4; + this.codedHeight = this.mbHeight << 4; + this.codedSize = this.codedWidth * this.codedHeight; + + this.halfWidth = this.mbWidth << 3; + this.halfHeight = this.mbHeight << 3; + + // Allocated buffers and resize the canvas + this.currentY = new Uint8ClampedArray(this.codedSize); + this.currentY32 = new Uint32Array(this.currentY.buffer); + + this.currentCr = new Uint8ClampedArray(this.codedSize >> 2); + this.currentCr32 = new Uint32Array(this.currentCr.buffer); + + this.currentCb = new Uint8ClampedArray(this.codedSize >> 2); + this.currentCb32 = new Uint32Array(this.currentCb.buffer); + + + this.forwardY = new Uint8ClampedArray(this.codedSize); + this.forwardY32 = new Uint32Array(this.forwardY.buffer); + + this.forwardCr = new Uint8ClampedArray(this.codedSize >> 2); + this.forwardCr32 = new Uint32Array(this.forwardCr.buffer); + + this.forwardCb = new Uint8ClampedArray(this.codedSize >> 2); + this.forwardCb32 = new Uint32Array(this.forwardCb.buffer); +}; + + +// Picture Layer + +MPEG1.prototype.currentY = null; +MPEG1.prototype.currentCr = null; +MPEG1.prototype.currentCb = null; + +MPEG1.prototype.pictureType = 0; + +// Buffers for motion compensation +MPEG1.prototype.forwardY = null; +MPEG1.prototype.forwardCr = null; +MPEG1.prototype.forwardCb = null; + +MPEG1.prototype.fullPelForward = false; +MPEG1.prototype.forwardFCode = 0; +MPEG1.prototype.forwardRSize = 0; +MPEG1.prototype.forwardF = 0; + +MPEG1.prototype.decodePicture = function(skipOutput) { + this.currentFrame++; + + this.bits.skip(10); // skip temporalReference + this.pictureType = this.bits.read(3); + this.bits.skip(16); // skip vbv_delay + + // Skip B and D frames or unknown coding type + if (this.pictureType <= 0 || this.pictureType >= MPEG1.PICTURE_TYPE.B) { + return; + } + + // full_pel_forward, forward_f_code + if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) { + this.fullPelForward = this.bits.read(1); + this.forwardFCode = this.bits.read(3); + if (this.forwardFCode === 0) { + // Ignore picture with zero forward_f_code + return; + } + this.forwardRSize = this.forwardFCode - 1; + this.forwardF = 1 << this.forwardRSize; + } + + var code = 0; + do { + code = this.bits.findNextStartCode(); + } while (code === MPEG1.START.EXTENSION || code === MPEG1.START.USER_DATA ); + + + while (code >= MPEG1.START.SLICE_FIRST && code <= MPEG1.START.SLICE_LAST) { + this.decodeSlice(code & 0x000000FF); + code = this.bits.findNextStartCode(); + } + + if (code !== -1) { + // We found the next start code; rewind 32bits and let the main loop + // handle it. + this.bits.rewind(32); + } + + // Invoke decode callbacks + if (this.destination) { + this.destination.render(this.currentY, this.currentCr, this.currentCb, true); + } + + // If this is a reference picutre then rotate the prediction pointers + if ( + this.pictureType === MPEG1.PICTURE_TYPE.INTRA || + this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE + ) { + var + tmpY = this.forwardY, + tmpY32 = this.forwardY32, + tmpCr = this.forwardCr, + tmpCr32 = this.forwardCr32, + tmpCb = this.forwardCb, + tmpCb32 = this.forwardCb32; + + this.forwardY = this.currentY; + this.forwardY32 = this.currentY32; + this.forwardCr = this.currentCr; + this.forwardCr32 = this.currentCr32; + this.forwardCb = this.currentCb; + this.forwardCb32 = this.currentCb32; + + this.currentY = tmpY; + this.currentY32 = tmpY32; + this.currentCr = tmpCr; + this.currentCr32 = tmpCr32; + this.currentCb = tmpCb; + this.currentCb32 = tmpCb32; + } +}; + + +// Slice Layer + +MPEG1.prototype.quantizerScale = 0; +MPEG1.prototype.sliceBegin = false; + +MPEG1.prototype.decodeSlice = function(slice) { + this.sliceBegin = true; + this.macroblockAddress = (slice - 1) * this.mbWidth - 1; + + // Reset motion vectors and DC predictors + this.motionFwH = this.motionFwHPrev = 0; + this.motionFwV = this.motionFwVPrev = 0; + this.dcPredictorY = 128; + this.dcPredictorCr = 128; + this.dcPredictorCb = 128; + + this.quantizerScale = this.bits.read(5); + + // skip extra bits + while (this.bits.read(1)) { + this.bits.skip(8); + } + + do { + this.decodeMacroblock(); + } while (!this.bits.nextBytesAreStartCode()); +}; + + +// Macroblock Layer + +MPEG1.prototype.macroblockAddress = 0; +MPEG1.prototype.mbRow = 0; +MPEG1.prototype.mbCol = 0; + +MPEG1.prototype.macroblockType = 0; +MPEG1.prototype.macroblockIntra = false; +MPEG1.prototype.macroblockMotFw = false; + +MPEG1.prototype.motionFwH = 0; +MPEG1.prototype.motionFwV = 0; +MPEG1.prototype.motionFwHPrev = 0; +MPEG1.prototype.motionFwVPrev = 0; + +MPEG1.prototype.decodeMacroblock = function() { + // Decode macroblock_address_increment + var + increment = 0, + t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT); + + while (t === 34) { + // macroblock_stuffing + t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT); + } + while (t === 35) { + // macroblock_escape + increment += 33; + t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT); + } + increment += t; + + // Process any skipped macroblocks + if (this.sliceBegin) { + // The first macroblock_address_increment of each slice is relative + // to beginning of the preverious row, not the preverious macroblock + this.sliceBegin = false; + this.macroblockAddress += increment; + } + else { + if (this.macroblockAddress + increment >= this.mbSize) { + // Illegal (too large) macroblock_address_increment + return; + } + if (increment > 1) { + // Skipped macroblocks reset DC predictors + this.dcPredictorY = 128; + this.dcPredictorCr = 128; + this.dcPredictorCb = 128; + + // Skipped macroblocks in P-pictures reset motion vectors + if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) { + this.motionFwH = this.motionFwHPrev = 0; + this.motionFwV = this.motionFwVPrev = 0; + } + } + + // Predict skipped macroblocks + while (increment > 1) { + this.macroblockAddress++; + this.mbRow = (this.macroblockAddress / this.mbWidth)|0; + this.mbCol = this.macroblockAddress % this.mbWidth; + this.copyMacroblock( + this.motionFwH, this.motionFwV, + this.forwardY, this.forwardCr, this.forwardCb + ); + increment--; + } + this.macroblockAddress++; + } + this.mbRow = (this.macroblockAddress / this.mbWidth)|0; + this.mbCol = this.macroblockAddress % this.mbWidth; + + // Process the current macroblock + var mbTable = MPEG1.MACROBLOCK_TYPE[this.pictureType]; + this.macroblockType = this.readHuffman(mbTable); + this.macroblockIntra = (this.macroblockType & 0x01); + this.macroblockMotFw = (this.macroblockType & 0x08); + + // Quantizer scale + if ((this.macroblockType & 0x10) !== 0) { + this.quantizerScale = this.bits.read(5); + } + + if (this.macroblockIntra) { + // Intra-coded macroblocks reset motion vectors + this.motionFwH = this.motionFwHPrev = 0; + this.motionFwV = this.motionFwVPrev = 0; + } + else { + // Non-intra macroblocks reset DC predictors + this.dcPredictorY = 128; + this.dcPredictorCr = 128; + this.dcPredictorCb = 128; + + this.decodeMotionVectors(); + this.copyMacroblock( + this.motionFwH, this.motionFwV, + this.forwardY, this.forwardCr, this.forwardCb + ); + } + + // Decode blocks + var cbp = ((this.macroblockType & 0x02) !== 0) + ? this.readHuffman(MPEG1.CODE_BLOCK_PATTERN) + : (this.macroblockIntra ? 0x3f : 0); + + for (var block = 0, mask = 0x20; block < 6; block++) { + if ((cbp & mask) !== 0) { + this.decodeBlock(block); + } + mask >>= 1; + } +}; + + +MPEG1.prototype.decodeMotionVectors = function() { + var code, d, r = 0; + + // Forward + if (this.macroblockMotFw) { + // Horizontal forward + code = this.readHuffman(MPEG1.MOTION); + if ((code !== 0) && (this.forwardF !== 1)) { + r = this.bits.read(this.forwardRSize); + d = ((Math.abs(code) - 1) << this.forwardRSize) + r + 1; + if (code < 0) { + d = -d; + } + } + else { + d = code; + } + + this.motionFwHPrev += d; + if (this.motionFwHPrev > (this.forwardF << 4) - 1) { + this.motionFwHPrev -= this.forwardF << 5; + } + else if (this.motionFwHPrev < ((-this.forwardF) << 4)) { + this.motionFwHPrev += this.forwardF << 5; + } + + this.motionFwH = this.motionFwHPrev; + if (this.fullPelForward) { + this.motionFwH <<= 1; + } + + // Vertical forward + code = this.readHuffman(MPEG1.MOTION); + if ((code !== 0) && (this.forwardF !== 1)) { + r = this.bits.read(this.forwardRSize); + d = ((Math.abs(code) - 1) << this.forwardRSize) + r + 1; + if (code < 0) { + d = -d; + } + } + else { + d = code; + } + + this.motionFwVPrev += d; + if (this.motionFwVPrev > (this.forwardF << 4) - 1) { + this.motionFwVPrev -= this.forwardF << 5; + } + else if (this.motionFwVPrev < ((-this.forwardF) << 4)) { + this.motionFwVPrev += this.forwardF << 5; + } + + this.motionFwV = this.motionFwVPrev; + if (this.fullPelForward) { + this.motionFwV <<= 1; + } + } + else if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) { + // No motion information in P-picture, reset vectors + this.motionFwH = this.motionFwHPrev = 0; + this.motionFwV = this.motionFwVPrev = 0; + } +}; + +MPEG1.prototype.copyMacroblock = function(motionH, motionV, sY, sCr, sCb) { + var + width, scan, + H, V, oddH, oddV, + src, dest, last; + + // We use 32bit writes here + var dY = this.currentY32, + dCb = this.currentCb32, + dCr = this.currentCr32; + + // Luminance + width = this.codedWidth; + scan = width - 16; + + H = motionH >> 1; + V = motionV >> 1; + oddH = (motionH & 1) === 1; + oddV = (motionV & 1) === 1; + + src = ((this.mbRow << 4) + V) * width + (this.mbCol << 4) + H; + dest = (this.mbRow * width + this.mbCol) << 2; + last = dest + (width << 2); + + var x, y1, y2, y; + if (oddH) { + if (oddV) { + while (dest < last) { + y1 = sY[src] + sY[src+width]; src++; + for (x = 0; x < 4; x++) { + y2 = sY[src] + sY[src+width]; src++; + y = (((y1 + y2 + 2) >> 2) & 0xff); + + y1 = sY[src] + sY[src+width]; src++; + y |= (((y1 + y2 + 2) << 6) & 0xff00); + + y2 = sY[src] + sY[src+width]; src++; + y |= (((y1 + y2 + 2) << 14) & 0xff0000); + + y1 = sY[src] + sY[src+width]; src++; + y |= (((y1 + y2 + 2) << 22) & 0xff000000); + + dY[dest++] = y; + } + dest += scan >> 2; src += scan-1; + } + } + else { + while (dest < last) { + y1 = sY[src++]; + for (x = 0; x < 4; x++) { + y2 = sY[src++]; + y = (((y1 + y2 + 1) >> 1) & 0xff); + + y1 = sY[src++]; + y |= (((y1 + y2 + 1) << 7) & 0xff00); + + y2 = sY[src++]; + y |= (((y1 + y2 + 1) << 15) & 0xff0000); + + y1 = sY[src++]; + y |= (((y1 + y2 + 1) << 23) & 0xff000000); + + dY[dest++] = y; + } + dest += scan >> 2; src += scan-1; + } + } + } + else { + if (oddV) { + while (dest < last) { + for (x = 0; x < 4; x++) { + y = (((sY[src] + sY[src+width] + 1) >> 1) & 0xff); src++; + y |= (((sY[src] + sY[src+width] + 1) << 7) & 0xff00); src++; + y |= (((sY[src] + sY[src+width] + 1) << 15) & 0xff0000); src++; + y |= (((sY[src] + sY[src+width] + 1) << 23) & 0xff000000); src++; + + dY[dest++] = y; + } + dest += scan >> 2; src += scan; + } + } + else { + while (dest < last) { + for (x = 0; x < 4; x++) { + y = sY[src]; src++; + y |= sY[src] << 8; src++; + y |= sY[src] << 16; src++; + y |= sY[src] << 24; src++; + + dY[dest++] = y; + } + dest += scan >> 2; src += scan; + } + } + } + + // Chrominance + + width = this.halfWidth; + scan = width - 8; + + H = (motionH/2) >> 1; + V = (motionV/2) >> 1; + oddH = ((motionH/2) & 1) === 1; + oddV = ((motionV/2) & 1) === 1; + + src = ((this.mbRow << 3) + V) * width + (this.mbCol << 3) + H; + dest = (this.mbRow * width + this.mbCol) << 1; + last = dest + (width << 1); + + var cr1, cr2, cr, + cb1, cb2, cb; + if (oddH) { + if (oddV) { + while (dest < last) { + cr1 = sCr[src] + sCr[src+width]; + cb1 = sCb[src] + sCb[src+width]; + src++; + for (x = 0; x < 2; x++) { + cr2 = sCr[src] + sCr[src+width]; + cb2 = sCb[src] + sCb[src+width]; src++; + cr = (((cr1 + cr2 + 2) >> 2) & 0xff); + cb = (((cb1 + cb2 + 2) >> 2) & 0xff); + + cr1 = sCr[src] + sCr[src+width]; + cb1 = sCb[src] + sCb[src+width]; src++; + cr |= (((cr1 + cr2 + 2) << 6) & 0xff00); + cb |= (((cb1 + cb2 + 2) << 6) & 0xff00); + + cr2 = sCr[src] + sCr[src+width]; + cb2 = sCb[src] + sCb[src+width]; src++; + cr |= (((cr1 + cr2 + 2) << 14) & 0xff0000); + cb |= (((cb1 + cb2 + 2) << 14) & 0xff0000); + + cr1 = sCr[src] + sCr[src+width]; + cb1 = sCb[src] + sCb[src+width]; src++; + cr |= (((cr1 + cr2 + 2) << 22) & 0xff000000); + cb |= (((cb1 + cb2 + 2) << 22) & 0xff000000); + + dCr[dest] = cr; + dCb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan-1; + } + } + else { + while (dest < last) { + cr1 = sCr[src]; + cb1 = sCb[src]; + src++; + for (x = 0; x < 2; x++) { + cr2 = sCr[src]; + cb2 = sCb[src++]; + cr = (((cr1 + cr2 + 1) >> 1) & 0xff); + cb = (((cb1 + cb2 + 1) >> 1) & 0xff); + + cr1 = sCr[src]; + cb1 = sCb[src++]; + cr |= (((cr1 + cr2 + 1) << 7) & 0xff00); + cb |= (((cb1 + cb2 + 1) << 7) & 0xff00); + + cr2 = sCr[src]; + cb2 = sCb[src++]; + cr |= (((cr1 + cr2 + 1) << 15) & 0xff0000); + cb |= (((cb1 + cb2 + 1) << 15) & 0xff0000); + + cr1 = sCr[src]; + cb1 = sCb[src++]; + cr |= (((cr1 + cr2 + 1) << 23) & 0xff000000); + cb |= (((cb1 + cb2 + 1) << 23) & 0xff000000); + + dCr[dest] = cr; + dCb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan-1; + } + } + } + else { + if (oddV) { + while (dest < last) { + for (x = 0; x < 2; x++) { + cr = (((sCr[src] + sCr[src+width] + 1) >> 1) & 0xff); + cb = (((sCb[src] + sCb[src+width] + 1) >> 1) & 0xff); src++; + + cr |= (((sCr[src] + sCr[src+width] + 1) << 7) & 0xff00); + cb |= (((sCb[src] + sCb[src+width] + 1) << 7) & 0xff00); src++; + + cr |= (((sCr[src] + sCr[src+width] + 1) << 15) & 0xff0000); + cb |= (((sCb[src] + sCb[src+width] + 1) << 15) & 0xff0000); src++; + + cr |= (((sCr[src] + sCr[src+width] + 1) << 23) & 0xff000000); + cb |= (((sCb[src] + sCb[src+width] + 1) << 23) & 0xff000000); src++; + + dCr[dest] = cr; + dCb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan; + } + } + else { + while (dest < last) { + for (x = 0; x < 2; x++) { + cr = sCr[src]; + cb = sCb[src]; src++; + + cr |= sCr[src] << 8; + cb |= sCb[src] << 8; src++; + + cr |= sCr[src] << 16; + cb |= sCb[src] << 16; src++; + + cr |= sCr[src] << 24; + cb |= sCb[src] << 24; src++; + + dCr[dest] = cr; + dCb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan; + } + } + } +}; + + +// Block layer + +MPEG1.prototype.dcPredictorY = 0; +MPEG1.prototype.dcPredictorCr = 0; +MPEG1.prototype.dcPredictorCb = 0; + +MPEG1.prototype.blockData = null; + +MPEG1.prototype.decodeBlock = function(block) { + + var + n = 0, + quantMatrix; + + // Decode DC coefficient of intra-coded blocks + if (this.macroblockIntra) { + var + predictor, + dctSize; + + // DC prediction + + if (block < 4) { + predictor = this.dcPredictorY; + dctSize = this.readHuffman(MPEG1.DCT_DC_SIZE_LUMINANCE); + } + else { + predictor = (block === 4 ? this.dcPredictorCr : this.dcPredictorCb); + dctSize = this.readHuffman(MPEG1.DCT_DC_SIZE_CHROMINANCE); + } + + // Read DC coeff + if (dctSize > 0) { + var differential = this.bits.read(dctSize); + if ((differential & (1 << (dctSize - 1))) !== 0) { + this.blockData[0] = predictor + differential; + } + else { + this.blockData[0] = predictor + ((-1 << dctSize)|(differential+1)); + } + } + else { + this.blockData[0] = predictor; + } + + // Save predictor value + if (block < 4) { + this.dcPredictorY = this.blockData[0]; + } + else if (block === 4) { + this.dcPredictorCr = this.blockData[0]; + } + else { + this.dcPredictorCb = this.blockData[0]; + } + + // Dequantize + premultiply + this.blockData[0] <<= (3 + 5); + + quantMatrix = this.intraQuantMatrix; + n = 1; + } + else { + quantMatrix = this.nonIntraQuantMatrix; + } + + // Decode AC coefficients (+DC for non-intra) + var level = 0; + while (true) { + var + run = 0, + coeff = this.readHuffman(MPEG1.DCT_COEFF); + + if ((coeff === 0x0001) && (n > 0) && (this.bits.read(1) === 0)) { + // end_of_block + break; + } + if (coeff === 0xffff) { + // escape + run = this.bits.read(6); + level = this.bits.read(8); + if (level === 0) { + level = this.bits.read(8); + } + else if (level === 128) { + level = this.bits.read(8) - 256; + } + else if (level > 128) { + level = level - 256; + } + } + else { + run = coeff >> 8; + level = coeff & 0xff; + if (this.bits.read(1)) { + level = -level; + } + } + + n += run; + var dezigZagged = MPEG1.ZIG_ZAG[n]; + n++; + + // Dequantize, oddify, clip + level <<= 1; + if (!this.macroblockIntra) { + level += (level < 0 ? -1 : 1); + } + level = (level * this.quantizerScale * quantMatrix[dezigZagged]) >> 4; + if ((level & 1) === 0) { + level -= level > 0 ? 1 : -1; + } + if (level > 2047) { + level = 2047; + } + else if (level < -2048) { + level = -2048; + } + + // Save premultiplied coefficient + this.blockData[dezigZagged] = level * MPEG1.PREMULTIPLIER_MATRIX[dezigZagged]; + } + + // Move block to its place + var + destArray, + destIndex, + scan; + + if (block < 4) { + destArray = this.currentY; + scan = this.codedWidth - 8; + destIndex = (this.mbRow * this.codedWidth + this.mbCol) << 4; + if ((block & 1) !== 0) { + destIndex += 8; + } + if ((block & 2) !== 0) { + destIndex += this.codedWidth << 3; + } + } + else { + destArray = (block === 4) ? this.currentCb : this.currentCr; + scan = (this.codedWidth >> 1) - 8; + destIndex = ((this.mbRow * this.codedWidth) << 2) + (this.mbCol << 3); + } + + if (this.macroblockIntra) { + // Overwrite (no prediction) + if (n === 1) { + MPEG1.CopyValueToDestination((this.blockData[0] + 128) >> 8, destArray, destIndex, scan); + this.blockData[0] = 0; + } + else { + MPEG1.IDCT(this.blockData); + MPEG1.CopyBlockToDestination(this.blockData, destArray, destIndex, scan); + JSMpeg.Fill(this.blockData, 0); + } + } + else { + // Add data to the predicted macroblock + if (n === 1) { + MPEG1.AddValueToDestination((this.blockData[0] + 128) >> 8, destArray, destIndex, scan); + this.blockData[0] = 0; + } + else { + MPEG1.IDCT(this.blockData); + MPEG1.AddBlockToDestination(this.blockData, destArray, destIndex, scan); + JSMpeg.Fill(this.blockData, 0); + } + } + + n = 0; +}; + +MPEG1.CopyBlockToDestination = function(block, dest, index, scan) { + for (var n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] = block[n+0]; + dest[index+1] = block[n+1]; + dest[index+2] = block[n+2]; + dest[index+3] = block[n+3]; + dest[index+4] = block[n+4]; + dest[index+5] = block[n+5]; + dest[index+6] = block[n+6]; + dest[index+7] = block[n+7]; + } +}; + +MPEG1.AddBlockToDestination = function(block, dest, index, scan) { + for (var n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] += block[n+0]; + dest[index+1] += block[n+1]; + dest[index+2] += block[n+2]; + dest[index+3] += block[n+3]; + dest[index+4] += block[n+4]; + dest[index+5] += block[n+5]; + dest[index+6] += block[n+6]; + dest[index+7] += block[n+7]; + } +}; + +MPEG1.CopyValueToDestination = function(value, dest, index, scan) { + for (var n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] = value; + dest[index+1] = value; + dest[index+2] = value; + dest[index+3] = value; + dest[index+4] = value; + dest[index+5] = value; + dest[index+6] = value; + dest[index+7] = value; + } +}; + +MPEG1.AddValueToDestination = function(value, dest, index, scan) { + for (var n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] += value; + dest[index+1] += value; + dest[index+2] += value; + dest[index+3] += value; + dest[index+4] += value; + dest[index+5] += value; + dest[index+6] += value; + dest[index+7] += value; + } +}; + +MPEG1.IDCT = function(block) { + // See http://vsr.informatik.tu-chemnitz.de/~jan/MPEG/HTML/IDCT.html + // for more info. + + var + b1, b3, b4, b6, b7, tmp1, tmp2, m0, + x0, x1, x2, x3, x4, y3, y4, y5, y6, y7; + + // Transform columns + for (var i = 0; i < 8; ++i) { + b1 = block[4*8+i]; + b3 = block[2*8+i] + block[6*8+i]; + b4 = block[5*8+i] - block[3*8+i]; + tmp1 = block[1*8+i] + block[7*8+i]; + tmp2 = block[3*8+i] + block[5*8+i]; + b6 = block[1*8+i] - block[7*8+i]; + b7 = tmp1 + tmp2; + m0 = block[0*8+i]; + x4 = ((b6*473 - b4*196 + 128) >> 8) - b7; + x0 = x4 - (((tmp1 - tmp2)*362 + 128) >> 8); + x1 = m0 - b1; + x2 = (((block[2*8+i] - block[6*8+i])*362 + 128) >> 8) - b3; + x3 = m0 + b1; + y3 = x1 + x2; + y4 = x3 + b3; + y5 = x1 - x2; + y6 = x3 - b3; + y7 = -x0 - ((b4*473 + b6*196 + 128) >> 8); + block[0*8+i] = b7 + y4; + block[1*8+i] = x4 + y3; + block[2*8+i] = y5 - x0; + block[3*8+i] = y6 - y7; + block[4*8+i] = y6 + y7; + block[5*8+i] = x0 + y5; + block[6*8+i] = y3 - x4; + block[7*8+i] = y4 - b7; + } + + // Transform rows + for (var i = 0; i < 64; i += 8) { + b1 = block[4+i]; + b3 = block[2+i] + block[6+i]; + b4 = block[5+i] - block[3+i]; + tmp1 = block[1+i] + block[7+i]; + tmp2 = block[3+i] + block[5+i]; + b6 = block[1+i] - block[7+i]; + b7 = tmp1 + tmp2; + m0 = block[0+i]; + x4 = ((b6*473 - b4*196 + 128) >> 8) - b7; + x0 = x4 - (((tmp1 - tmp2)*362 + 128) >> 8); + x1 = m0 - b1; + x2 = (((block[2+i] - block[6+i])*362 + 128) >> 8) - b3; + x3 = m0 + b1; + y3 = x1 + x2; + y4 = x3 + b3; + y5 = x1 - x2; + y6 = x3 - b3; + y7 = -x0 - ((b4*473 + b6*196 + 128) >> 8); + block[0+i] = (b7 + y4 + 128) >> 8; + block[1+i] = (x4 + y3 + 128) >> 8; + block[2+i] = (y5 - x0 + 128) >> 8; + block[3+i] = (y6 - y7 + 128) >> 8; + block[4+i] = (y6 + y7 + 128) >> 8; + block[5+i] = (x0 + y5 + 128) >> 8; + block[6+i] = (y3 - x4 + 128) >> 8; + block[7+i] = (y4 - b7 + 128) >> 8; + } +}; + + +// VLC Tables and Constants + +MPEG1.PICTURE_RATE = [ + 0.000, 23.976, 24.000, 25.000, 29.970, 30.000, 50.000, 59.940, + 60.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000 +]; + +MPEG1.ZIG_ZAG = new Uint8Array([ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +]); + +MPEG1.DEFAULT_INTRA_QUANT_MATRIX = new Uint8Array([ + 8, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +]); + +MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX = new Uint8Array([ + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16 +]); + +MPEG1.PREMULTIPLIER_MATRIX = new Uint8Array([ + 32, 44, 42, 38, 32, 25, 17, 9, + 44, 62, 58, 52, 44, 35, 24, 12, + 42, 58, 55, 49, 42, 33, 23, 12, + 38, 52, 49, 44, 38, 30, 20, 10, + 32, 44, 42, 38, 32, 25, 17, 9, + 25, 35, 33, 30, 25, 20, 14, 7, + 17, 24, 23, 20, 17, 14, 9, 5, + 9, 12, 12, 10, 9, 7, 5, 2 +]); + +// MPEG-1 VLC + +// macroblock_stuffing decodes as 34. +// macroblock_escape decodes as 35. + +MPEG1.MACROBLOCK_ADDRESS_INCREMENT = new Int16Array([ + 1*3, 2*3, 0, // 0 + 3*3, 4*3, 0, // 1 0 + 0, 0, 1, // 2 1. + 5*3, 6*3, 0, // 3 00 + 7*3, 8*3, 0, // 4 01 + 9*3, 10*3, 0, // 5 000 + 11*3, 12*3, 0, // 6 001 + 0, 0, 3, // 7 010. + 0, 0, 2, // 8 011. + 13*3, 14*3, 0, // 9 0000 + 15*3, 16*3, 0, // 10 0001 + 0, 0, 5, // 11 0010. + 0, 0, 4, // 12 0011. + 17*3, 18*3, 0, // 13 0000 0 + 19*3, 20*3, 0, // 14 0000 1 + 0, 0, 7, // 15 0001 0. + 0, 0, 6, // 16 0001 1. + 21*3, 22*3, 0, // 17 0000 00 + 23*3, 24*3, 0, // 18 0000 01 + 25*3, 26*3, 0, // 19 0000 10 + 27*3, 28*3, 0, // 20 0000 11 + -1, 29*3, 0, // 21 0000 000 + -1, 30*3, 0, // 22 0000 001 + 31*3, 32*3, 0, // 23 0000 010 + 33*3, 34*3, 0, // 24 0000 011 + 35*3, 36*3, 0, // 25 0000 100 + 37*3, 38*3, 0, // 26 0000 101 + 0, 0, 9, // 27 0000 110. + 0, 0, 8, // 28 0000 111. + 39*3, 40*3, 0, // 29 0000 0001 + 41*3, 42*3, 0, // 30 0000 0011 + 43*3, 44*3, 0, // 31 0000 0100 + 45*3, 46*3, 0, // 32 0000 0101 + 0, 0, 15, // 33 0000 0110. + 0, 0, 14, // 34 0000 0111. + 0, 0, 13, // 35 0000 1000. + 0, 0, 12, // 36 0000 1001. + 0, 0, 11, // 37 0000 1010. + 0, 0, 10, // 38 0000 1011. + 47*3, -1, 0, // 39 0000 0001 0 + -1, 48*3, 0, // 40 0000 0001 1 + 49*3, 50*3, 0, // 41 0000 0011 0 + 51*3, 52*3, 0, // 42 0000 0011 1 + 53*3, 54*3, 0, // 43 0000 0100 0 + 55*3, 56*3, 0, // 44 0000 0100 1 + 57*3, 58*3, 0, // 45 0000 0101 0 + 59*3, 60*3, 0, // 46 0000 0101 1 + 61*3, -1, 0, // 47 0000 0001 00 + -1, 62*3, 0, // 48 0000 0001 11 + 63*3, 64*3, 0, // 49 0000 0011 00 + 65*3, 66*3, 0, // 50 0000 0011 01 + 67*3, 68*3, 0, // 51 0000 0011 10 + 69*3, 70*3, 0, // 52 0000 0011 11 + 71*3, 72*3, 0, // 53 0000 0100 00 + 73*3, 74*3, 0, // 54 0000 0100 01 + 0, 0, 21, // 55 0000 0100 10. + 0, 0, 20, // 56 0000 0100 11. + 0, 0, 19, // 57 0000 0101 00. + 0, 0, 18, // 58 0000 0101 01. + 0, 0, 17, // 59 0000 0101 10. + 0, 0, 16, // 60 0000 0101 11. + 0, 0, 35, // 61 0000 0001 000. -- macroblock_escape + 0, 0, 34, // 62 0000 0001 111. -- macroblock_stuffing + 0, 0, 33, // 63 0000 0011 000. + 0, 0, 32, // 64 0000 0011 001. + 0, 0, 31, // 65 0000 0011 010. + 0, 0, 30, // 66 0000 0011 011. + 0, 0, 29, // 67 0000 0011 100. + 0, 0, 28, // 68 0000 0011 101. + 0, 0, 27, // 69 0000 0011 110. + 0, 0, 26, // 70 0000 0011 111. + 0, 0, 25, // 71 0000 0100 000. + 0, 0, 24, // 72 0000 0100 001. + 0, 0, 23, // 73 0000 0100 010. + 0, 0, 22 // 74 0000 0100 011. +]); + +// macroblock_type bitmap: +// 0x10 macroblock_quant +// 0x08 macroblock_motion_forward +// 0x04 macroblock_motion_backward +// 0x02 macrobkock_pattern +// 0x01 macroblock_intra +// + +MPEG1.MACROBLOCK_TYPE_INTRA = new Int8Array([ + 1*3, 2*3, 0, // 0 + -1, 3*3, 0, // 1 0 + 0, 0, 0x01, // 2 1. + 0, 0, 0x11 // 3 01. +]); + +MPEG1.MACROBLOCK_TYPE_PREDICTIVE = new Int8Array([ + 1*3, 2*3, 0, // 0 + 3*3, 4*3, 0, // 1 0 + 0, 0, 0x0a, // 2 1. + 5*3, 6*3, 0, // 3 00 + 0, 0, 0x02, // 4 01. + 7*3, 8*3, 0, // 5 000 + 0, 0, 0x08, // 6 001. + 9*3, 10*3, 0, // 7 0000 + 11*3, 12*3, 0, // 8 0001 + -1, 13*3, 0, // 9 00000 + 0, 0, 0x12, // 10 00001. + 0, 0, 0x1a, // 11 00010. + 0, 0, 0x01, // 12 00011. + 0, 0, 0x11 // 13 000001. +]); + +MPEG1.MACROBLOCK_TYPE_B = new Int8Array([ + 1*3, 2*3, 0, // 0 + 3*3, 5*3, 0, // 1 0 + 4*3, 6*3, 0, // 2 1 + 8*3, 7*3, 0, // 3 00 + 0, 0, 0x0c, // 4 10. + 9*3, 10*3, 0, // 5 01 + 0, 0, 0x0e, // 6 11. + 13*3, 14*3, 0, // 7 001 + 12*3, 11*3, 0, // 8 000 + 0, 0, 0x04, // 9 010. + 0, 0, 0x06, // 10 011. + 18*3, 16*3, 0, // 11 0001 + 15*3, 17*3, 0, // 12 0000 + 0, 0, 0x08, // 13 0010. + 0, 0, 0x0a, // 14 0011. + -1, 19*3, 0, // 15 00000 + 0, 0, 0x01, // 16 00011. + 20*3, 21*3, 0, // 17 00001 + 0, 0, 0x1e, // 18 00010. + 0, 0, 0x11, // 19 000001. + 0, 0, 0x16, // 20 000010. + 0, 0, 0x1a // 21 000011. +]); + +MPEG1.MACROBLOCK_TYPE = [ + null, + MPEG1.MACROBLOCK_TYPE_INTRA, + MPEG1.MACROBLOCK_TYPE_PREDICTIVE, + MPEG1.MACROBLOCK_TYPE_B +]; + +MPEG1.CODE_BLOCK_PATTERN = new Int16Array([ + 2*3, 1*3, 0, // 0 + 3*3, 6*3, 0, // 1 1 + 4*3, 5*3, 0, // 2 0 + 8*3, 11*3, 0, // 3 10 + 12*3, 13*3, 0, // 4 00 + 9*3, 7*3, 0, // 5 01 + 10*3, 14*3, 0, // 6 11 + 20*3, 19*3, 0, // 7 011 + 18*3, 16*3, 0, // 8 100 + 23*3, 17*3, 0, // 9 010 + 27*3, 25*3, 0, // 10 110 + 21*3, 28*3, 0, // 11 101 + 15*3, 22*3, 0, // 12 000 + 24*3, 26*3, 0, // 13 001 + 0, 0, 60, // 14 111. + 35*3, 40*3, 0, // 15 0000 + 44*3, 48*3, 0, // 16 1001 + 38*3, 36*3, 0, // 17 0101 + 42*3, 47*3, 0, // 18 1000 + 29*3, 31*3, 0, // 19 0111 + 39*3, 32*3, 0, // 20 0110 + 0, 0, 32, // 21 1010. + 45*3, 46*3, 0, // 22 0001 + 33*3, 41*3, 0, // 23 0100 + 43*3, 34*3, 0, // 24 0010 + 0, 0, 4, // 25 1101. + 30*3, 37*3, 0, // 26 0011 + 0, 0, 8, // 27 1100. + 0, 0, 16, // 28 1011. + 0, 0, 44, // 29 0111 0. + 50*3, 56*3, 0, // 30 0011 0 + 0, 0, 28, // 31 0111 1. + 0, 0, 52, // 32 0110 1. + 0, 0, 62, // 33 0100 0. + 61*3, 59*3, 0, // 34 0010 1 + 52*3, 60*3, 0, // 35 0000 0 + 0, 0, 1, // 36 0101 1. + 55*3, 54*3, 0, // 37 0011 1 + 0, 0, 61, // 38 0101 0. + 0, 0, 56, // 39 0110 0. + 57*3, 58*3, 0, // 40 0000 1 + 0, 0, 2, // 41 0100 1. + 0, 0, 40, // 42 1000 0. + 51*3, 62*3, 0, // 43 0010 0 + 0, 0, 48, // 44 1001 0. + 64*3, 63*3, 0, // 45 0001 0 + 49*3, 53*3, 0, // 46 0001 1 + 0, 0, 20, // 47 1000 1. + 0, 0, 12, // 48 1001 1. + 80*3, 83*3, 0, // 49 0001 10 + 0, 0, 63, // 50 0011 00. + 77*3, 75*3, 0, // 51 0010 00 + 65*3, 73*3, 0, // 52 0000 00 + 84*3, 66*3, 0, // 53 0001 11 + 0, 0, 24, // 54 0011 11. + 0, 0, 36, // 55 0011 10. + 0, 0, 3, // 56 0011 01. + 69*3, 87*3, 0, // 57 0000 10 + 81*3, 79*3, 0, // 58 0000 11 + 68*3, 71*3, 0, // 59 0010 11 + 70*3, 78*3, 0, // 60 0000 01 + 67*3, 76*3, 0, // 61 0010 10 + 72*3, 74*3, 0, // 62 0010 01 + 86*3, 85*3, 0, // 63 0001 01 + 88*3, 82*3, 0, // 64 0001 00 + -1, 94*3, 0, // 65 0000 000 + 95*3, 97*3, 0, // 66 0001 111 + 0, 0, 33, // 67 0010 100. + 0, 0, 9, // 68 0010 110. + 106*3, 110*3, 0, // 69 0000 100 + 102*3, 116*3, 0, // 70 0000 010 + 0, 0, 5, // 71 0010 111. + 0, 0, 10, // 72 0010 010. + 93*3, 89*3, 0, // 73 0000 001 + 0, 0, 6, // 74 0010 011. + 0, 0, 18, // 75 0010 001. + 0, 0, 17, // 76 0010 101. + 0, 0, 34, // 77 0010 000. + 113*3, 119*3, 0, // 78 0000 011 + 103*3, 104*3, 0, // 79 0000 111 + 90*3, 92*3, 0, // 80 0001 100 + 109*3, 107*3, 0, // 81 0000 110 + 117*3, 118*3, 0, // 82 0001 001 + 101*3, 99*3, 0, // 83 0001 101 + 98*3, 96*3, 0, // 84 0001 110 + 100*3, 91*3, 0, // 85 0001 011 + 114*3, 115*3, 0, // 86 0001 010 + 105*3, 108*3, 0, // 87 0000 101 + 112*3, 111*3, 0, // 88 0001 000 + 121*3, 125*3, 0, // 89 0000 0011 + 0, 0, 41, // 90 0001 1000. + 0, 0, 14, // 91 0001 0111. + 0, 0, 21, // 92 0001 1001. + 124*3, 122*3, 0, // 93 0000 0010 + 120*3, 123*3, 0, // 94 0000 0001 + 0, 0, 11, // 95 0001 1110. + 0, 0, 19, // 96 0001 1101. + 0, 0, 7, // 97 0001 1111. + 0, 0, 35, // 98 0001 1100. + 0, 0, 13, // 99 0001 1011. + 0, 0, 50, // 100 0001 0110. + 0, 0, 49, // 101 0001 1010. + 0, 0, 58, // 102 0000 0100. + 0, 0, 37, // 103 0000 1110. + 0, 0, 25, // 104 0000 1111. + 0, 0, 45, // 105 0000 1010. + 0, 0, 57, // 106 0000 1000. + 0, 0, 26, // 107 0000 1101. + 0, 0, 29, // 108 0000 1011. + 0, 0, 38, // 109 0000 1100. + 0, 0, 53, // 110 0000 1001. + 0, 0, 23, // 111 0001 0001. + 0, 0, 43, // 112 0001 0000. + 0, 0, 46, // 113 0000 0110. + 0, 0, 42, // 114 0001 0100. + 0, 0, 22, // 115 0001 0101. + 0, 0, 54, // 116 0000 0101. + 0, 0, 51, // 117 0001 0010. + 0, 0, 15, // 118 0001 0011. + 0, 0, 30, // 119 0000 0111. + 0, 0, 39, // 120 0000 0001 0. + 0, 0, 47, // 121 0000 0011 0. + 0, 0, 55, // 122 0000 0010 1. + 0, 0, 27, // 123 0000 0001 1. + 0, 0, 59, // 124 0000 0010 0. + 0, 0, 31 // 125 0000 0011 1. +]); + +MPEG1.MOTION = new Int16Array([ + 1*3, 2*3, 0, // 0 + 4*3, 3*3, 0, // 1 0 + 0, 0, 0, // 2 1. + 6*3, 5*3, 0, // 3 01 + 8*3, 7*3, 0, // 4 00 + 0, 0, -1, // 5 011. + 0, 0, 1, // 6 010. + 9*3, 10*3, 0, // 7 001 + 12*3, 11*3, 0, // 8 000 + 0, 0, 2, // 9 0010. + 0, 0, -2, // 10 0011. + 14*3, 15*3, 0, // 11 0001 + 16*3, 13*3, 0, // 12 0000 + 20*3, 18*3, 0, // 13 0000 1 + 0, 0, 3, // 14 0001 0. + 0, 0, -3, // 15 0001 1. + 17*3, 19*3, 0, // 16 0000 0 + -1, 23*3, 0, // 17 0000 00 + 27*3, 25*3, 0, // 18 0000 11 + 26*3, 21*3, 0, // 19 0000 01 + 24*3, 22*3, 0, // 20 0000 10 + 32*3, 28*3, 0, // 21 0000 011 + 29*3, 31*3, 0, // 22 0000 101 + -1, 33*3, 0, // 23 0000 001 + 36*3, 35*3, 0, // 24 0000 100 + 0, 0, -4, // 25 0000 111. + 30*3, 34*3, 0, // 26 0000 010 + 0, 0, 4, // 27 0000 110. + 0, 0, -7, // 28 0000 0111. + 0, 0, 5, // 29 0000 1010. + 37*3, 41*3, 0, // 30 0000 0100 + 0, 0, -5, // 31 0000 1011. + 0, 0, 7, // 32 0000 0110. + 38*3, 40*3, 0, // 33 0000 0011 + 42*3, 39*3, 0, // 34 0000 0101 + 0, 0, -6, // 35 0000 1001. + 0, 0, 6, // 36 0000 1000. + 51*3, 54*3, 0, // 37 0000 0100 0 + 50*3, 49*3, 0, // 38 0000 0011 0 + 45*3, 46*3, 0, // 39 0000 0101 1 + 52*3, 47*3, 0, // 40 0000 0011 1 + 43*3, 53*3, 0, // 41 0000 0100 1 + 44*3, 48*3, 0, // 42 0000 0101 0 + 0, 0, 10, // 43 0000 0100 10. + 0, 0, 9, // 44 0000 0101 00. + 0, 0, 8, // 45 0000 0101 10. + 0, 0, -8, // 46 0000 0101 11. + 57*3, 66*3, 0, // 47 0000 0011 11 + 0, 0, -9, // 48 0000 0101 01. + 60*3, 64*3, 0, // 49 0000 0011 01 + 56*3, 61*3, 0, // 50 0000 0011 00 + 55*3, 62*3, 0, // 51 0000 0100 00 + 58*3, 63*3, 0, // 52 0000 0011 10 + 0, 0, -10, // 53 0000 0100 11. + 59*3, 65*3, 0, // 54 0000 0100 01 + 0, 0, 12, // 55 0000 0100 000. + 0, 0, 16, // 56 0000 0011 000. + 0, 0, 13, // 57 0000 0011 110. + 0, 0, 14, // 58 0000 0011 100. + 0, 0, 11, // 59 0000 0100 010. + 0, 0, 15, // 60 0000 0011 010. + 0, 0, -16, // 61 0000 0011 001. + 0, 0, -12, // 62 0000 0100 001. + 0, 0, -14, // 63 0000 0011 101. + 0, 0, -15, // 64 0000 0011 011. + 0, 0, -11, // 65 0000 0100 011. + 0, 0, -13 // 66 0000 0011 111. +]); + +MPEG1.DCT_DC_SIZE_LUMINANCE = new Int8Array([ + 2*3, 1*3, 0, // 0 + 6*3, 5*3, 0, // 1 1 + 3*3, 4*3, 0, // 2 0 + 0, 0, 1, // 3 00. + 0, 0, 2, // 4 01. + 9*3, 8*3, 0, // 5 11 + 7*3, 10*3, 0, // 6 10 + 0, 0, 0, // 7 100. + 12*3, 11*3, 0, // 8 111 + 0, 0, 4, // 9 110. + 0, 0, 3, // 10 101. + 13*3, 14*3, 0, // 11 1111 + 0, 0, 5, // 12 1110. + 0, 0, 6, // 13 1111 0. + 16*3, 15*3, 0, // 14 1111 1 + 17*3, -1, 0, // 15 1111 11 + 0, 0, 7, // 16 1111 10. + 0, 0, 8 // 17 1111 110. +]); + +MPEG1.DCT_DC_SIZE_CHROMINANCE = new Int8Array([ + 2*3, 1*3, 0, // 0 + 4*3, 3*3, 0, // 1 1 + 6*3, 5*3, 0, // 2 0 + 8*3, 7*3, 0, // 3 11 + 0, 0, 2, // 4 10. + 0, 0, 1, // 5 01. + 0, 0, 0, // 6 00. + 10*3, 9*3, 0, // 7 111 + 0, 0, 3, // 8 110. + 12*3, 11*3, 0, // 9 1111 + 0, 0, 4, // 10 1110. + 14*3, 13*3, 0, // 11 1111 1 + 0, 0, 5, // 12 1111 0. + 16*3, 15*3, 0, // 13 1111 11 + 0, 0, 6, // 14 1111 10. + 17*3, -1, 0, // 15 1111 111 + 0, 0, 7, // 16 1111 110. + 0, 0, 8 // 17 1111 1110. +]); + +// dct_coeff bitmap: +// 0xff00 run +// 0x00ff level + +// Decoded values are unsigned. Sign bit follows in the stream. + +// Interpretation of the value 0x0001 +// for dc_coeff_first: run=0, level=1 +// for dc_coeff_next: If the next bit is 1: run=0, level=1 +// If the next bit is 0: end_of_block + +// escape decodes as 0xffff. + +MPEG1.DCT_COEFF = new Int32Array([ + 1*3, 2*3, 0, // 0 + 4*3, 3*3, 0, // 1 0 + 0, 0, 0x0001, // 2 1. + 7*3, 8*3, 0, // 3 01 + 6*3, 5*3, 0, // 4 00 + 13*3, 9*3, 0, // 5 001 + 11*3, 10*3, 0, // 6 000 + 14*3, 12*3, 0, // 7 010 + 0, 0, 0x0101, // 8 011. + 20*3, 22*3, 0, // 9 0011 + 18*3, 21*3, 0, // 10 0001 + 16*3, 19*3, 0, // 11 0000 + 0, 0, 0x0201, // 12 0101. + 17*3, 15*3, 0, // 13 0010 + 0, 0, 0x0002, // 14 0100. + 0, 0, 0x0003, // 15 0010 1. + 27*3, 25*3, 0, // 16 0000 0 + 29*3, 31*3, 0, // 17 0010 0 + 24*3, 26*3, 0, // 18 0001 0 + 32*3, 30*3, 0, // 19 0000 1 + 0, 0, 0x0401, // 20 0011 0. + 23*3, 28*3, 0, // 21 0001 1 + 0, 0, 0x0301, // 22 0011 1. + 0, 0, 0x0102, // 23 0001 10. + 0, 0, 0x0701, // 24 0001 00. + 0, 0, 0xffff, // 25 0000 01. -- escape + 0, 0, 0x0601, // 26 0001 01. + 37*3, 36*3, 0, // 27 0000 00 + 0, 0, 0x0501, // 28 0001 11. + 35*3, 34*3, 0, // 29 0010 00 + 39*3, 38*3, 0, // 30 0000 11 + 33*3, 42*3, 0, // 31 0010 01 + 40*3, 41*3, 0, // 32 0000 10 + 52*3, 50*3, 0, // 33 0010 010 + 54*3, 53*3, 0, // 34 0010 001 + 48*3, 49*3, 0, // 35 0010 000 + 43*3, 45*3, 0, // 36 0000 001 + 46*3, 44*3, 0, // 37 0000 000 + 0, 0, 0x0801, // 38 0000 111. + 0, 0, 0x0004, // 39 0000 110. + 0, 0, 0x0202, // 40 0000 100. + 0, 0, 0x0901, // 41 0000 101. + 51*3, 47*3, 0, // 42 0010 011 + 55*3, 57*3, 0, // 43 0000 0010 + 60*3, 56*3, 0, // 44 0000 0001 + 59*3, 58*3, 0, // 45 0000 0011 + 61*3, 62*3, 0, // 46 0000 0000 + 0, 0, 0x0a01, // 47 0010 0111. + 0, 0, 0x0d01, // 48 0010 0000. + 0, 0, 0x0006, // 49 0010 0001. + 0, 0, 0x0103, // 50 0010 0101. + 0, 0, 0x0005, // 51 0010 0110. + 0, 0, 0x0302, // 52 0010 0100. + 0, 0, 0x0b01, // 53 0010 0011. + 0, 0, 0x0c01, // 54 0010 0010. + 76*3, 75*3, 0, // 55 0000 0010 0 + 67*3, 70*3, 0, // 56 0000 0001 1 + 73*3, 71*3, 0, // 57 0000 0010 1 + 78*3, 74*3, 0, // 58 0000 0011 1 + 72*3, 77*3, 0, // 59 0000 0011 0 + 69*3, 64*3, 0, // 60 0000 0001 0 + 68*3, 63*3, 0, // 61 0000 0000 0 + 66*3, 65*3, 0, // 62 0000 0000 1 + 81*3, 87*3, 0, // 63 0000 0000 01 + 91*3, 80*3, 0, // 64 0000 0001 01 + 82*3, 79*3, 0, // 65 0000 0000 11 + 83*3, 86*3, 0, // 66 0000 0000 10 + 93*3, 92*3, 0, // 67 0000 0001 10 + 84*3, 85*3, 0, // 68 0000 0000 00 + 90*3, 94*3, 0, // 69 0000 0001 00 + 88*3, 89*3, 0, // 70 0000 0001 11 + 0, 0, 0x0203, // 71 0000 0010 11. + 0, 0, 0x0104, // 72 0000 0011 00. + 0, 0, 0x0007, // 73 0000 0010 10. + 0, 0, 0x0402, // 74 0000 0011 11. + 0, 0, 0x0502, // 75 0000 0010 01. + 0, 0, 0x1001, // 76 0000 0010 00. + 0, 0, 0x0f01, // 77 0000 0011 01. + 0, 0, 0x0e01, // 78 0000 0011 10. + 105*3, 107*3, 0, // 79 0000 0000 111 + 111*3, 114*3, 0, // 80 0000 0001 011 + 104*3, 97*3, 0, // 81 0000 0000 010 + 125*3, 119*3, 0, // 82 0000 0000 110 + 96*3, 98*3, 0, // 83 0000 0000 100 + -1, 123*3, 0, // 84 0000 0000 000 + 95*3, 101*3, 0, // 85 0000 0000 001 + 106*3, 121*3, 0, // 86 0000 0000 101 + 99*3, 102*3, 0, // 87 0000 0000 011 + 113*3, 103*3, 0, // 88 0000 0001 110 + 112*3, 116*3, 0, // 89 0000 0001 111 + 110*3, 100*3, 0, // 90 0000 0001 000 + 124*3, 115*3, 0, // 91 0000 0001 010 + 117*3, 122*3, 0, // 92 0000 0001 101 + 109*3, 118*3, 0, // 93 0000 0001 100 + 120*3, 108*3, 0, // 94 0000 0001 001 + 127*3, 136*3, 0, // 95 0000 0000 0010 + 139*3, 140*3, 0, // 96 0000 0000 1000 + 130*3, 126*3, 0, // 97 0000 0000 0101 + 145*3, 146*3, 0, // 98 0000 0000 1001 + 128*3, 129*3, 0, // 99 0000 0000 0110 + 0, 0, 0x0802, // 100 0000 0001 0001. + 132*3, 134*3, 0, // 101 0000 0000 0011 + 155*3, 154*3, 0, // 102 0000 0000 0111 + 0, 0, 0x0008, // 103 0000 0001 1101. + 137*3, 133*3, 0, // 104 0000 0000 0100 + 143*3, 144*3, 0, // 105 0000 0000 1110 + 151*3, 138*3, 0, // 106 0000 0000 1010 + 142*3, 141*3, 0, // 107 0000 0000 1111 + 0, 0, 0x000a, // 108 0000 0001 0011. + 0, 0, 0x0009, // 109 0000 0001 1000. + 0, 0, 0x000b, // 110 0000 0001 0000. + 0, 0, 0x1501, // 111 0000 0001 0110. + 0, 0, 0x0602, // 112 0000 0001 1110. + 0, 0, 0x0303, // 113 0000 0001 1100. + 0, 0, 0x1401, // 114 0000 0001 0111. + 0, 0, 0x0702, // 115 0000 0001 0101. + 0, 0, 0x1101, // 116 0000 0001 1111. + 0, 0, 0x1201, // 117 0000 0001 1010. + 0, 0, 0x1301, // 118 0000 0001 1001. + 148*3, 152*3, 0, // 119 0000 0000 1101 + 0, 0, 0x0403, // 120 0000 0001 0010. + 153*3, 150*3, 0, // 121 0000 0000 1011 + 0, 0, 0x0105, // 122 0000 0001 1011. + 131*3, 135*3, 0, // 123 0000 0000 0001 + 0, 0, 0x0204, // 124 0000 0001 0100. + 149*3, 147*3, 0, // 125 0000 0000 1100 + 172*3, 173*3, 0, // 126 0000 0000 0101 1 + 162*3, 158*3, 0, // 127 0000 0000 0010 0 + 170*3, 161*3, 0, // 128 0000 0000 0110 0 + 168*3, 166*3, 0, // 129 0000 0000 0110 1 + 157*3, 179*3, 0, // 130 0000 0000 0101 0 + 169*3, 167*3, 0, // 131 0000 0000 0001 0 + 174*3, 171*3, 0, // 132 0000 0000 0011 0 + 178*3, 177*3, 0, // 133 0000 0000 0100 1 + 156*3, 159*3, 0, // 134 0000 0000 0011 1 + 164*3, 165*3, 0, // 135 0000 0000 0001 1 + 183*3, 182*3, 0, // 136 0000 0000 0010 1 + 175*3, 176*3, 0, // 137 0000 0000 0100 0 + 0, 0, 0x0107, // 138 0000 0000 1010 1. + 0, 0, 0x0a02, // 139 0000 0000 1000 0. + 0, 0, 0x0902, // 140 0000 0000 1000 1. + 0, 0, 0x1601, // 141 0000 0000 1111 1. + 0, 0, 0x1701, // 142 0000 0000 1111 0. + 0, 0, 0x1901, // 143 0000 0000 1110 0. + 0, 0, 0x1801, // 144 0000 0000 1110 1. + 0, 0, 0x0503, // 145 0000 0000 1001 0. + 0, 0, 0x0304, // 146 0000 0000 1001 1. + 0, 0, 0x000d, // 147 0000 0000 1100 1. + 0, 0, 0x000c, // 148 0000 0000 1101 0. + 0, 0, 0x000e, // 149 0000 0000 1100 0. + 0, 0, 0x000f, // 150 0000 0000 1011 1. + 0, 0, 0x0205, // 151 0000 0000 1010 0. + 0, 0, 0x1a01, // 152 0000 0000 1101 1. + 0, 0, 0x0106, // 153 0000 0000 1011 0. + 180*3, 181*3, 0, // 154 0000 0000 0111 1 + 160*3, 163*3, 0, // 155 0000 0000 0111 0 + 196*3, 199*3, 0, // 156 0000 0000 0011 10 + 0, 0, 0x001b, // 157 0000 0000 0101 00. + 203*3, 185*3, 0, // 158 0000 0000 0010 01 + 202*3, 201*3, 0, // 159 0000 0000 0011 11 + 0, 0, 0x0013, // 160 0000 0000 0111 00. + 0, 0, 0x0016, // 161 0000 0000 0110 01. + 197*3, 207*3, 0, // 162 0000 0000 0010 00 + 0, 0, 0x0012, // 163 0000 0000 0111 01. + 191*3, 192*3, 0, // 164 0000 0000 0001 10 + 188*3, 190*3, 0, // 165 0000 0000 0001 11 + 0, 0, 0x0014, // 166 0000 0000 0110 11. + 184*3, 194*3, 0, // 167 0000 0000 0001 01 + 0, 0, 0x0015, // 168 0000 0000 0110 10. + 186*3, 193*3, 0, // 169 0000 0000 0001 00 + 0, 0, 0x0017, // 170 0000 0000 0110 00. + 204*3, 198*3, 0, // 171 0000 0000 0011 01 + 0, 0, 0x0019, // 172 0000 0000 0101 10. + 0, 0, 0x0018, // 173 0000 0000 0101 11. + 200*3, 205*3, 0, // 174 0000 0000 0011 00 + 0, 0, 0x001f, // 175 0000 0000 0100 00. + 0, 0, 0x001e, // 176 0000 0000 0100 01. + 0, 0, 0x001c, // 177 0000 0000 0100 11. + 0, 0, 0x001d, // 178 0000 0000 0100 10. + 0, 0, 0x001a, // 179 0000 0000 0101 01. + 0, 0, 0x0011, // 180 0000 0000 0111 10. + 0, 0, 0x0010, // 181 0000 0000 0111 11. + 189*3, 206*3, 0, // 182 0000 0000 0010 11 + 187*3, 195*3, 0, // 183 0000 0000 0010 10 + 218*3, 211*3, 0, // 184 0000 0000 0001 010 + 0, 0, 0x0025, // 185 0000 0000 0010 011. + 215*3, 216*3, 0, // 186 0000 0000 0001 000 + 0, 0, 0x0024, // 187 0000 0000 0010 100. + 210*3, 212*3, 0, // 188 0000 0000 0001 110 + 0, 0, 0x0022, // 189 0000 0000 0010 110. + 213*3, 209*3, 0, // 190 0000 0000 0001 111 + 221*3, 222*3, 0, // 191 0000 0000 0001 100 + 219*3, 208*3, 0, // 192 0000 0000 0001 101 + 217*3, 214*3, 0, // 193 0000 0000 0001 001 + 223*3, 220*3, 0, // 194 0000 0000 0001 011 + 0, 0, 0x0023, // 195 0000 0000 0010 101. + 0, 0, 0x010b, // 196 0000 0000 0011 100. + 0, 0, 0x0028, // 197 0000 0000 0010 000. + 0, 0, 0x010c, // 198 0000 0000 0011 011. + 0, 0, 0x010a, // 199 0000 0000 0011 101. + 0, 0, 0x0020, // 200 0000 0000 0011 000. + 0, 0, 0x0108, // 201 0000 0000 0011 111. + 0, 0, 0x0109, // 202 0000 0000 0011 110. + 0, 0, 0x0026, // 203 0000 0000 0010 010. + 0, 0, 0x010d, // 204 0000 0000 0011 010. + 0, 0, 0x010e, // 205 0000 0000 0011 001. + 0, 0, 0x0021, // 206 0000 0000 0010 111. + 0, 0, 0x0027, // 207 0000 0000 0010 001. + 0, 0, 0x1f01, // 208 0000 0000 0001 1011. + 0, 0, 0x1b01, // 209 0000 0000 0001 1111. + 0, 0, 0x1e01, // 210 0000 0000 0001 1100. + 0, 0, 0x1002, // 211 0000 0000 0001 0101. + 0, 0, 0x1d01, // 212 0000 0000 0001 1101. + 0, 0, 0x1c01, // 213 0000 0000 0001 1110. + 0, 0, 0x010f, // 214 0000 0000 0001 0011. + 0, 0, 0x0112, // 215 0000 0000 0001 0000. + 0, 0, 0x0111, // 216 0000 0000 0001 0001. + 0, 0, 0x0110, // 217 0000 0000 0001 0010. + 0, 0, 0x0603, // 218 0000 0000 0001 0100. + 0, 0, 0x0b02, // 219 0000 0000 0001 1010. + 0, 0, 0x0e02, // 220 0000 0000 0001 0111. + 0, 0, 0x0d02, // 221 0000 0000 0001 1000. + 0, 0, 0x0c02, // 222 0000 0000 0001 1001. + 0, 0, 0x0f02 // 223 0000 0000 0001 0110. +]); + +MPEG1.PICTURE_TYPE = { + INTRA: 1, + PREDICTIVE: 2, + B: 3 +}; + +MPEG1.START = { + SEQUENCE: 0xB3, + SLICE_FIRST: 0x01, + SLICE_LAST: 0xAF, + PICTURE: 0x00, + EXTENSION: 0xB5, + USER_DATA: 0xB2 +}; + +return MPEG1; + +})(); + diff --git a/public/static/jsmpeg-master/src/player.js b/public/static/jsmpeg-master/src/player.js new file mode 100644 index 0000000..deae9cb --- /dev/null +++ b/public/static/jsmpeg-master/src/player.js @@ -0,0 +1,324 @@ +JSMpeg.Player = (function(){ "use strict"; + +var Player = function(url, options) { + this.options = options || {}; + + if (options.source) { + this.source = new options.source(url, options); + options.streaming = !!this.source.streaming; + } + else if (url.match(/^wss?:\/\//)) { + this.source = new JSMpeg.Source.WebSocket(url, options); + options.streaming = true; + } + else if (options.progressive !== false) { + this.source = new JSMpeg.Source.AjaxProgressive(url, options); + options.streaming = false; + } + else { + this.source = new JSMpeg.Source.Ajax(url, options); + options.streaming = false; + } + + this.maxAudioLag = options.maxAudioLag || 0.25; + this.loop = options.loop !== false; + this.autoplay = !!options.autoplay || options.streaming; + + this.demuxer = new JSMpeg.Demuxer.TS(options); + this.source.connect(this.demuxer); + + if (!options.disableWebAssembly && JSMpeg.WASMModule.IsSupported()) { + this.wasmModule = JSMpeg.WASMModule.GetModule(); + options.wasmModule = this.wasmModule; + } + + if (options.video !== false) { + this.video = options.wasmModule + ? new JSMpeg.Decoder.MPEG1VideoWASM(options) + : new JSMpeg.Decoder.MPEG1Video(options); + + this.renderer = !options.disableGl && JSMpeg.Renderer.WebGL.IsSupported() + ? new JSMpeg.Renderer.WebGL(options) + : new JSMpeg.Renderer.Canvas2D(options); + + this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.VIDEO_1, this.video); + this.video.connect(this.renderer); + } + + if (options.audio !== false && JSMpeg.AudioOutput.WebAudio.IsSupported()) { + this.audio = options.wasmModule + ? new JSMpeg.Decoder.MP2AudioWASM(options) + : new JSMpeg.Decoder.MP2Audio(options); + this.audioOut = new JSMpeg.AudioOutput.WebAudio(options); + this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.AUDIO_1, this.audio); + this.audio.connect(this.audioOut); + } + + Object.defineProperty(this, 'currentTime', { + get: this.getCurrentTime, + set: this.setCurrentTime + }); + Object.defineProperty(this, 'volume', { + get: this.getVolume, + set: this.setVolume + }); + + this.paused = true; + this.unpauseOnShow = false; + if (options.pauseWhenHidden !== false) { + document.addEventListener('visibilitychange', this.showHide.bind(this)); + } + + // If we have WebAssembly support, wait until the module is compiled before + // loading the source. Otherwise the decoders won't know what to do with + // the source data. + if (this.wasmModule) { + if (this.wasmModule.ready) { + this.startLoading(); + } + else if (JSMpeg.WASM_BINARY_INLINED) { + var wasm = JSMpeg.Base64ToArrayBuffer(JSMpeg.WASM_BINARY_INLINED); + this.wasmModule.loadFromBuffer(wasm, this.startLoading.bind(this)); + } + else { + this.wasmModule.loadFromFile('jsmpeg.wasm', this.startLoading.bind(this)); + } + } + else { + this.startLoading(); + + } +}; + +Player.prototype.startLoading = function() { + this.source.start(); + if (this.autoplay) { + this.play(); + } +}; + +Player.prototype.showHide = function(ev) { + if (document.visibilityState === 'hidden') { + this.unpauseOnShow = this.wantsToPlay; + this.pause(); + } + else if (this.unpauseOnShow) { + this.play(); + } +}; + +Player.prototype.play = function(ev) { + if (this.animationId) { + return; + } + + this.animationId = requestAnimationFrame(this.update.bind(this)); + this.wantsToPlay = true; + this.paused = false; +}; + +Player.prototype.pause = function(ev) { + if (this.paused) { + return; + } + + cancelAnimationFrame(this.animationId); + this.animationId = null; + this.wantsToPlay = false; + this.isPlaying = false; + this.paused = true; + + if (this.audio && this.audio.canPlay) { + // Seek to the currentTime again - audio may already be enqueued a bit + // further, so we have to rewind it. + this.audioOut.stop(); + this.seek(this.currentTime); + } + + if (this.options.onPause) { + this.options.onPause(this); + } +}; + +Player.prototype.getVolume = function() { + return this.audioOut ? this.audioOut.volume : 0; +}; + +Player.prototype.setVolume = function(volume) { + if (this.audioOut) { + this.audioOut.volume = volume; + } +}; + +Player.prototype.stop = function(ev) { + this.pause(); + this.seek(0); + if (this.video && this.options.decodeFirstFrame !== false) { + this.video.decode(); + } +}; + +Player.prototype.destroy = function() { + this.pause(); + this.source.destroy(); + this.video && this.video.destroy(); + this.renderer && this.renderer.destroy(); + this.audio && this.audio.destroy(); + this.audioOut && this.audioOut.destroy(); +}; + +Player.prototype.seek = function(time) { + var startOffset = this.audio && this.audio.canPlay + ? this.audio.startTime + : this.video.startTime; + + if (this.video) { + this.video.seek(time + startOffset); + } + if (this.audio) { + this.audio.seek(time + startOffset); + } + + this.startTime = JSMpeg.Now() - time; +}; + +Player.prototype.getCurrentTime = function() { + return this.audio && this.audio.canPlay + ? this.audio.currentTime - this.audio.startTime + : this.video.currentTime - this.video.startTime; +}; + +Player.prototype.setCurrentTime = function(time) { + this.seek(time); +}; + +Player.prototype.update = function() { + this.animationId = requestAnimationFrame(this.update.bind(this)); + + if (!this.source.established) { + if (this.renderer) { + this.renderer.renderProgress(this.source.progress); + } + return; + } + + if (!this.isPlaying) { + this.isPlaying = true; + this.startTime = JSMpeg.Now() - this.currentTime; + + if (this.options.onPlay) { + this.options.onPlay(this); + } + } + + if (this.options.streaming) { + this.updateForStreaming(); + } + else { + this.updateForStaticFile(); + } +}; + +Player.prototype.updateForStreaming = function() { + // When streaming, immediately decode everything we have buffered up until + // now to minimize playback latency. + + if (this.video) { + this.video.decode(); + } + + if (this.audio) { + var decoded = false; + do { + // If there's a lot of audio enqueued already, disable output and + // catch up with the encoding. + if (this.audioOut.enqueuedTime > this.maxAudioLag) { + this.audioOut.resetEnqueuedTime(); + this.audioOut.enabled = false; + } + decoded = this.audio.decode(); + } while (decoded); + this.audioOut.enabled = true; + } +}; + +Player.prototype.nextFrame = function() { + if (this.source.established && this.video) { + return this.video.decode(); + } + return false; +}; + +Player.prototype.updateForStaticFile = function() { + var notEnoughData = false, + headroom = 0; + + // If we have an audio track, we always try to sync the video to the audio. + // Gaps and discontinuities are far more percetable in audio than in video. + + if (this.audio && this.audio.canPlay) { + // Do we have to decode and enqueue some more audio data? + while ( + !notEnoughData && + this.audio.decodedTime - this.audio.currentTime < 0.25 + ) { + notEnoughData = !this.audio.decode(); + } + + // Sync video to audio + if (this.video && this.video.currentTime < this.audio.currentTime) { + notEnoughData = !this.video.decode(); + } + + headroom = this.demuxer.currentTime - this.audio.currentTime; + } + + + else if (this.video) { + // Video only - sync it to player's wallclock + var targetTime = (JSMpeg.Now() - this.startTime) + this.video.startTime, + lateTime = targetTime - this.video.currentTime, + frameTime = 1/this.video.frameRate; + + if (this.video && lateTime > 0) { + // If the video is too far behind (>2 frames), simply reset the + // target time to the next frame instead of trying to catch up. + if (lateTime > frameTime * 2) { + this.startTime += lateTime; + } + + notEnoughData = !this.video.decode(); + } + + headroom = this.demuxer.currentTime - targetTime; + } + + // Notify the source of the playhead headroom, so it can decide whether to + // continue loading further data. + this.source.resume(headroom); + + // If we failed to decode and the source is complete, it means we reached + // the end of our data. We may want to loop. + if (notEnoughData && this.source.completed) { + if (this.loop) { + this.seek(0); + } + else { + this.pause(); + if (this.options.onEnded) { + this.options.onEnded(this); + } + } + } + + // If there's not enough data and the source is not completed, we have + // just stalled. + else if (notEnoughData && this.options.onStalled) { + this.options.onStalled(this); + } +}; + +return Player; + +})(); + diff --git a/public/static/jsmpeg-master/src/ts.js b/public/static/jsmpeg-master/src/ts.js new file mode 100644 index 0000000..cc16d7c --- /dev/null +++ b/public/static/jsmpeg-master/src/ts.js @@ -0,0 +1,228 @@ +JSMpeg.Demuxer.TS = (function(){ "use strict"; + +var TS = function(options) { + this.bits = null; + this.leftoverBytes = null; + + this.guessVideoFrameEnd = true; + this.pidsToStreamIds = {}; + + this.pesPacketInfo = {}; + this.startTime = 0; + this.currentTime = 0; +}; + +TS.prototype.connect = function(streamId, destination) { + this.pesPacketInfo[streamId] = { + destination: destination, + currentLength: 0, + totalLength: 0, + pts: 0, + buffers: [] + }; +}; + +TS.prototype.write = function(buffer) { + if (this.leftoverBytes) { + var totalLength = buffer.byteLength + this.leftoverBytes.byteLength; + this.bits = new JSMpeg.BitBuffer(totalLength); + this.bits.write([this.leftoverBytes, buffer]); + } + else { + this.bits = new JSMpeg.BitBuffer(buffer); + } + + while (this.bits.has(188 << 3) && this.parsePacket()) {} + + var leftoverCount = this.bits.byteLength - (this.bits.index >> 3); + this.leftoverBytes = leftoverCount > 0 + ? this.bits.bytes.subarray(this.bits.index >> 3) + : null; +}; + +TS.prototype.parsePacket = function() { + // Check if we're in sync with packet boundaries; attempt to resync if not. + if (this.bits.read(8) !== 0x47) { + if (!this.resync()) { + // Couldn't resync; maybe next time... + return false; + } + } + + var end = (this.bits.index >> 3) + 187; + var transportError = this.bits.read(1), + payloadStart = this.bits.read(1), + transportPriority = this.bits.read(1), + pid = this.bits.read(13), + transportScrambling = this.bits.read(2), + adaptationField = this.bits.read(2), + continuityCounter = this.bits.read(4); + + + // If this is the start of a new payload; signal the end of the previous + // frame, if we didn't do so already. + var streamId = this.pidsToStreamIds[pid]; + if (payloadStart && streamId) { + var pi = this.pesPacketInfo[streamId]; + if (pi && pi.currentLength) { + this.packetComplete(pi); + } + } + + // Extract current payload + if (adaptationField & 0x1) { + if ((adaptationField & 0x2)) { + var adaptationFieldLength = this.bits.read(8); + this.bits.skip(adaptationFieldLength << 3); + } + + if (payloadStart && this.bits.nextBytesAreStartCode()) { + this.bits.skip(24); + streamId = this.bits.read(8); + this.pidsToStreamIds[pid] = streamId; + + var packetLength = this.bits.read(16) + this.bits.skip(8); + var ptsDtsFlag = this.bits.read(2); + this.bits.skip(6); + var headerLength = this.bits.read(8); + var payloadBeginIndex = this.bits.index + (headerLength << 3); + + var pi = this.pesPacketInfo[streamId]; + if (pi) { + var pts = 0; + if (ptsDtsFlag & 0x2) { + // The Presentation Timestamp is encoded as 33(!) bit + // integer, but has a "marker bit" inserted at weird places + // in between, making the whole thing 5 bytes in size. + // You can't make this shit up... + this.bits.skip(4); + var p32_30 = this.bits.read(3); + this.bits.skip(1); + var p29_15 = this.bits.read(15); + this.bits.skip(1); + var p14_0 = this.bits.read(15); + this.bits.skip(1); + + // Can't use bit shifts here; we need 33 bits of precision, + // so we're using JavaScript's double number type. Also + // divide by the 90khz clock to get the pts in seconds. + pts = (p32_30 * 1073741824 + p29_15 * 32768 + p14_0)/90000; + + this.currentTime = pts; + if (this.startTime === -1) { + this.startTime = pts; + } + } + + var payloadLength = packetLength + ? packetLength - headerLength - 3 + : 0; + this.packetStart(pi, pts, payloadLength); + } + + // Skip the rest of the header without parsing it + this.bits.index = payloadBeginIndex; + } + + if (streamId) { + // Attempt to detect if the PES packet is complete. For Audio (and + // other) packets, we received a total packet length with the PES + // header, so we can check the current length. + + // For Video packets, we have to guess the end by detecting if this + // TS packet was padded - there's no good reason to pad a TS packet + // in between, but it might just fit exactly. If this fails, we can + // only wait for the next PES header for that stream. + + var pi = this.pesPacketInfo[streamId]; + if (pi) { + var start = this.bits.index >> 3; + var complete = this.packetAddData(pi, start, end); + + var hasPadding = !payloadStart && (adaptationField & 0x2); + if (complete || (this.guessVideoFrameEnd && hasPadding)) { + this.packetComplete(pi); + } + } + } + } + + this.bits.index = end << 3; + return true; +}; + +TS.prototype.resync = function() { + // Check if we have enough data to attempt a resync. We need 5 full packets. + if (!this.bits.has((188 * 6) << 3)) { + return false; + } + + var byteIndex = this.bits.index >> 3; + + // Look for the first sync token in the first 187 bytes + for (var i = 0; i < 187; i++) { + if (this.bits.bytes[byteIndex + i] === 0x47) { + + // Look for 4 more sync tokens, each 188 bytes appart + var foundSync = true; + for (var j = 1; j < 5; j++) { + if (this.bits.bytes[byteIndex + i + 188 * j] !== 0x47) { + foundSync = false; + break; + } + } + + if (foundSync) { + this.bits.index = (byteIndex + i + 1) << 3; + return true; + } + } + } + + // In theory, we shouldn't arrive here. If we do, we had enough data but + // still didn't find sync - this can only happen if we were fed garbage + // data. Check your source! + console.warn('JSMpeg: Possible garbage data. Skipping.'); + this.bits.skip(187 << 3); + return false; +}; + +TS.prototype.packetStart = function(pi, pts, payloadLength) { + pi.totalLength = payloadLength; + pi.currentLength = 0; + pi.pts = pts; +}; + +TS.prototype.packetAddData = function(pi, start, end) { + pi.buffers.push(this.bits.bytes.subarray(start, end)); + pi.currentLength += end - start; + + var complete = (pi.totalLength !== 0 && pi.currentLength >= pi.totalLength); + return complete; +}; + +TS.prototype.packetComplete = function(pi) { + pi.destination.write(pi.pts, pi.buffers); + pi.totalLength = 0; + pi.currentLength = 0; + pi.buffers = []; +}; + +TS.STREAM = { + PACK_HEADER: 0xBA, + SYSTEM_HEADER: 0xBB, + PROGRAM_MAP: 0xBC, + PRIVATE_1: 0xBD, + PADDING: 0xBE, + PRIVATE_2: 0xBF, + AUDIO_1: 0xC0, + VIDEO_1: 0xE0, + DIRECTORY: 0xFF +}; + +return TS; + +})(); + + diff --git a/public/static/jsmpeg-master/src/video-element.js b/public/static/jsmpeg-master/src/video-element.js new file mode 100644 index 0000000..2f93637 --- /dev/null +++ b/public/static/jsmpeg-master/src/video-element.js @@ -0,0 +1,162 @@ +JSMpeg.VideoElement = (function(){ "use strict"; + +var VideoElement = function(element) { + var url = element.dataset.url; + + if (!url) { + throw ("VideoElement has no `data-url` attribute"); + } + + // Setup the div container, canvas and play button + var addStyles = function(element, styles) { + for (var name in styles) { + element.style[name] = styles[name]; + } + }; + + this.container = element; + addStyles(this.container, { + display: 'inline-block', + position: 'relative', + minWidth: '80px', minHeight: '80px' + }); + + this.canvas = document.createElement('canvas'); + this.canvas.width = 960; + this.canvas.height = 540; + addStyles(this.canvas, { + display: 'block', + width: '100%' + }); + this.container.appendChild(this.canvas); + + this.playButton = document.createElement('div'); + this.playButton.innerHTML = VideoElement.PLAY_BUTTON; + addStyles(this.playButton, { + zIndex: 2, position: 'absolute', + top: '0', bottom: '0', left: '0', right: '0', + maxWidth: '75px', maxHeight: '75px', + margin: 'auto', + opacity: '0.7', + cursor: 'pointer' + }); + this.container.appendChild(this.playButton); + + // Parse the data-options - we try to decode the values as json. This way + // we can get proper boolean and number values. If JSON.parse() fails, + // treat it as a string. + var options = {canvas: this.canvas}; + for (var option in element.dataset) { + try { + options[option] = JSON.parse(element.dataset[option]); + } + catch(err) { + options[option] = element.dataset[option]; + } + } + + // Create the player instance + this.player = new JSMpeg.Player(url, options); + element.playerInstance = this.player; + + // Setup the poster element, if any + if (options.poster && !options.autoplay && !this.player.options.streaming) { + options.decodeFirstFrame = false; + this.poster = new Image(); + this.poster.src = options.poster; + this.poster.addEventListener('load', this.posterLoaded) + addStyles(this.poster, { + display: 'block', zIndex: 1, position: 'absolute', + top: 0, left: 0, bottom: 0, right: 0 + }); + this.container.appendChild(this.poster); + } + + // Add the click handler if this video is pausable + if (!this.player.options.streaming) { + this.container.addEventListener('click', this.onClick.bind(this)); + } + + // Hide the play button if this video immediately begins playing + if (options.autoplay || this.player.options.streaming) { + this.playButton.style.display = 'none'; + } + + // Set up the unlock audio buton for iOS devices. iOS only allows us to + // play audio after a user action has initiated playing. For autoplay or + // streaming players we set up a muted speaker icon as the button. For all + // others, we can simply use the play button. + if (this.player.audioOut && !this.player.audioOut.unlocked) { + var unlockAudioElement = this.container; + + if (options.autoplay || this.player.options.streaming) { + this.unmuteButton = document.createElement('div'); + this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTON; + addStyles(this.unmuteButton, { + zIndex: 2, position: 'absolute', + bottom: '10px', right: '20px', + width: '75px', height: '75px', + margin: 'auto', + opacity: '0.7', + cursor: 'pointer' + }); + this.container.appendChild(this.unmuteButton); + unlockAudioElement = this.unmuteButton; + } + + this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement); + unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false); + unlockAudioElement.addEventListener('click', this.unlockAudioBound, true); + } +}; + +VideoElement.prototype.onUnlockAudio = function(element, ev) { + if (this.unmuteButton) { + ev.preventDefault(); + ev.stopPropagation(); + } + this.player.audioOut.unlock(function(){ + if (this.unmuteButton) { + this.unmuteButton.style.display = 'none'; + } + element.removeEventListener('touchstart', this.unlockAudioBound); + element.removeEventListener('click', this.unlockAudioBound); + }.bind(this)); +}; + +VideoElement.prototype.onClick = function(ev) { + if (this.player.isPlaying) { + this.player.pause(); + this.playButton.style.display = 'block'; + } + else { + this.player.play(); + this.playButton.style.display = 'none'; + if (this.poster) { + this.poster.style.display = 'none'; + } + } +}; + +VideoElement.PLAY_BUTTON = + '' + + '' + + '' + + ''; + +VideoElement.UNMUTE_BUTTON = + '' + + '' + + '' + + '' + + '' + + '' + + ''; + +return VideoElement; + +})(); + diff --git a/public/static/jsmpeg-master/src/wasm-module.js b/public/static/jsmpeg-master/src/wasm-module.js new file mode 100644 index 0000000..0425538 --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm-module.js @@ -0,0 +1,187 @@ +JSMpeg.WASMModule = (function(){ "use strict"; + +var WASM = function() { + this.stackSize = 5 * 1024 * 1024; // emscripten default + this.pageSize = 64 * 1024; // wasm page size + this.onInitCallbacks = []; + this.ready = false; + this.loadingFromFileStarted = false; + this.loadingFromBufferStarted = false; +}; + +WASM.prototype.write = function(buffer) { + this.loadFromBuffer(buffer); +}; + +WASM.prototype.loadFromFile = function(url, callback) { + if (callback) { + this.onInitCallbacks.push(callback); + } + + // Make sure this WASM Module is only instantiated once. If loadFromFile() + // was already called, bail out here. On instantiation all pending + // onInitCallbacks will be called. + if (this.loadingFromFileStarted) { + return; + } + this.loadingFromFileStarted = true; + + this.onInitCallback = callback; + var ajax = new JSMpeg.Source.Ajax(url, {}); + ajax.connect(this); + ajax.start(); +}; + +WASM.prototype.loadFromBuffer = function(buffer, callback) { + if (callback) { + this.onInitCallbacks.push(callback); + } + + // Make sure this WASM Module is only instantiated once. If loadFromBuffer() + // was already called, bail out here. On instantiation all pending + // onInitCallbacks will be called. + if (this.loadingFromBufferStarted) { + return; + } + this.loadingFromBufferStarted = true; + + this.moduleInfo = this.readDylinkSection(buffer); + if (!this.moduleInfo) { + for (var i = 0; i < this.onInitCallbacks.length; i++) { + this.onInitCallbacks[i](null); + } + return; + } + + this.memory = new WebAssembly.Memory({initial: 256}); + var env = { + memory: this.memory, + memoryBase: 0, + __memory_base: 0, + table: new WebAssembly.Table({initial: this.moduleInfo.tableSize, element: 'anyfunc'}), + tableBase: 0, + __table_base: 0, + abort: this.c_abort.bind(this), + ___assert_fail: this.c_assertFail.bind(this), + _sbrk: this.c_sbrk.bind(this) + }; + + this.brk = this.align(this.moduleInfo.memorySize + this.stackSize); + WebAssembly.instantiate(buffer, {env: env}).then(function(results){ + this.instance = results.instance; + if (this.instance.exports.__post_instantiate) { + this.instance.exports.__post_instantiate(); + } + this.createHeapViews(); + this.ready = true; + for (var i = 0; i < this.onInitCallbacks.length; i++) { + this.onInitCallbacks[i](this); + } + }.bind(this)) +}; + +WASM.prototype.createHeapViews = function() { + this.instance.heapU8 = new Uint8Array(this.memory.buffer); + this.instance.heapU32 = new Uint32Array(this.memory.buffer); + this.instance.heapF32 = new Float32Array(this.memory.buffer); +}; + +WASM.prototype.align = function(addr) { + var a = Math.pow(2, this.moduleInfo.memoryAlignment); + return Math.ceil(addr / a) * a; +}; + +WASM.prototype.c_sbrk = function(size) { + var previousBrk = this.brk; + this.brk += size; + + if (this.brk > this.memory.buffer.byteLength) { + var bytesNeeded = this.brk - this.memory.buffer.byteLength; + var pagesNeeded = Math.ceil(bytesNeeded / this.pageSize); + this.memory.grow(pagesNeeded); + this.createHeapViews(); + } + return previousBrk; +}; + +WASM.prototype.c_abort = function(size) { + console.warn('JSMPeg: WASM abort', arguments); +}; + +WASM.prototype.c_assertFail = function(size) { + console.warn('JSMPeg: WASM ___assert_fail', arguments); +}; + + +WASM.prototype.readDylinkSection = function(buffer) { + // Read the WASM header and dylink section of the .wasm binary data + // to get the needed table size and static data size. + + // https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md + // https://github.com/kripken/emscripten/blob/20602efb955a7c6c20865a495932427e205651d2/src/support.js + + var bytes = new Uint8Array(buffer); + var next = 0; + + var readVarUint = function () { + var ret = 0; + var mul = 1; + while (1) { + var byte = bytes[next++]; + ret += ((byte & 0x7f) * mul); + mul *= 0x80; + if (!(byte & 0x80)) { + return ret + } + } + } + + var matchNextBytes = function(expected) { + for (var i = 0; i < expected.length; i++) { + var b = typeof(expected[i]) === 'string' + ? expected[i].charCodeAt(0) + : expected[i]; + if (bytes[next++] !== b) { + return false; + } + } + return true; + }; + + + + // Make sure we have a wasm header + if (!matchNextBytes([0, 'a', 's', 'm'])) { + console.warn('JSMpeg: WASM header not found'); + return null; + } + + // Make sure we have a dylink section + var next = 9; + var sectionSize = readVarUint(); + if (!matchNextBytes([6, 'd', 'y', 'l', 'i', 'n', 'k'])) { + console.warn('JSMpeg: No dylink section found in WASM'); + return null; + } + + return { + memorySize: readVarUint(), + memoryAlignment: readVarUint(), + tableSize: readVarUint(), + tableAlignment: readVarUint() + }; +}; + +WASM.IsSupported = function() { + return (!!window.WebAssembly); +}; + +WASM.GetModule = function() { + WASM.CACHED_MODULE = WASM.CACHED_MODULE || new WASM(); + return WASM.CACHED_MODULE; +}; + +return WASM; + +})(); + diff --git a/public/static/jsmpeg-master/src/wasm/buffer.c b/public/static/jsmpeg-master/src/wasm/buffer.c new file mode 100644 index 0000000..954820c --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm/buffer.c @@ -0,0 +1,190 @@ +#include +#include +#include + +#include "buffer.h" + +typedef struct bit_buffer_t { + uint8_t *bytes; + unsigned int index; + unsigned int byte_capacity; + unsigned int byte_length; + bit_buffer_mode_t mode; +} bit_buffer_t; + +void bit_buffer_resize(bit_buffer_t *self, unsigned int byte_capacity); +void bit_buffer_evict(bit_buffer_t *self, unsigned int bytes_needed); + + + +bit_buffer_t *bit_buffer_create(unsigned int initial_byte_capacity, bit_buffer_mode_t mode) { + bit_buffer_t *self = malloc(sizeof(bit_buffer_t)); + memset(self, 0, sizeof(bit_buffer_t)); + self->mode = mode; + self->bytes = malloc(initial_byte_capacity); + self->byte_capacity = initial_byte_capacity; + self->byte_length = 0; + self->index = 0; + return self; +} + + +void bit_buffer_destroy(bit_buffer_t *self) { + free(self->bytes); + free(self); +} + + +int bit_buffer_get_index(bit_buffer_t *self) { + return self->index; +} + + +void bit_buffer_set_index(bit_buffer_t *self, unsigned int index) { + self->index = index; // TODO check validity! +} + + +uint8_t *bit_buffer_get_write_ptr(bit_buffer_t *self, unsigned int bytes_to_write) { + int bytes_available = self->byte_capacity - self->byte_length; + + if (bytes_to_write > bytes_available) { + if (self->mode == BIT_BUFFER_MODE_EXPAND) { + int new_byte_capacity = self->byte_capacity * 2; + if (new_byte_capacity + bytes_available < bytes_to_write) { + new_byte_capacity = bytes_to_write - bytes_available; + } + bit_buffer_resize(self, new_byte_capacity); + } + else { + bit_buffer_evict(self, bytes_to_write); + } + } + + return self->bytes + self->byte_length; +}; + + +void bit_buffer_did_write(bit_buffer_t *self, unsigned int bytes_written) { + self->byte_length += bytes_written; +} + + +int bit_buffer_find_next_start_code(bit_buffer_t *self) { + for (int i = ((self->index + 7) >> 3); i < self->byte_length; i++) { + if( + self->bytes[i] == 0x00 && + self->bytes[i+1] == 0x00 && + self->bytes[i+2] == 0x01 + ) { + self->index = (i+4) << 3; + return self->bytes[i+3]; + } + } + self->index = (self->byte_length << 3); + return -1; +} + + +int bit_buffer_find_start_code(bit_buffer_t *self, int code) { + int current = 0; + while (true) { + current = bit_buffer_find_next_start_code(self); + if (current == code || current == -1) { + return current; + } + } + return -1; +} + + +int bit_buffer_next_bytes_are_start_code(bit_buffer_t *self) { + int i = ((self->index + 7) >> 3); + return ( + i >= self->byte_length || ( + self->bytes[i] == 0x00 && + self->bytes[i+1] == 0x00 && + self->bytes[i+2] == 0x01 + ) + ); +} + + +int bit_buffer_peek(bit_buffer_t *self, unsigned int count) { + int offset = self->index; + int value = 0; + while (count) { + int current_byte = self->bytes[offset >> 3]; + int remaining = 8 - (offset & 7); // remaining bits in byte + int read = remaining < count ? remaining : count; // bits in self run + int shift = remaining - read; + int mask = (0xff >> (8-read)); + + value = (value << read) | ((current_byte & (mask << shift)) >> shift); + + offset += read; + count -= read; + } + + return value; +} + + +int bit_buffer_read(bit_buffer_t *self, unsigned int count) { + int value = bit_buffer_peek(self, count); + self->index += count; + return value; +} + + +int bit_buffer_skip(bit_buffer_t *self, unsigned int count) { + return (self->index += count); +} + + +void bit_buffer_rewind(bit_buffer_t *self, unsigned int count) { + self->index = self->index - count; + if (self->index < 0) { + self->index = 0; + } +} + + +int bit_buffer_has(bit_buffer_t *self, unsigned int count) { + return ((self->byte_length << 3) - self->index) >= count; +} + + +void bit_buffer_resize(bit_buffer_t *self, unsigned int byte_capacity) { + self->bytes = realloc(self->bytes, byte_capacity); + self->byte_capacity = byte_capacity; + if (self->index > self->byte_length << 3) { + self->index = self->byte_length << 3; + } +} + + +void bit_buffer_evict(bit_buffer_t *self, unsigned int bytes_needed) { + int byte_pos = self->index >> 3; + int bytes_available = self->byte_capacity - self->byte_length; + + // If the current index is the write position, we can simply reset both + // to 0. Also reset (and throw away yet unread data) if we won't be able + // to fit the new data in even after a normal eviction. + if ( + byte_pos == self->byte_length || + bytes_needed > bytes_available + byte_pos // emergency evac + ) { + self->byte_length = 0; + self->index = 0; + return; + } + else if (byte_pos == 0) { + // Nothing read yet - we can't evict anything + return; + } + + memmove(self->bytes, self->bytes + byte_pos, self->byte_length - byte_pos); + self->byte_length -= byte_pos; + self->index -= byte_pos << 3; +} diff --git a/public/static/jsmpeg-master/src/wasm/buffer.h b/public/static/jsmpeg-master/src/wasm/buffer.h new file mode 100644 index 0000000..d857247 --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm/buffer.h @@ -0,0 +1,31 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include + +typedef struct bit_buffer_t bit_buffer_t; + +typedef enum { + BIT_BUFFER_MODE_EVICT = 1, + BIT_BUFFER_MODE_EXPAND = 2 +} bit_buffer_mode_t; + + +bit_buffer_t *bit_buffer_create(unsigned int initial_byte_capacity, bit_buffer_mode_t mode); +void bit_buffer_destroy(bit_buffer_t *self); + +int bit_buffer_get_index(bit_buffer_t *self); +void bit_buffer_set_index(bit_buffer_t *self, unsigned int index); + +uint8_t *bit_buffer_get_write_ptr(bit_buffer_t *self, unsigned int bytes_to_write); +void bit_buffer_did_write(bit_buffer_t *self, unsigned int bytes_written); +int bit_buffer_find_next_start_code(bit_buffer_t *self); +int bit_buffer_find_start_code(bit_buffer_t *self, int code); +int bit_buffer_next_bytes_are_start_code(bit_buffer_t *self); +int bit_buffer_peek(bit_buffer_t *self, unsigned int count); +int bit_buffer_read(bit_buffer_t *self, unsigned int count); +int bit_buffer_skip(bit_buffer_t *self, unsigned int count); +int bit_buffer_has(bit_buffer_t *self, unsigned int count); +void bit_buffer_rewind(bit_buffer_t *self, unsigned int count); + +#endif diff --git a/public/static/jsmpeg-master/src/wasm/mp2.c b/public/static/jsmpeg-master/src/wasm/mp2.c new file mode 100644 index 0000000..f984a2f --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm/mp2.c @@ -0,0 +1,704 @@ +#include +#include +#include "mp2.h" + +const static int FRAME_SYNC = 0x7ff; + +const static int VERSION_MPEG_2_5 = 0x0; +const static int VERSION_MPEG_2 = 0x2; +const static int VERSION_MPEG_1 = 0x3; + +const static int LAYER_III = 0x1; +const static int LAYER_II = 0x2; +const static int LAYER_I = 0x3; + +const static int MODE_STEREO = 0x0; +const static int MODE_JOINT_STEREO = 0x1; +const static int MODE_DUAL_CHANNEL = 0x2; +const static int MODE_MONO = 0x3; + +const static unsigned short SAMPLE_RATE[] = { + 44100, 48000, 32000, 0, // MPEG-1 + 22050, 24000, 16000, 0 // MPEG-2 +}; + +const static short BIT_RATE[] = { + 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1 + 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2 +}; + +const static int SCALEFACTOR_BASE[] = { + 0x02000000, 0x01965FEA, 0x01428A30 +}; + +const static float SYNTHESIS_WINDOW[] = { + 0.0, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -1.0, -1.0, -1.0, -1.0, -1.5, + -1.5, -2.0, -2.0, -2.5, -2.5, -3.0, + -3.5, -3.5, -4.0, -4.5, -5.0, -5.5, + -6.5, -7.0, -8.0, -8.5, -9.5, -10.5, + -12.0, -13.0, -14.5, -15.5, -17.5, -19.0, + -20.5, -22.5, -24.5, -26.5, -29.0, -31.5, + -34.0, -36.5, -39.5, -42.5, -45.5, -48.5, + -52.0, -55.5, -58.5, -62.5, -66.0, -69.5, + -73.5, -77.0, -80.5, -84.5, -88.0, -91.5, + -95.0, -98.0, -101.0, -104.0, 106.5, 109.0, + 111.0, 112.5, 113.5, 114.0, 114.0, 113.5, + 112.0, 110.5, 107.5, 104.0, 100.0, 94.5, + 88.5, 81.5, 73.0, 63.5, 53.0, 41.5, + 28.5, 14.5, -1.0, -18.0, -36.0, -55.5, + -76.5, -98.5, -122.0, -147.0, -173.5, -200.5, + -229.5, -259.5, -290.5, -322.5, -355.5, -389.5, + -424.0, -459.5, -495.5, -532.0, -568.5, -605.0, + -641.5, -678.0, -714.0, -749.0, -783.5, -817.0, + -849.0, -879.5, -908.5, -935.0, -959.5, -981.0, + -1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5, + -1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, + 911.0, 869.5, 822.0, 767.5, 707.0, 640.0, + 565.5, 485.0, 397.0, 302.5, 201.0, 92.5, + -22.5, -144.0, -272.5, -407.0, -547.5, -694.0, + -846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5, + -1852.5, -2031.5, -2212.5, -2394.0, -2576.5, -2758.5, + -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5, + -3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5, + -4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5, + -4983.0, -4967.5, -4931.5, -4875.0, -4796.0, -4694.5, + -4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0, + 3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5, + 1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0, + -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5, + -6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5, + -11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5, + -16895.5, -17820.0, -18744.5, -19668.0, -20588.0, -21503.0, + -22410.5, -23308.5, -24195.0, -25068.5, -25926.5, -26767.0, + -27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0, + -32009.5, -32645.0, -33247.0, -33814.5, -34346.0, -34839.5, + -35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0, + -37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, + 37428.0, 37315.0, 37156.5, 36954.0, 36707.5, 36417.5, + 36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5, + 33247.0, 32645.0, 32009.5, 31342.0, 30644.5, 29919.0, + 29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5, + 24195.0, 23308.5, 22410.5, 21503.0, 20588.0, 19668.0, + 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5, + 13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, + 8077.5, 7274.0, 6490.0, 5727.5, 4987.5, 4270.0, + 3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0, + -35.0, -541.0, -1018.5, -1467.5, -1888.0, -2280.5, + -2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0, + 4246.0, 4420.0, 4569.5, 4694.5, 4796.0, 4875.0, + 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0, + 4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5, + 4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5, + 3294.5, 3118.5, 2939.5, 2758.5, 2576.5, 2394.0, + 2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5, + 1165.0, 1003.0, 846.0, 694.0, 547.5, 407.0, + 272.5, 144.0, 22.5, -92.5, -201.0, -302.5, + -397.0, -485.0, -565.5, -640.0, -707.0, -767.5, + -822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, + 1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5, + 1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0, + 908.5, 879.5, 849.0, 817.0, 783.5, 749.0, + 714.0, 678.0, 641.5, 605.0, 568.5, 532.0, + 495.5, 459.5, 424.0, 389.5, 355.5, 322.5, + 290.5, 259.5, 229.5, 200.5, 173.5, 147.0, + 122.0, 98.5, 76.5, 55.5, 36.0, 18.0, + 1.0, -14.5, -28.5, -41.5, -53.0, -63.5, + -73.0, -81.5, -88.5, -94.5, -100.0, -104.0, + -107.5, -110.5, -112.0, -113.5, -114.0, -114.0, + -113.5, -112.5, -111.0, -109.0, 106.5, 104.0, + 101.0, 98.0, 95.0, 91.5, 88.0, 84.5, + 80.5, 77.0, 73.5, 69.5, 66.0, 62.5, + 58.5, 55.5, 52.0, 48.5, 45.5, 42.5, + 39.5, 36.5, 34.0, 31.5, 29.0, 26.5, + 24.5, 22.5, 20.5, 19.0, 17.5, 15.5, + 14.5, 13.0, 12.0, 10.5, 9.5, 8.5, + 8.0, 7.0, 6.5, 5.5, 5.0, 4.5, + 4.0, 3.5, 3.5, 3.0, 2.5, 2.5, + 2.0, 2.0, 1.5, 1.5, 1.0, 1.0, + 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5 +}; + +// Quantizer lookup, step 1: bitrate classes +const static uint8_t QUANT_LUT_STEP_1[2][16] = { + // 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate + { 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2}, // mono + // 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan + { 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2} // stereo +}; + +// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit +const static uint8_t QUANT_TAB_A = (27 | 64); // Table 3-B.2a: high-rate, sblimit = 27 +const static uint8_t QUANT_TAB_B = (30 | 64); // Table 3-B.2b: high-rate, sblimit = 30 +const static uint8_t QUANT_TAB_C = 8; // Table 3-B.2c: low-rate, sblimit = 8 +const static uint8_t QUANT_TAB_D = 12; // Table 3-B.2d: low-rate, sblimit = 12 + +const static uint8_t QUANT_LUT_STEP_2[3][3] = { + // 44.1 kHz, 48 kHz, 32 kHz + {QUANT_TAB_C, QUANT_TAB_C, QUANT_TAB_D}, // 32 - 48 kbit/sec/ch + {QUANT_TAB_A, QUANT_TAB_A, QUANT_TAB_A}, // 56 - 80 kbit/sec/ch + {QUANT_TAB_B, QUANT_TAB_A, QUANT_TAB_B} // 96+ kbit/sec/ch +}; + +// Quantizer lookup, step 3: B2 table, subband -> nbal, row index +// (upper 4 bits: nbal, lower 4 bits: row index) +const static uint8_t QUANT_LUT_STEP_3[3][32] = { + // Low-rate table (3-B.2c and 3-B.2d) + { + 0x44,0x44, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34 + }, + // High-rate table (3-B.2a and 3-B.2b) + { + 0x43,0x43,0x43, + 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42, + 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20 + }, + // MPEG-2 LSR table (B.2 in ISO 13818-3) + { + 0x45,0x45,0x45,0x45, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34, + 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, + 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24 + } +}; + +// Quantizer lookup, step 4: table row, allocation[] value -> quant table index +const static uint8_t QUANT_LUT_STEP4[6][16] = { + {0, 1, 2, 17}, + {0, 1, 2, 3, 4, 5, 6, 17}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}, + {0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} +}; + +typedef struct quantizer_spec_t { + unsigned short levels; + unsigned char group; + unsigned char bits; +} quantizer_spec_t; + +const static quantizer_spec_t QUANT_TAB[] = { + {.levels = 3, .group = 1, .bits = 5}, // 1 + {.levels = 5, .group = 1, .bits = 7}, // 2 + {.levels = 7, .group = 0, .bits = 3}, // 3 + {.levels = 9, .group = 1, .bits = 10}, // 4 + {.levels = 15, .group = 0, .bits = 4}, // 5 + {.levels = 31, .group = 0, .bits = 5}, // 6 + {.levels = 63, .group = 0, .bits = 6}, // 7 + {.levels = 127, .group = 0, .bits = 7}, // 8 + {.levels = 255, .group = 0, .bits = 8}, // 9 + {.levels = 511, .group = 0, .bits = 9}, // 10 + {.levels = 1023, .group = 0, .bits = 10}, // 11 + {.levels = 2047, .group = 0, .bits = 11}, // 12 + {.levels = 4095, .group = 0, .bits = 12}, // 13 + {.levels = 8191, .group = 0, .bits = 13}, // 14 + {.levels = 16383, .group = 0, .bits = 14}, // 15 + {.levels = 32767, .group = 0, .bits = 15}, // 16 + {.levels = 65535, .group = 0, .bits = 16} // 17 +}; + +#define SAMPLES_PER_FRAME 1152 + +typedef struct mp2_decoder_t { + int sample_rate; + int v_pos; + + bit_buffer_t *bits; + + const quantizer_spec_t *allocation[2][32]; + uint8_t scale_factor_info[2][32]; + int scale_factor[2][32][3]; + int sample[2][32][3]; + + float channel_left[SAMPLES_PER_FRAME]; + float channel_right[SAMPLES_PER_FRAME]; + float D[1024]; + float V[2][1024]; + int U[32]; +} mp2_decoder_t; + + +void matrix_transform(int s[32][3], int ss, float *d, int dp); +void read_samples(mp2_decoder_t *self, int ch, int sb, int part); +const quantizer_spec_t *read_allocation(mp2_decoder_t *self, int sb, int tab3); +int decode_frame(mp2_decoder_t *self); + + +// ----------------------------------------------------------------------------- +// Public interface + +mp2_decoder_t *mp2_decoder_create(unsigned int buffer_size, bit_buffer_mode_t buffer_mode) { + mp2_decoder_t *self = malloc(sizeof(mp2_decoder_t)); + memset(self, 0, sizeof(mp2_decoder_t)); + self->bits = bit_buffer_create(buffer_size, buffer_mode); + + self->sample_rate = 44100; + memcpy(self->D, SYNTHESIS_WINDOW, 512 * sizeof(float)); + memcpy(self->D + 512, SYNTHESIS_WINDOW, 512 * sizeof(float)); + + return self; +} + +void mp2_decoder_destroy(mp2_decoder_t *self) { + bit_buffer_destroy(self->bits); + free(self); +} + +void *mp2_decoder_get_write_ptr(mp2_decoder_t *self, unsigned int byte_size) { + return bit_buffer_get_write_ptr(self->bits, byte_size); +} + +int mp2_decoder_get_index(mp2_decoder_t *self) { + return bit_buffer_get_index(self->bits); +} + +void mp2_decoder_set_index(mp2_decoder_t *self, unsigned int index) { + bit_buffer_set_index(self->bits, index); +} + +void mp2_decoder_did_write(mp2_decoder_t *self, unsigned int byte_size) { + bit_buffer_did_write(self->bits, byte_size); +} + +int mp2_decoder_get_sample_rate(mp2_decoder_t *self) { + return self->sample_rate; +} + +void *mp2_decoder_get_left_channel_ptr(mp2_decoder_t *self) { + return self->channel_left; +} + +void *mp2_decoder_get_right_channel_ptr(mp2_decoder_t *self) { + return self->channel_right; +} + +int mp2_decoder_decode(mp2_decoder_t *self) { + int byte_pos = bit_buffer_get_index(self->bits) >> 3; + + if (!bit_buffer_has(self->bits, 16)) { + return 0; + } + + int decoded_bytes = decode_frame(self); + bit_buffer_set_index(self->bits, (byte_pos + decoded_bytes) << 3); + return decoded_bytes; +} + + + + +int decode_frame(mp2_decoder_t *self) { + // Check for valid header: syncword OK, MPEG-Audio Layer 2 + int sync = bit_buffer_read(self->bits, 11); + int version = bit_buffer_read(self->bits, 2); + int layer = bit_buffer_read(self->bits, 2); + int hasCRC = !bit_buffer_read(self->bits, 1); + + if ( + sync != FRAME_SYNC || + version != VERSION_MPEG_1 || + layer != LAYER_II + ) { + return 0; // Invalid header or unsupported version + } + + int bitrate_index = bit_buffer_read(self->bits, 4) - 1; + if (bitrate_index > 13) { + return 0; // Invalid bit rate or 'free format' + } + + int sample_rate_index = bit_buffer_read(self->bits, 2); + int sample_rate = SAMPLE_RATE[sample_rate_index]; + if (sample_rate_index == 3) { + return 0; // Invalid sample rate + } + if (version == VERSION_MPEG_2) { + sample_rate_index += 4; + bitrate_index += 14; + } + int padding = bit_buffer_read(self->bits, 1), + privat = bit_buffer_read(self->bits, 1), + mode = bit_buffer_read(self->bits, 2); + + // Parse the mode_extension, set up the stereo bound + int bound = 0; + if (mode == MODE_JOINT_STEREO) { + bound = (bit_buffer_read(self->bits, 2) + 1) << 2; + } + else { + bit_buffer_skip(self->bits, 2); + bound = (mode == MODE_MONO) ? 0 : 32; + } + + // Discard the last 4 bits of the header and the CRC value, if present + bit_buffer_skip(self->bits, 4); + if (hasCRC) { + bit_buffer_skip(self->bits, 16); + } + + // Compute the frame size + int bitrate = BIT_RATE[bitrate_index]; + sample_rate = SAMPLE_RATE[sample_rate_index]; + int frame_size = ((144000 * bitrate / sample_rate) + padding)|0; + + + // Prepare the quantizer table lookups + int tab3 = 0; + int sblimit = 0; + if (version == VERSION_MPEG_2) { + // MPEG-2 (LSR) + tab3 = 2; + sblimit = 30; + } + else { + // MPEG-1 + int tab1 = (mode == MODE_MONO) ? 0 : 1; + int tab2 = QUANT_LUT_STEP_1[tab1][bitrate_index]; + tab3 = QUANT_LUT_STEP_2[tab2][sample_rate_index]; + sblimit = tab3 & 63; + tab3 >>= 6; + } + + if (bound > sblimit) { + bound = sblimit; + } + + // Read the allocation information + for (int sb = 0; sb < bound; sb++) { + self->allocation[0][sb] = read_allocation(self, sb, tab3); + self->allocation[1][sb] = read_allocation(self, sb, tab3); + } + + for (int sb = bound; sb < sblimit; sb++) { + self->allocation[0][sb] = + self->allocation[1][sb] = + read_allocation(self, sb, tab3); + } + + // Read scale factor selector information + int channels = (mode == MODE_MONO) ? 1 : 2; + for (int sb = 0; sb < sblimit; sb++) { + for (int ch = 0; ch < channels; ch++) { + if (self->allocation[ch][sb]) { + self->scale_factor_info[ch][sb] = bit_buffer_read(self->bits, 2); + } + } + if (mode == MODE_MONO) { + self->scale_factor_info[1][sb] = self->scale_factor_info[0][sb]; + } + } + + // Read scale factors + for (int sb = 0; sb < sblimit; sb++) { + for (int ch = 0; ch < channels; ch++) { + if (self->allocation[ch][sb]) { + int *sf = self->scale_factor[ch][sb]; + switch (self->scale_factor_info[ch][sb]) { + case 0: + sf[0] = bit_buffer_read(self->bits, 6); + sf[1] = bit_buffer_read(self->bits, 6); + sf[2] = bit_buffer_read(self->bits, 6); + break; + case 1: + sf[0] = + sf[1] = bit_buffer_read(self->bits, 6); + sf[2] = bit_buffer_read(self->bits, 6); + break; + case 2: + sf[0] = + sf[1] = + sf[2] = bit_buffer_read(self->bits, 6); + break; + case 3: + sf[0] = bit_buffer_read(self->bits, 6); + sf[1] = + sf[2] = bit_buffer_read(self->bits, 6); + break; + } + } + } + if (mode == MODE_MONO) { + self->scale_factor[1][sb][0] = self->scale_factor[0][sb][0]; + self->scale_factor[1][sb][1] = self->scale_factor[0][sb][1]; + self->scale_factor[1][sb][2] = self->scale_factor[0][sb][2]; + } + } + + // Coefficient input and reconstruction + int out_pos = 0; + for (int part = 0; part < 3; part++) { + for (int granule = 0; granule < 4; granule++) { + + // Read the samples + for (int sb = 0; sb < bound; sb++) { + read_samples(self, 0, sb, part); + read_samples(self, 1, sb, part); + } + for (int sb = bound; sb < sblimit; sb++) { + read_samples(self, 0, sb, part); + self->sample[1][sb][0] = self->sample[0][sb][0]; + self->sample[1][sb][1] = self->sample[0][sb][1]; + self->sample[1][sb][2] = self->sample[0][sb][2]; + } + for (int sb = sblimit; sb < 32; sb++) { + self->sample[0][sb][0] = 0; + self->sample[0][sb][1] = 0; + self->sample[0][sb][2] = 0; + self->sample[1][sb][0] = 0; + self->sample[1][sb][1] = 0; + self->sample[1][sb][2] = 0; + } + + // Synthesis loop + for (int p = 0; p < 3; p++) { + // Shifting step + self->v_pos = (self->v_pos - 64) & 1023; + + for (int ch = 0; ch < 2; ch++) { + matrix_transform(self->sample[ch], p, self->V[ch], self->v_pos); + + // Build U, windowing, calculate output + memset(self->U, 0, sizeof(self->U)); + + int d_index = 512 - (self->v_pos >> 1); + int v_index = (self->v_pos % 128) >> 1; + while (v_index < 1024) { + for (int i = 0; i < 32; ++i) { + self->U[i] += self->D[d_index++] * self->V[ch][v_index++]; + } + + v_index += 128-32; + d_index += 64-32; + } + + v_index = (128-32 + 1024) - v_index; + d_index -= (512 - 32); + while (v_index < 1024) { + for (int i = 0; i < 32; ++i) { + self->U[i] += self->D[d_index++] * self->V[ch][v_index++]; + } + + v_index += 128-32; + d_index += 64-32; + } + + // Output samples + float *out_channel = ch == 0 + ? self->channel_left + : self->channel_right; + for (int j = 0; j < 32; j++) { + out_channel[out_pos + j] = (float)self->U[j] / 2147418112.0; + } + } // End of synthesis channel loop + out_pos += 32; + } // End of synthesis sub-block loop + + } // Decoding of the granule finished + } + + self->sample_rate = sample_rate; + return frame_size; +} + +const quantizer_spec_t *read_allocation(mp2_decoder_t *self, int sb, int tab3) { + int tab4 = QUANT_LUT_STEP_3[tab3][sb]; + int qtab = QUANT_LUT_STEP4[tab4 & 15][bit_buffer_read(self->bits, tab4 >> 4)]; + return qtab ? (&QUANT_TAB[qtab - 1]) : 0; +} + +void read_samples(mp2_decoder_t *self, int ch, int sb, int part) { + const quantizer_spec_t *q = self->allocation[ch][sb]; + int sf = self->scale_factor[ch][sb][part]; + int *sample = self->sample[ch][sb]; + int val = 0; + + if (!q) { + // No bits allocated for this subband + sample[0] = sample[1] = sample[2] = 0; + return; + } + + // Resolve scalefactor + if (sf == 63) { + sf = 0; + } + else { + int shift = (sf / 3)|0; + sf = (SCALEFACTOR_BASE[sf % 3] + ((1 << shift) >> 1)) >> shift; + } + + // Decode samples + int adj = q->levels; + if (q->group) { + // Decode grouped samples + val = bit_buffer_read(self->bits, q->bits); + sample[0] = val % adj; + val /= adj; + sample[1] = val % adj; + sample[2] = val / adj; + } + else { + // Decode direct samples + sample[0] = bit_buffer_read(self->bits, q->bits); + sample[1] = bit_buffer_read(self->bits, q->bits); + sample[2] = bit_buffer_read(self->bits, q->bits); + } + + // Postmultiply samples + int scale = 65536 / (adj + 1); + adj = ((adj + 1) >> 1) - 1; + + val = (adj - sample[0]) * scale; + sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; + + val = (adj - sample[1]) * scale; + sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; + + val = (adj - sample[2]) * scale; + sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; +} + +void matrix_transform(int s[32][3], int ss, float *d, int dp) { + float t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, + t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, + t25, t26, t27, t28, t29, t30, t31, t32, t33; + + t01 = s[ 0][ss] + s[31][ss]; t02 = (float)(s[ 0][ss] - s[31][ss]) * 0.500602998235; + t03 = s[ 1][ss] + s[30][ss]; t04 = (float)(s[ 1][ss] - s[30][ss]) * 0.505470959898; + t05 = s[ 2][ss] + s[29][ss]; t06 = (float)(s[ 2][ss] - s[29][ss]) * 0.515447309923; + t07 = s[ 3][ss] + s[28][ss]; t08 = (float)(s[ 3][ss] - s[28][ss]) * 0.53104259109; + t09 = s[ 4][ss] + s[27][ss]; t10 = (float)(s[ 4][ss] - s[27][ss]) * 0.553103896034; + t11 = s[ 5][ss] + s[26][ss]; t12 = (float)(s[ 5][ss] - s[26][ss]) * 0.582934968206; + t13 = s[ 6][ss] + s[25][ss]; t14 = (float)(s[ 6][ss] - s[25][ss]) * 0.622504123036; + t15 = s[ 7][ss] + s[24][ss]; t16 = (float)(s[ 7][ss] - s[24][ss]) * 0.674808341455; + t17 = s[ 8][ss] + s[23][ss]; t18 = (float)(s[ 8][ss] - s[23][ss]) * 0.744536271002; + t19 = s[ 9][ss] + s[22][ss]; t20 = (float)(s[ 9][ss] - s[22][ss]) * 0.839349645416; + t21 = s[10][ss] + s[21][ss]; t22 = (float)(s[10][ss] - s[21][ss]) * 0.972568237862; + t23 = s[11][ss] + s[20][ss]; t24 = (float)(s[11][ss] - s[20][ss]) * 1.16943993343; + t25 = s[12][ss] + s[19][ss]; t26 = (float)(s[12][ss] - s[19][ss]) * 1.48416461631; + t27 = s[13][ss] + s[18][ss]; t28 = (float)(s[13][ss] - s[18][ss]) * 2.05778100995; + t29 = s[14][ss] + s[17][ss]; t30 = (float)(s[14][ss] - s[17][ss]) * 3.40760841847; + t31 = s[15][ss] + s[16][ss]; t32 = (float)(s[15][ss] - s[16][ss]) * 10.1900081235; + + t33 = t01 + t31; t31 = (t01 - t31) * 0.502419286188; + t01 = t03 + t29; t29 = (t03 - t29) * 0.52249861494; + t03 = t05 + t27; t27 = (t05 - t27) * 0.566944034816; + t05 = t07 + t25; t25 = (t07 - t25) * 0.64682178336; + t07 = t09 + t23; t23 = (t09 - t23) * 0.788154623451; + t09 = t11 + t21; t21 = (t11 - t21) * 1.06067768599; + t11 = t13 + t19; t19 = (t13 - t19) * 1.72244709824; + t13 = t15 + t17; t17 = (t15 - t17) * 5.10114861869; + t15 = t33 + t13; t13 = (t33 - t13) * 0.509795579104; + t33 = t01 + t11; t01 = (t01 - t11) * 0.601344886935; + t11 = t03 + t09; t09 = (t03 - t09) * 0.899976223136; + t03 = t05 + t07; t07 = (t05 - t07) * 2.56291544774; + t05 = t15 + t03; t15 = (t15 - t03) * 0.541196100146; + t03 = t33 + t11; t11 = (t33 - t11) * 1.30656296488; + t33 = t05 + t03; t05 = (t05 - t03) * 0.707106781187; + t03 = t15 + t11; t15 = (t15 - t11) * 0.707106781187; + t03 += t15; + t11 = t13 + t07; t13 = (t13 - t07) * 0.541196100146; + t07 = t01 + t09; t09 = (t01 - t09) * 1.30656296488; + t01 = t11 + t07; t07 = (t11 - t07) * 0.707106781187; + t11 = t13 + t09; t13 = (t13 - t09) * 0.707106781187; + t11 += t13; t01 += t11; + t11 += t07; t07 += t13; + t09 = t31 + t17; t31 = (t31 - t17) * 0.509795579104; + t17 = t29 + t19; t29 = (t29 - t19) * 0.601344886935; + t19 = t27 + t21; t21 = (t27 - t21) * 0.899976223136; + t27 = t25 + t23; t23 = (t25 - t23) * 2.56291544774; + t25 = t09 + t27; t09 = (t09 - t27) * 0.541196100146; + t27 = t17 + t19; t19 = (t17 - t19) * 1.30656296488; + t17 = t25 + t27; t27 = (t25 - t27) * 0.707106781187; + t25 = t09 + t19; t19 = (t09 - t19) * 0.707106781187; + t25 += t19; + t09 = t31 + t23; t31 = (t31 - t23) * 0.541196100146; + t23 = t29 + t21; t21 = (t29 - t21) * 1.30656296488; + t29 = t09 + t23; t23 = (t09 - t23) * 0.707106781187; + t09 = t31 + t21; t31 = (t31 - t21) * 0.707106781187; + t09 += t31; t29 += t09; t09 += t23; t23 += t31; + t17 += t29; t29 += t25; t25 += t09; t09 += t27; + t27 += t23; t23 += t19; t19 += t31; + t21 = t02 + t32; t02 = (t02 - t32) * 0.502419286188; + t32 = t04 + t30; t04 = (t04 - t30) * 0.52249861494; + t30 = t06 + t28; t28 = (t06 - t28) * 0.566944034816; + t06 = t08 + t26; t08 = (t08 - t26) * 0.64682178336; + t26 = t10 + t24; t10 = (t10 - t24) * 0.788154623451; + t24 = t12 + t22; t22 = (t12 - t22) * 1.06067768599; + t12 = t14 + t20; t20 = (t14 - t20) * 1.72244709824; + t14 = t16 + t18; t16 = (t16 - t18) * 5.10114861869; + t18 = t21 + t14; t14 = (t21 - t14) * 0.509795579104; + t21 = t32 + t12; t32 = (t32 - t12) * 0.601344886935; + t12 = t30 + t24; t24 = (t30 - t24) * 0.899976223136; + t30 = t06 + t26; t26 = (t06 - t26) * 2.56291544774; + t06 = t18 + t30; t18 = (t18 - t30) * 0.541196100146; + t30 = t21 + t12; t12 = (t21 - t12) * 1.30656296488; + t21 = t06 + t30; t30 = (t06 - t30) * 0.707106781187; + t06 = t18 + t12; t12 = (t18 - t12) * 0.707106781187; + t06 += t12; + t18 = t14 + t26; t26 = (t14 - t26) * 0.541196100146; + t14 = t32 + t24; t24 = (t32 - t24) * 1.30656296488; + t32 = t18 + t14; t14 = (t18 - t14) * 0.707106781187; + t18 = t26 + t24; t24 = (t26 - t24) * 0.707106781187; + t18 += t24; t32 += t18; + t18 += t14; t26 = t14 + t24; + t14 = t02 + t16; t02 = (t02 - t16) * 0.509795579104; + t16 = t04 + t20; t04 = (t04 - t20) * 0.601344886935; + t20 = t28 + t22; t22 = (t28 - t22) * 0.899976223136; + t28 = t08 + t10; t10 = (t08 - t10) * 2.56291544774; + t08 = t14 + t28; t14 = (t14 - t28) * 0.541196100146; + t28 = t16 + t20; t20 = (t16 - t20) * 1.30656296488; + t16 = t08 + t28; t28 = (t08 - t28) * 0.707106781187; + t08 = t14 + t20; t20 = (t14 - t20) * 0.707106781187; + t08 += t20; + t14 = t02 + t10; t02 = (t02 - t10) * 0.541196100146; + t10 = t04 + t22; t22 = (t04 - t22) * 1.30656296488; + t04 = t14 + t10; t10 = (t14 - t10) * 0.707106781187; + t14 = t02 + t22; t02 = (t02 - t22) * 0.707106781187; + t14 += t02; t04 += t14; t14 += t10; t10 += t02; + t16 += t04; t04 += t08; t08 += t14; t14 += t28; + t28 += t10; t10 += t20; t20 += t02; t21 += t16; + t16 += t32; t32 += t04; t04 += t06; t06 += t08; + t08 += t18; t18 += t14; t14 += t30; t30 += t28; + t28 += t26; t26 += t10; t10 += t12; t12 += t20; + t20 += t24; t24 += t02; + + d[dp + 48] = -t33; + d[dp + 49] = d[dp + 47] = -t21; + d[dp + 50] = d[dp + 46] = -t17; + d[dp + 51] = d[dp + 45] = -t16; + d[dp + 52] = d[dp + 44] = -t01; + d[dp + 53] = d[dp + 43] = -t32; + d[dp + 54] = d[dp + 42] = -t29; + d[dp + 55] = d[dp + 41] = -t04; + d[dp + 56] = d[dp + 40] = -t03; + d[dp + 57] = d[dp + 39] = -t06; + d[dp + 58] = d[dp + 38] = -t25; + d[dp + 59] = d[dp + 37] = -t08; + d[dp + 60] = d[dp + 36] = -t11; + d[dp + 61] = d[dp + 35] = -t18; + d[dp + 62] = d[dp + 34] = -t09; + d[dp + 63] = d[dp + 33] = -t14; + d[dp + 32] = -t05; + d[dp + 0] = t05; d[dp + 31] = -t30; + d[dp + 1] = t30; d[dp + 30] = -t27; + d[dp + 2] = t27; d[dp + 29] = -t28; + d[dp + 3] = t28; d[dp + 28] = -t07; + d[dp + 4] = t07; d[dp + 27] = -t26; + d[dp + 5] = t26; d[dp + 26] = -t23; + d[dp + 6] = t23; d[dp + 25] = -t10; + d[dp + 7] = t10; d[dp + 24] = -t15; + d[dp + 8] = t15; d[dp + 23] = -t12; + d[dp + 9] = t12; d[dp + 22] = -t19; + d[dp + 10] = t19; d[dp + 21] = -t20; + d[dp + 11] = t20; d[dp + 20] = -t13; + d[dp + 12] = t13; d[dp + 19] = -t24; + d[dp + 13] = t24; d[dp + 18] = -t31; + d[dp + 14] = t31; d[dp + 17] = -t02; + d[dp + 15] = t02; d[dp + 16] = 0.0; +}; + diff --git a/public/static/jsmpeg-master/src/wasm/mp2.h b/public/static/jsmpeg-master/src/wasm/mp2.h new file mode 100644 index 0000000..069b2a7 --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm/mp2.h @@ -0,0 +1,22 @@ +#ifndef MP2_H +#define MP2_H + +#include +#include +#include "buffer.h" + +typedef struct mp2_decoder_t mp2_decoder_t; + +mp2_decoder_t *mp2_decoder_create(unsigned int buffer_size, bit_buffer_mode_t buffer_mode); +void mp2_decoder_destroy(mp2_decoder_t *self); +void *mp2_decoder_get_write_ptr(mp2_decoder_t *self, unsigned int byte_size); +int mp2_decoder_get_index(mp2_decoder_t *self); +void mp2_decoder_set_index(mp2_decoder_t *self, unsigned int index); +void mp2_decoder_did_write(mp2_decoder_t *self, unsigned int byte_size); + +void *mp2_decoder_get_left_channel_ptr(mp2_decoder_t *self); +void *mp2_decoder_get_right_channel_ptr(mp2_decoder_t *self); +int mp2_decoder_get_sample_rate(mp2_decoder_t *self); +int mp2_decoder_decode(mp2_decoder_t *self); + +#endif diff --git a/public/static/jsmpeg-master/src/wasm/mpeg1.c b/public/static/jsmpeg-master/src/wasm/mpeg1.c new file mode 100644 index 0000000..37f49fa --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm/mpeg1.c @@ -0,0 +1,1748 @@ +#include +#include +#include "mpeg1.h" + +static const float PICTURE_RATE[] = { + 0.000, 23.976, 24.000, 25.000, 29.970, 30.000, 50.000, 59.940, + 60.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000 +}; + +static const uint8_t ZIG_ZAG[] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static const uint8_t DEFAULT_INTRA_QUANT_MATRIX[] = { + 8, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +}; + +static const uint8_t DEFAULT_NON_INTRA_QUANT_MATRIX[] = { + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16 +}; + +static const uint8_t PREMULTIPLIER_MATRIX[] = { + 32, 44, 42, 38, 32, 25, 17, 9, + 44, 62, 58, 52, 44, 35, 24, 12, + 42, 58, 55, 49, 42, 33, 23, 12, + 38, 52, 49, 44, 38, 30, 20, 10, + 32, 44, 42, 38, 32, 25, 17, 9, + 25, 35, 33, 30, 25, 20, 14, 7, + 17, 24, 23, 20, 17, 14, 9, 5, + 9, 12, 12, 10, 9, 7, 5, 2 +}; + +// MPEG-1 VLC + +// macroblock_stuffing decodes as 34. +// macroblock_escape decodes as 35. + +static const int MACROBLOCK_ADDRESS_INCREMENT[] = { + 1*3, 2*3, 0, // 0 + 3*3, 4*3, 0, // 1 0 + 0, 0, 1, // 2 1. + 5*3, 6*3, 0, // 3 00 + 7*3, 8*3, 0, // 4 01 + 9*3, 10*3, 0, // 5 000 + 11*3, 12*3, 0, // 6 001 + 0, 0, 3, // 7 010. + 0, 0, 2, // 8 011. + 13*3, 14*3, 0, // 9 0000 + 15*3, 16*3, 0, // 10 0001 + 0, 0, 5, // 11 0010. + 0, 0, 4, // 12 0011. + 17*3, 18*3, 0, // 13 0000 0 + 19*3, 20*3, 0, // 14 0000 1 + 0, 0, 7, // 15 0001 0. + 0, 0, 6, // 16 0001 1. + 21*3, 22*3, 0, // 17 0000 00 + 23*3, 24*3, 0, // 18 0000 01 + 25*3, 26*3, 0, // 19 0000 10 + 27*3, 28*3, 0, // 20 0000 11 + -1, 29*3, 0, // 21 0000 000 + -1, 30*3, 0, // 22 0000 001 + 31*3, 32*3, 0, // 23 0000 010 + 33*3, 34*3, 0, // 24 0000 011 + 35*3, 36*3, 0, // 25 0000 100 + 37*3, 38*3, 0, // 26 0000 101 + 0, 0, 9, // 27 0000 110. + 0, 0, 8, // 28 0000 111. + 39*3, 40*3, 0, // 29 0000 0001 + 41*3, 42*3, 0, // 30 0000 0011 + 43*3, 44*3, 0, // 31 0000 0100 + 45*3, 46*3, 0, // 32 0000 0101 + 0, 0, 15, // 33 0000 0110. + 0, 0, 14, // 34 0000 0111. + 0, 0, 13, // 35 0000 1000. + 0, 0, 12, // 36 0000 1001. + 0, 0, 11, // 37 0000 1010. + 0, 0, 10, // 38 0000 1011. + 47*3, -1, 0, // 39 0000 0001 0 + -1, 48*3, 0, // 40 0000 0001 1 + 49*3, 50*3, 0, // 41 0000 0011 0 + 51*3, 52*3, 0, // 42 0000 0011 1 + 53*3, 54*3, 0, // 43 0000 0100 0 + 55*3, 56*3, 0, // 44 0000 0100 1 + 57*3, 58*3, 0, // 45 0000 0101 0 + 59*3, 60*3, 0, // 46 0000 0101 1 + 61*3, -1, 0, // 47 0000 0001 00 + -1, 62*3, 0, // 48 0000 0001 11 + 63*3, 64*3, 0, // 49 0000 0011 00 + 65*3, 66*3, 0, // 50 0000 0011 01 + 67*3, 68*3, 0, // 51 0000 0011 10 + 69*3, 70*3, 0, // 52 0000 0011 11 + 71*3, 72*3, 0, // 53 0000 0100 00 + 73*3, 74*3, 0, // 54 0000 0100 01 + 0, 0, 21, // 55 0000 0100 10. + 0, 0, 20, // 56 0000 0100 11. + 0, 0, 19, // 57 0000 0101 00. + 0, 0, 18, // 58 0000 0101 01. + 0, 0, 17, // 59 0000 0101 10. + 0, 0, 16, // 60 0000 0101 11. + 0, 0, 35, // 61 0000 0001 000. -- macroblock_escape + 0, 0, 34, // 62 0000 0001 111. -- macroblock_stuffing + 0, 0, 33, // 63 0000 0011 000. + 0, 0, 32, // 64 0000 0011 001. + 0, 0, 31, // 65 0000 0011 010. + 0, 0, 30, // 66 0000 0011 011. + 0, 0, 29, // 67 0000 0011 100. + 0, 0, 28, // 68 0000 0011 101. + 0, 0, 27, // 69 0000 0011 110. + 0, 0, 26, // 70 0000 0011 111. + 0, 0, 25, // 71 0000 0100 000. + 0, 0, 24, // 72 0000 0100 001. + 0, 0, 23, // 73 0000 0100 010. + 0, 0, 22 // 74 0000 0100 011. +}; + +// macroblock_type bitmap: +// 0x10 macroblock_quant +// 0x08 macroblock_motion_forward +// 0x04 macroblock_motion_backward +// 0x02 macrobkock_pattern +// 0x01 macroblock_intra +// + +static const int MACROBLOCK_TYPE_INTRA[] = { + 1*3, 2*3, 0, // 0 + -1, 3*3, 0, // 1 0 + 0, 0, 0x01, // 2 1. + 0, 0, 0x11 // 3 01. +}; + +static const int MACROBLOCK_TYPE_PREDICTIVE[] = { + 1*3, 2*3, 0, // 0 + 3*3, 4*3, 0, // 1 0 + 0, 0, 0x0a, // 2 1. + 5*3, 6*3, 0, // 3 00 + 0, 0, 0x02, // 4 01. + 7*3, 8*3, 0, // 5 000 + 0, 0, 0x08, // 6 001. + 9*3, 10*3, 0, // 7 0000 + 11*3, 12*3, 0, // 8 0001 + -1, 13*3, 0, // 9 00000 + 0, 0, 0x12, // 10 00001. + 0, 0, 0x1a, // 11 00010. + 0, 0, 0x01, // 12 00011. + 0, 0, 0x11 // 13 000001. +}; + +static const int MACROBLOCK_TYPE_B[] = { + 1*3, 2*3, 0, // 0 + 3*3, 5*3, 0, // 1 0 + 4*3, 6*3, 0, // 2 1 + 8*3, 7*3, 0, // 3 00 + 0, 0, 0x0c, // 4 10. + 9*3, 10*3, 0, // 5 01 + 0, 0, 0x0e, // 6 11. + 13*3, 14*3, 0, // 7 001 + 12*3, 11*3, 0, // 8 000 + 0, 0, 0x04, // 9 010. + 0, 0, 0x06, // 10 011. + 18*3, 16*3, 0, // 11 0001 + 15*3, 17*3, 0, // 12 0000 + 0, 0, 0x08, // 13 0010. + 0, 0, 0x0a, // 14 0011. + -1, 19*3, 0, // 15 00000 + 0, 0, 0x01, // 16 00011. + 20*3, 21*3, 0, // 17 00001 + 0, 0, 0x1e, // 18 00010. + 0, 0, 0x11, // 19 000001. + 0, 0, 0x16, // 20 000010. + 0, 0, 0x1a // 21 000011. +}; + +static const int *MACROBLOCK_TYPE[] = { + NULL, + MACROBLOCK_TYPE_INTRA, + MACROBLOCK_TYPE_PREDICTIVE, + MACROBLOCK_TYPE_B +}; + +static const int CODE_BLOCK_PATTERN[] = { + 2*3, 1*3, 0, // 0 + 3*3, 6*3, 0, // 1 1 + 4*3, 5*3, 0, // 2 0 + 8*3, 11*3, 0, // 3 10 + 12*3, 13*3, 0, // 4 00 + 9*3, 7*3, 0, // 5 01 + 10*3, 14*3, 0, // 6 11 + 20*3, 19*3, 0, // 7 011 + 18*3, 16*3, 0, // 8 100 + 23*3, 17*3, 0, // 9 010 + 27*3, 25*3, 0, // 10 110 + 21*3, 28*3, 0, // 11 101 + 15*3, 22*3, 0, // 12 000 + 24*3, 26*3, 0, // 13 001 + 0, 0, 60, // 14 111. + 35*3, 40*3, 0, // 15 0000 + 44*3, 48*3, 0, // 16 1001 + 38*3, 36*3, 0, // 17 0101 + 42*3, 47*3, 0, // 18 1000 + 29*3, 31*3, 0, // 19 0111 + 39*3, 32*3, 0, // 20 0110 + 0, 0, 32, // 21 1010. + 45*3, 46*3, 0, // 22 0001 + 33*3, 41*3, 0, // 23 0100 + 43*3, 34*3, 0, // 24 0010 + 0, 0, 4, // 25 1101. + 30*3, 37*3, 0, // 26 0011 + 0, 0, 8, // 27 1100. + 0, 0, 16, // 28 1011. + 0, 0, 44, // 29 0111 0. + 50*3, 56*3, 0, // 30 0011 0 + 0, 0, 28, // 31 0111 1. + 0, 0, 52, // 32 0110 1. + 0, 0, 62, // 33 0100 0. + 61*3, 59*3, 0, // 34 0010 1 + 52*3, 60*3, 0, // 35 0000 0 + 0, 0, 1, // 36 0101 1. + 55*3, 54*3, 0, // 37 0011 1 + 0, 0, 61, // 38 0101 0. + 0, 0, 56, // 39 0110 0. + 57*3, 58*3, 0, // 40 0000 1 + 0, 0, 2, // 41 0100 1. + 0, 0, 40, // 42 1000 0. + 51*3, 62*3, 0, // 43 0010 0 + 0, 0, 48, // 44 1001 0. + 64*3, 63*3, 0, // 45 0001 0 + 49*3, 53*3, 0, // 46 0001 1 + 0, 0, 20, // 47 1000 1. + 0, 0, 12, // 48 1001 1. + 80*3, 83*3, 0, // 49 0001 10 + 0, 0, 63, // 50 0011 00. + 77*3, 75*3, 0, // 51 0010 00 + 65*3, 73*3, 0, // 52 0000 00 + 84*3, 66*3, 0, // 53 0001 11 + 0, 0, 24, // 54 0011 11. + 0, 0, 36, // 55 0011 10. + 0, 0, 3, // 56 0011 01. + 69*3, 87*3, 0, // 57 0000 10 + 81*3, 79*3, 0, // 58 0000 11 + 68*3, 71*3, 0, // 59 0010 11 + 70*3, 78*3, 0, // 60 0000 01 + 67*3, 76*3, 0, // 61 0010 10 + 72*3, 74*3, 0, // 62 0010 01 + 86*3, 85*3, 0, // 63 0001 01 + 88*3, 82*3, 0, // 64 0001 00 + -1, 94*3, 0, // 65 0000 000 + 95*3, 97*3, 0, // 66 0001 111 + 0, 0, 33, // 67 0010 100. + 0, 0, 9, // 68 0010 110. + 106*3, 110*3, 0, // 69 0000 100 + 102*3, 116*3, 0, // 70 0000 010 + 0, 0, 5, // 71 0010 111. + 0, 0, 10, // 72 0010 010. + 93*3, 89*3, 0, // 73 0000 001 + 0, 0, 6, // 74 0010 011. + 0, 0, 18, // 75 0010 001. + 0, 0, 17, // 76 0010 101. + 0, 0, 34, // 77 0010 000. + 113*3, 119*3, 0, // 78 0000 011 + 103*3, 104*3, 0, // 79 0000 111 + 90*3, 92*3, 0, // 80 0001 100 + 109*3, 107*3, 0, // 81 0000 110 + 117*3, 118*3, 0, // 82 0001 001 + 101*3, 99*3, 0, // 83 0001 101 + 98*3, 96*3, 0, // 84 0001 110 + 100*3, 91*3, 0, // 85 0001 011 + 114*3, 115*3, 0, // 86 0001 010 + 105*3, 108*3, 0, // 87 0000 101 + 112*3, 111*3, 0, // 88 0001 000 + 121*3, 125*3, 0, // 89 0000 0011 + 0, 0, 41, // 90 0001 1000. + 0, 0, 14, // 91 0001 0111. + 0, 0, 21, // 92 0001 1001. + 124*3, 122*3, 0, // 93 0000 0010 + 120*3, 123*3, 0, // 94 0000 0001 + 0, 0, 11, // 95 0001 1110. + 0, 0, 19, // 96 0001 1101. + 0, 0, 7, // 97 0001 1111. + 0, 0, 35, // 98 0001 1100. + 0, 0, 13, // 99 0001 1011. + 0, 0, 50, // 100 0001 0110. + 0, 0, 49, // 101 0001 1010. + 0, 0, 58, // 102 0000 0100. + 0, 0, 37, // 103 0000 1110. + 0, 0, 25, // 104 0000 1111. + 0, 0, 45, // 105 0000 1010. + 0, 0, 57, // 106 0000 1000. + 0, 0, 26, // 107 0000 1101. + 0, 0, 29, // 108 0000 1011. + 0, 0, 38, // 109 0000 1100. + 0, 0, 53, // 110 0000 1001. + 0, 0, 23, // 111 0001 0001. + 0, 0, 43, // 112 0001 0000. + 0, 0, 46, // 113 0000 0110. + 0, 0, 42, // 114 0001 0100. + 0, 0, 22, // 115 0001 0101. + 0, 0, 54, // 116 0000 0101. + 0, 0, 51, // 117 0001 0010. + 0, 0, 15, // 118 0001 0011. + 0, 0, 30, // 119 0000 0111. + 0, 0, 39, // 120 0000 0001 0. + 0, 0, 47, // 121 0000 0011 0. + 0, 0, 55, // 122 0000 0010 1. + 0, 0, 27, // 123 0000 0001 1. + 0, 0, 59, // 124 0000 0010 0. + 0, 0, 31 // 125 0000 0011 1. +}; + +static const int MOTION[] = { + 1*3, 2*3, 0, // 0 + 4*3, 3*3, 0, // 1 0 + 0, 0, 0, // 2 1. + 6*3, 5*3, 0, // 3 01 + 8*3, 7*3, 0, // 4 00 + 0, 0, -1, // 5 011. + 0, 0, 1, // 6 010. + 9*3, 10*3, 0, // 7 001 + 12*3, 11*3, 0, // 8 000 + 0, 0, 2, // 9 0010. + 0, 0, -2, // 10 0011. + 14*3, 15*3, 0, // 11 0001 + 16*3, 13*3, 0, // 12 0000 + 20*3, 18*3, 0, // 13 0000 1 + 0, 0, 3, // 14 0001 0. + 0, 0, -3, // 15 0001 1. + 17*3, 19*3, 0, // 16 0000 0 + -1, 23*3, 0, // 17 0000 00 + 27*3, 25*3, 0, // 18 0000 11 + 26*3, 21*3, 0, // 19 0000 01 + 24*3, 22*3, 0, // 20 0000 10 + 32*3, 28*3, 0, // 21 0000 011 + 29*3, 31*3, 0, // 22 0000 101 + -1, 33*3, 0, // 23 0000 001 + 36*3, 35*3, 0, // 24 0000 100 + 0, 0, -4, // 25 0000 111. + 30*3, 34*3, 0, // 26 0000 010 + 0, 0, 4, // 27 0000 110. + 0, 0, -7, // 28 0000 0111. + 0, 0, 5, // 29 0000 1010. + 37*3, 41*3, 0, // 30 0000 0100 + 0, 0, -5, // 31 0000 1011. + 0, 0, 7, // 32 0000 0110. + 38*3, 40*3, 0, // 33 0000 0011 + 42*3, 39*3, 0, // 34 0000 0101 + 0, 0, -6, // 35 0000 1001. + 0, 0, 6, // 36 0000 1000. + 51*3, 54*3, 0, // 37 0000 0100 0 + 50*3, 49*3, 0, // 38 0000 0011 0 + 45*3, 46*3, 0, // 39 0000 0101 1 + 52*3, 47*3, 0, // 40 0000 0011 1 + 43*3, 53*3, 0, // 41 0000 0100 1 + 44*3, 48*3, 0, // 42 0000 0101 0 + 0, 0, 10, // 43 0000 0100 10. + 0, 0, 9, // 44 0000 0101 00. + 0, 0, 8, // 45 0000 0101 10. + 0, 0, -8, // 46 0000 0101 11. + 57*3, 66*3, 0, // 47 0000 0011 11 + 0, 0, -9, // 48 0000 0101 01. + 60*3, 64*3, 0, // 49 0000 0011 01 + 56*3, 61*3, 0, // 50 0000 0011 00 + 55*3, 62*3, 0, // 51 0000 0100 00 + 58*3, 63*3, 0, // 52 0000 0011 10 + 0, 0, -10, // 53 0000 0100 11. + 59*3, 65*3, 0, // 54 0000 0100 01 + 0, 0, 12, // 55 0000 0100 000. + 0, 0, 16, // 56 0000 0011 000. + 0, 0, 13, // 57 0000 0011 110. + 0, 0, 14, // 58 0000 0011 100. + 0, 0, 11, // 59 0000 0100 010. + 0, 0, 15, // 60 0000 0011 010. + 0, 0, -16, // 61 0000 0011 001. + 0, 0, -12, // 62 0000 0100 001. + 0, 0, -14, // 63 0000 0011 101. + 0, 0, -15, // 64 0000 0011 011. + 0, 0, -11, // 65 0000 0100 011. + 0, 0, -13 // 66 0000 0011 111. +}; + +static const int DCT_DC_SIZE_LUMINANCE[] = { + 2*3, 1*3, 0, // 0 + 6*3, 5*3, 0, // 1 1 + 3*3, 4*3, 0, // 2 0 + 0, 0, 1, // 3 00. + 0, 0, 2, // 4 01. + 9*3, 8*3, 0, // 5 11 + 7*3, 10*3, 0, // 6 10 + 0, 0, 0, // 7 100. + 12*3, 11*3, 0, // 8 111 + 0, 0, 4, // 9 110. + 0, 0, 3, // 10 101. + 13*3, 14*3, 0, // 11 1111 + 0, 0, 5, // 12 1110. + 0, 0, 6, // 13 1111 0. + 16*3, 15*3, 0, // 14 1111 1 + 17*3, -1, 0, // 15 1111 11 + 0, 0, 7, // 16 1111 10. + 0, 0, 8 // 17 1111 110. +}; + +static const int DCT_DC_SIZE_CHROMINANCE[] = { + 2*3, 1*3, 0, // 0 + 4*3, 3*3, 0, // 1 1 + 6*3, 5*3, 0, // 2 0 + 8*3, 7*3, 0, // 3 11 + 0, 0, 2, // 4 10. + 0, 0, 1, // 5 01. + 0, 0, 0, // 6 00. + 10*3, 9*3, 0, // 7 111 + 0, 0, 3, // 8 110. + 12*3, 11*3, 0, // 9 1111 + 0, 0, 4, // 10 1110. + 14*3, 13*3, 0, // 11 1111 1 + 0, 0, 5, // 12 1111 0. + 16*3, 15*3, 0, // 13 1111 11 + 0, 0, 6, // 14 1111 10. + 17*3, -1, 0, // 15 1111 111 + 0, 0, 7, // 16 1111 110. + 0, 0, 8 // 17 1111 1110. +}; + +// dct_coeff bitmap: +// 0xff00 run +// 0x00ff level + +// Decoded values are unsigned. Sign bit follows in the stream. + +// Interpretation of the value 0x0001 +// for dc_coeff_first: run=0, level=1 +// for dc_coeff_next: If the next bit is 1: run=0, level=1 +// If the next bit is 0: end_of_block + +// escape decodes as 0xffff. + +static const int DCT_COEFF[] = { + 1*3, 2*3, 0, // 0 + 4*3, 3*3, 0, // 1 0 + 0, 0, 0x0001, // 2 1. + 7*3, 8*3, 0, // 3 01 + 6*3, 5*3, 0, // 4 00 + 13*3, 9*3, 0, // 5 001 + 11*3, 10*3, 0, // 6 000 + 14*3, 12*3, 0, // 7 010 + 0, 0, 0x0101, // 8 011. + 20*3, 22*3, 0, // 9 0011 + 18*3, 21*3, 0, // 10 0001 + 16*3, 19*3, 0, // 11 0000 + 0, 0, 0x0201, // 12 0101. + 17*3, 15*3, 0, // 13 0010 + 0, 0, 0x0002, // 14 0100. + 0, 0, 0x0003, // 15 0010 1. + 27*3, 25*3, 0, // 16 0000 0 + 29*3, 31*3, 0, // 17 0010 0 + 24*3, 26*3, 0, // 18 0001 0 + 32*3, 30*3, 0, // 19 0000 1 + 0, 0, 0x0401, // 20 0011 0. + 23*3, 28*3, 0, // 21 0001 1 + 0, 0, 0x0301, // 22 0011 1. + 0, 0, 0x0102, // 23 0001 10. + 0, 0, 0x0701, // 24 0001 00. + 0, 0, 0xffff, // 25 0000 01. -- escape + 0, 0, 0x0601, // 26 0001 01. + 37*3, 36*3, 0, // 27 0000 00 + 0, 0, 0x0501, // 28 0001 11. + 35*3, 34*3, 0, // 29 0010 00 + 39*3, 38*3, 0, // 30 0000 11 + 33*3, 42*3, 0, // 31 0010 01 + 40*3, 41*3, 0, // 32 0000 10 + 52*3, 50*3, 0, // 33 0010 010 + 54*3, 53*3, 0, // 34 0010 001 + 48*3, 49*3, 0, // 35 0010 000 + 43*3, 45*3, 0, // 36 0000 001 + 46*3, 44*3, 0, // 37 0000 000 + 0, 0, 0x0801, // 38 0000 111. + 0, 0, 0x0004, // 39 0000 110. + 0, 0, 0x0202, // 40 0000 100. + 0, 0, 0x0901, // 41 0000 101. + 51*3, 47*3, 0, // 42 0010 011 + 55*3, 57*3, 0, // 43 0000 0010 + 60*3, 56*3, 0, // 44 0000 0001 + 59*3, 58*3, 0, // 45 0000 0011 + 61*3, 62*3, 0, // 46 0000 0000 + 0, 0, 0x0a01, // 47 0010 0111. + 0, 0, 0x0d01, // 48 0010 0000. + 0, 0, 0x0006, // 49 0010 0001. + 0, 0, 0x0103, // 50 0010 0101. + 0, 0, 0x0005, // 51 0010 0110. + 0, 0, 0x0302, // 52 0010 0100. + 0, 0, 0x0b01, // 53 0010 0011. + 0, 0, 0x0c01, // 54 0010 0010. + 76*3, 75*3, 0, // 55 0000 0010 0 + 67*3, 70*3, 0, // 56 0000 0001 1 + 73*3, 71*3, 0, // 57 0000 0010 1 + 78*3, 74*3, 0, // 58 0000 0011 1 + 72*3, 77*3, 0, // 59 0000 0011 0 + 69*3, 64*3, 0, // 60 0000 0001 0 + 68*3, 63*3, 0, // 61 0000 0000 0 + 66*3, 65*3, 0, // 62 0000 0000 1 + 81*3, 87*3, 0, // 63 0000 0000 01 + 91*3, 80*3, 0, // 64 0000 0001 01 + 82*3, 79*3, 0, // 65 0000 0000 11 + 83*3, 86*3, 0, // 66 0000 0000 10 + 93*3, 92*3, 0, // 67 0000 0001 10 + 84*3, 85*3, 0, // 68 0000 0000 00 + 90*3, 94*3, 0, // 69 0000 0001 00 + 88*3, 89*3, 0, // 70 0000 0001 11 + 0, 0, 0x0203, // 71 0000 0010 11. + 0, 0, 0x0104, // 72 0000 0011 00. + 0, 0, 0x0007, // 73 0000 0010 10. + 0, 0, 0x0402, // 74 0000 0011 11. + 0, 0, 0x0502, // 75 0000 0010 01. + 0, 0, 0x1001, // 76 0000 0010 00. + 0, 0, 0x0f01, // 77 0000 0011 01. + 0, 0, 0x0e01, // 78 0000 0011 10. + 105*3, 107*3, 0, // 79 0000 0000 111 + 111*3, 114*3, 0, // 80 0000 0001 011 + 104*3, 97*3, 0, // 81 0000 0000 010 + 125*3, 119*3, 0, // 82 0000 0000 110 + 96*3, 98*3, 0, // 83 0000 0000 100 + -1, 123*3, 0, // 84 0000 0000 000 + 95*3, 101*3, 0, // 85 0000 0000 001 + 106*3, 121*3, 0, // 86 0000 0000 101 + 99*3, 102*3, 0, // 87 0000 0000 011 + 113*3, 103*3, 0, // 88 0000 0001 110 + 112*3, 116*3, 0, // 89 0000 0001 111 + 110*3, 100*3, 0, // 90 0000 0001 000 + 124*3, 115*3, 0, // 91 0000 0001 010 + 117*3, 122*3, 0, // 92 0000 0001 101 + 109*3, 118*3, 0, // 93 0000 0001 100 + 120*3, 108*3, 0, // 94 0000 0001 001 + 127*3, 136*3, 0, // 95 0000 0000 0010 + 139*3, 140*3, 0, // 96 0000 0000 1000 + 130*3, 126*3, 0, // 97 0000 0000 0101 + 145*3, 146*3, 0, // 98 0000 0000 1001 + 128*3, 129*3, 0, // 99 0000 0000 0110 + 0, 0, 0x0802, // 100 0000 0001 0001. + 132*3, 134*3, 0, // 101 0000 0000 0011 + 155*3, 154*3, 0, // 102 0000 0000 0111 + 0, 0, 0x0008, // 103 0000 0001 1101. + 137*3, 133*3, 0, // 104 0000 0000 0100 + 143*3, 144*3, 0, // 105 0000 0000 1110 + 151*3, 138*3, 0, // 106 0000 0000 1010 + 142*3, 141*3, 0, // 107 0000 0000 1111 + 0, 0, 0x000a, // 108 0000 0001 0011. + 0, 0, 0x0009, // 109 0000 0001 1000. + 0, 0, 0x000b, // 110 0000 0001 0000. + 0, 0, 0x1501, // 111 0000 0001 0110. + 0, 0, 0x0602, // 112 0000 0001 1110. + 0, 0, 0x0303, // 113 0000 0001 1100. + 0, 0, 0x1401, // 114 0000 0001 0111. + 0, 0, 0x0702, // 115 0000 0001 0101. + 0, 0, 0x1101, // 116 0000 0001 1111. + 0, 0, 0x1201, // 117 0000 0001 1010. + 0, 0, 0x1301, // 118 0000 0001 1001. + 148*3, 152*3, 0, // 119 0000 0000 1101 + 0, 0, 0x0403, // 120 0000 0001 0010. + 153*3, 150*3, 0, // 121 0000 0000 1011 + 0, 0, 0x0105, // 122 0000 0001 1011. + 131*3, 135*3, 0, // 123 0000 0000 0001 + 0, 0, 0x0204, // 124 0000 0001 0100. + 149*3, 147*3, 0, // 125 0000 0000 1100 + 172*3, 173*3, 0, // 126 0000 0000 0101 1 + 162*3, 158*3, 0, // 127 0000 0000 0010 0 + 170*3, 161*3, 0, // 128 0000 0000 0110 0 + 168*3, 166*3, 0, // 129 0000 0000 0110 1 + 157*3, 179*3, 0, // 130 0000 0000 0101 0 + 169*3, 167*3, 0, // 131 0000 0000 0001 0 + 174*3, 171*3, 0, // 132 0000 0000 0011 0 + 178*3, 177*3, 0, // 133 0000 0000 0100 1 + 156*3, 159*3, 0, // 134 0000 0000 0011 1 + 164*3, 165*3, 0, // 135 0000 0000 0001 1 + 183*3, 182*3, 0, // 136 0000 0000 0010 1 + 175*3, 176*3, 0, // 137 0000 0000 0100 0 + 0, 0, 0x0107, // 138 0000 0000 1010 1. + 0, 0, 0x0a02, // 139 0000 0000 1000 0. + 0, 0, 0x0902, // 140 0000 0000 1000 1. + 0, 0, 0x1601, // 141 0000 0000 1111 1. + 0, 0, 0x1701, // 142 0000 0000 1111 0. + 0, 0, 0x1901, // 143 0000 0000 1110 0. + 0, 0, 0x1801, // 144 0000 0000 1110 1. + 0, 0, 0x0503, // 145 0000 0000 1001 0. + 0, 0, 0x0304, // 146 0000 0000 1001 1. + 0, 0, 0x000d, // 147 0000 0000 1100 1. + 0, 0, 0x000c, // 148 0000 0000 1101 0. + 0, 0, 0x000e, // 149 0000 0000 1100 0. + 0, 0, 0x000f, // 150 0000 0000 1011 1. + 0, 0, 0x0205, // 151 0000 0000 1010 0. + 0, 0, 0x1a01, // 152 0000 0000 1101 1. + 0, 0, 0x0106, // 153 0000 0000 1011 0. + 180*3, 181*3, 0, // 154 0000 0000 0111 1 + 160*3, 163*3, 0, // 155 0000 0000 0111 0 + 196*3, 199*3, 0, // 156 0000 0000 0011 10 + 0, 0, 0x001b, // 157 0000 0000 0101 00. + 203*3, 185*3, 0, // 158 0000 0000 0010 01 + 202*3, 201*3, 0, // 159 0000 0000 0011 11 + 0, 0, 0x0013, // 160 0000 0000 0111 00. + 0, 0, 0x0016, // 161 0000 0000 0110 01. + 197*3, 207*3, 0, // 162 0000 0000 0010 00 + 0, 0, 0x0012, // 163 0000 0000 0111 01. + 191*3, 192*3, 0, // 164 0000 0000 0001 10 + 188*3, 190*3, 0, // 165 0000 0000 0001 11 + 0, 0, 0x0014, // 166 0000 0000 0110 11. + 184*3, 194*3, 0, // 167 0000 0000 0001 01 + 0, 0, 0x0015, // 168 0000 0000 0110 10. + 186*3, 193*3, 0, // 169 0000 0000 0001 00 + 0, 0, 0x0017, // 170 0000 0000 0110 00. + 204*3, 198*3, 0, // 171 0000 0000 0011 01 + 0, 0, 0x0019, // 172 0000 0000 0101 10. + 0, 0, 0x0018, // 173 0000 0000 0101 11. + 200*3, 205*3, 0, // 174 0000 0000 0011 00 + 0, 0, 0x001f, // 175 0000 0000 0100 00. + 0, 0, 0x001e, // 176 0000 0000 0100 01. + 0, 0, 0x001c, // 177 0000 0000 0100 11. + 0, 0, 0x001d, // 178 0000 0000 0100 10. + 0, 0, 0x001a, // 179 0000 0000 0101 01. + 0, 0, 0x0011, // 180 0000 0000 0111 10. + 0, 0, 0x0010, // 181 0000 0000 0111 11. + 189*3, 206*3, 0, // 182 0000 0000 0010 11 + 187*3, 195*3, 0, // 183 0000 0000 0010 10 + 218*3, 211*3, 0, // 184 0000 0000 0001 010 + 0, 0, 0x0025, // 185 0000 0000 0010 011. + 215*3, 216*3, 0, // 186 0000 0000 0001 000 + 0, 0, 0x0024, // 187 0000 0000 0010 100. + 210*3, 212*3, 0, // 188 0000 0000 0001 110 + 0, 0, 0x0022, // 189 0000 0000 0010 110. + 213*3, 209*3, 0, // 190 0000 0000 0001 111 + 221*3, 222*3, 0, // 191 0000 0000 0001 100 + 219*3, 208*3, 0, // 192 0000 0000 0001 101 + 217*3, 214*3, 0, // 193 0000 0000 0001 001 + 223*3, 220*3, 0, // 194 0000 0000 0001 011 + 0, 0, 0x0023, // 195 0000 0000 0010 101. + 0, 0, 0x010b, // 196 0000 0000 0011 100. + 0, 0, 0x0028, // 197 0000 0000 0010 000. + 0, 0, 0x010c, // 198 0000 0000 0011 011. + 0, 0, 0x010a, // 199 0000 0000 0011 101. + 0, 0, 0x0020, // 200 0000 0000 0011 000. + 0, 0, 0x0108, // 201 0000 0000 0011 111. + 0, 0, 0x0109, // 202 0000 0000 0011 110. + 0, 0, 0x0026, // 203 0000 0000 0010 010. + 0, 0, 0x010d, // 204 0000 0000 0011 010. + 0, 0, 0x010e, // 205 0000 0000 0011 001. + 0, 0, 0x0021, // 206 0000 0000 0010 111. + 0, 0, 0x0027, // 207 0000 0000 0010 001. + 0, 0, 0x1f01, // 208 0000 0000 0001 1011. + 0, 0, 0x1b01, // 209 0000 0000 0001 1111. + 0, 0, 0x1e01, // 210 0000 0000 0001 1100. + 0, 0, 0x1002, // 211 0000 0000 0001 0101. + 0, 0, 0x1d01, // 212 0000 0000 0001 1101. + 0, 0, 0x1c01, // 213 0000 0000 0001 1110. + 0, 0, 0x010f, // 214 0000 0000 0001 0011. + 0, 0, 0x0112, // 215 0000 0000 0001 0000. + 0, 0, 0x0111, // 216 0000 0000 0001 0001. + 0, 0, 0x0110, // 217 0000 0000 0001 0010. + 0, 0, 0x0603, // 218 0000 0000 0001 0100. + 0, 0, 0x0b02, // 219 0000 0000 0001 1010. + 0, 0, 0x0e02, // 220 0000 0000 0001 0111. + 0, 0, 0x0d02, // 221 0000 0000 0001 1000. + 0, 0, 0x0c02, // 222 0000 0000 0001 1001. + 0, 0, 0x0f02 // 223 0000 0000 0001 0110. +}; + +static const int PICTURE_TYPE_INTRA = 1; +static const int PICTURE_TYPE_PREDICTIVE = 2; +static const int PICTURE_TYPE_B = 3; + +static const int START_SEQUENCE = 0xB3; +static const int START_SLICE_FIRST = 0x01; +static const int START_SLICE_LAST = 0xAF; +static const int START_PICTURE = 0x00; +static const int START_EXTENSION = 0xB5; +static const int START_USER_DATA = 0xB2; + + +typedef struct mpeg1_planes_t { + uint8_t *y; + uint8_t *cr; + uint8_t *cb; +} mpeg1_planes_t; + +typedef struct mpeg1_decoder_t { + float frame_rate; + int width; + int height; + int mb_width; + int mb_height; + int mb_size; + + int coded_width; + int coded_height; + int coded_size; + + int half_width; + int half_height; + + int picture_type; + int full_pel_forward; + int forward_f_code; + int forward_r_size; + int forward_f; + + int has_sequence_header; + + int quantizer_scale; + int slice_begin; + int macroblock_address; + + int mb_row; + int mb_col; + + int macroblock_type; + int macroblock_intra; + int macroblock_motion_fw; + + int motion_fw_h; + int motion_fw_v; + int motion_fw_h_prev; + int motion_fw_v_prev; + + int dc_predictor_y; + int dc_predictor_cr; + int dc_predictor_cb; + + bit_buffer_t *bits; + + mpeg1_planes_t planes_current; + mpeg1_planes_t planes_forward; + + int block_data[64]; + uint8_t intra_quant_matrix[64]; + uint8_t non_intra_quant_matrix[64]; +} mpeg1_decoder_t; + + +void decode_sequence_header(mpeg1_decoder_t *self); +void decode_picture(mpeg1_decoder_t *self); +void init_buffers(mpeg1_decoder_t *self); +void decode_slice(mpeg1_decoder_t *self, int slice); +void decode_macroblock(mpeg1_decoder_t *self); +void decode_motion_vectors(mpeg1_decoder_t *self); +void copy_macroblock(mpeg1_decoder_t *self, int motion_h, int motion_v, uint8_t *s_y, uint8_t *s_cr, uint8_t *s_cb); +void decode_block(mpeg1_decoder_t *self, int block); + +void copy_block_to_destination(int *block, uint8_t *dest, int index, int scan); +void add_block_to_destination(int *block, uint8_t *dest, int index, int scan); +void copy_value_to_destination(int value, uint8_t *dest, int index, int scan); +void add_value_to_destination(int value, uint8_t *dest, int index, int scan); +void idct(int *block); +void zero_block_data(mpeg1_decoder_t *self); +int read_huffman(bit_buffer_t *bits, const int *code_table); + + + + +// ----------------------------------------------------------------------------- +// Public interface + +mpeg1_decoder_t *mpeg1_decoder_create(unsigned int buffer_size, bit_buffer_mode_t buffer_mode) { + mpeg1_decoder_t *self = malloc(sizeof(mpeg1_decoder_t)); + memset(self, 0, sizeof(mpeg1_decoder_t)); + self->bits = bit_buffer_create(buffer_size, buffer_mode); + return self; +} + +void mpeg1_decoder_destroy(mpeg1_decoder_t *self) { + bit_buffer_destroy(self->bits); + + if (self->has_sequence_header) { + free(self->planes_current.y); + free(self->planes_current.cr); + free(self->planes_current.cb); + + free(self->planes_forward.y); + free(self->planes_forward.cr); + free(self->planes_forward.cb); + } + + free(self); +} + +void *mpeg1_decoder_get_write_ptr(mpeg1_decoder_t *self, unsigned int byte_size) { + return bit_buffer_get_write_ptr(self->bits, byte_size); +} + +int mpeg1_decoder_get_index(mpeg1_decoder_t *self) { + return bit_buffer_get_index(self->bits); +} + +void mpeg1_decoder_set_index(mpeg1_decoder_t *self, unsigned int index) { + bit_buffer_set_index(self->bits, index); +} + +void mpeg1_decoder_did_write(mpeg1_decoder_t *self, unsigned int byte_size) { + bit_buffer_did_write(self->bits, byte_size); + if (!self->has_sequence_header) { + if (bit_buffer_find_start_code(self->bits, START_SEQUENCE) != -1) { + decode_sequence_header(self); + } + } +} + +int mpeg1_decoder_has_sequence_header(mpeg1_decoder_t *self) { + return self->has_sequence_header; +} + +float mpeg1_decoder_get_frame_rate(mpeg1_decoder_t *self) { + return self->frame_rate; +} + +int mpeg1_decoder_get_coded_size(mpeg1_decoder_t *self) { + return self->coded_size; +} + +int mpeg1_decoder_get_width(mpeg1_decoder_t *self) { + return self->width; +} + +int mpeg1_decoder_get_height(mpeg1_decoder_t *self) { + return self->height; +} + +void *mpeg1_decoder_get_y_ptr(mpeg1_decoder_t *self) { + return self->planes_forward.y; +} + +void *mpeg1_decoder_get_cr_ptr(mpeg1_decoder_t *self) { + return self->planes_forward.cr; +} + +void *mpeg1_decoder_get_cb_ptr(mpeg1_decoder_t *self) { + return self->planes_forward.cb; +} + +bool mpeg1_decoder_decode(mpeg1_decoder_t *self) { + if (!self->has_sequence_header) { + return false; + } + + if (bit_buffer_find_start_code(self->bits, START_PICTURE) == -1) { + return false; + } + + decode_picture(self); + return true; +} + + + + +// ----------------------------------------------------------------------------- +// Private methods + +void decode_sequence_header(mpeg1_decoder_t *self) { + int previous_width = self->width; + int previous_height = self->height; + + self->width = bit_buffer_read(self->bits, 12); + self->height = bit_buffer_read(self->bits, 12); + + // skip pixel aspect ratio + bit_buffer_skip(self->bits, 4); + + self->frame_rate = PICTURE_RATE[bit_buffer_read(self->bits, 4)]; + + // skip bitRate, marker, bufferSize and constrained bit + bit_buffer_skip(self->bits, 18 + 1 + 10 + 1); + + if (bit_buffer_read(self->bits, 1)) { // load custom intra quant matrix? + for (int i = 0; i < 64; i++) { + self->intra_quant_matrix[ZIG_ZAG[i]] = bit_buffer_read(self->bits, 8); + } + } + else { + memcpy(self->intra_quant_matrix, DEFAULT_INTRA_QUANT_MATRIX, 64); + } + + if (bit_buffer_read(self->bits, 1)) { // load custom non intra quant matrix? + for (int i = 0; i < 64; i++) { + int idx = ZIG_ZAG[i]; + self->non_intra_quant_matrix[idx] = bit_buffer_read(self->bits, 8); + } + } + else { + memcpy(self->non_intra_quant_matrix, DEFAULT_NON_INTRA_QUANT_MATRIX, 64); + } + + if (self->has_sequence_header) { + if (self->width == previous_width && self->height == previous_height) { + // We already had a sequence header with the same width/height; + // nothing else to do here. + return; + } + + // We had a sequence header but with different dimensions; + // delete the previous planes and allocate new. + free(self->planes_current.y); + free(self->planes_current.cr); + free(self->planes_current.cb); + + free(self->planes_forward.y); + free(self->planes_forward.cr); + free(self->planes_forward.cb); + } + + self->mb_width = (self->width + 15) >> 4; + self->mb_height = (self->height + 15) >> 4; + self->mb_size = self->mb_width * self->mb_height; + + self->coded_width = self->mb_width << 4; + self->coded_height = self->mb_height << 4; + self->coded_size = self->coded_width * self->coded_height; + + self->half_width = self->mb_width << 3; + self->half_height = self->mb_height << 3; + + self->planes_current.y = (uint8_t*)malloc(self->coded_size); + self->planes_current.cr = (uint8_t*)malloc(self->coded_size >> 2); + self->planes_current.cb = (uint8_t*)malloc(self->coded_size >> 2); + + self->planes_forward.y = (uint8_t*)malloc(self->coded_size); + self->planes_forward.cr = (uint8_t*)malloc(self->coded_size >> 2); + self->planes_forward.cb = (uint8_t*)malloc(self->coded_size >> 2); + + self->has_sequence_header = true; +} + + +void decode_picture(mpeg1_decoder_t *self) { + bit_buffer_skip(self->bits, 10); // skip temporalReference + self->picture_type = bit_buffer_read(self->bits, 3); + bit_buffer_skip(self->bits, 16); // skip vbv_delay + + // Skip B and D frames or unknown coding type + if (self->picture_type <= 0 || self->picture_type >= PICTURE_TYPE_B) { + return; + } + + // full_pel_forward, forward_f_code + if (self->picture_type == PICTURE_TYPE_PREDICTIVE) { + self->full_pel_forward = bit_buffer_read(self->bits, 1); + self->forward_f_code = bit_buffer_read(self->bits, 3); + if (self->forward_f_code == 0) { + // Ignore picture with zero self->forward_f_code + return; + } + self->forward_r_size = self->forward_f_code - 1; + self->forward_f = 1 << self->forward_r_size; + } + + int code = 0; + do { + code = bit_buffer_find_next_start_code(self->bits); + } while (code == START_EXTENSION || code == START_USER_DATA); + + + while (code >= START_SLICE_FIRST && code <= START_SLICE_LAST) { + decode_slice(self, code & 0x000000FF); + code = bit_buffer_find_next_start_code(self->bits); + } + + if (code != -1) { + // We found the next start code; rewind 32self->bits and let the main loop + // handle it. + bit_buffer_rewind(self->bits, 32); + } + + // If this is a reference picutre then rotate the prediction pointers + if ( + self->picture_type == PICTURE_TYPE_INTRA || + self->picture_type == PICTURE_TYPE_PREDICTIVE + ) { + mpeg1_planes_t temp = self->planes_forward; + self->planes_forward = self->planes_current; + self->planes_current = temp; + } +} + + +// Slice Layer + +void decode_slice(mpeg1_decoder_t *self, int slice) { + self->slice_begin = true; + self->macroblock_address = (slice - 1) * self->mb_width - 1; + + // Reset motion vectors and DC predictors + self->motion_fw_h = self->motion_fw_h_prev = 0; + self->motion_fw_v = self->motion_fw_v_prev = 0; + self->dc_predictor_y = 128; + self->dc_predictor_cr = 128; + self->dc_predictor_cb = 128; + + self->quantizer_scale = bit_buffer_read(self->bits, 5); + + // skip extra self->bits + while (bit_buffer_read(self->bits, 1)) { + bit_buffer_skip(self->bits, 8); + } + + do { + decode_macroblock(self); + } while (!bit_buffer_next_bytes_are_start_code(self->bits)); +}; + + +// Macroblock Layer + +void decode_macroblock(mpeg1_decoder_t *self) { + // Decode self->macroblock_address_increment + int increment = 0; + int x = 0; + int t = read_huffman(self->bits, MACROBLOCK_ADDRESS_INCREMENT); + + while (t == 34) { + // macroblock_stuffing + x = 1; + t = read_huffman(self->bits, MACROBLOCK_ADDRESS_INCREMENT); + } + while (t == 35) { + // macroblock_escape + increment += 33; + x = + t = read_huffman(self->bits, MACROBLOCK_ADDRESS_INCREMENT); + } + increment += t; + + // Process any skipped macroblocks + if (self->slice_begin) { + // The first self->macroblock_address_increment of each slice is relative + // to beginning of the preverious row, not the preverious macroblock + self->slice_begin = false; + self->macroblock_address += increment; + } + else { + if (self->macroblock_address + increment >= self->mb_size) { + // Illegal (too large) self->macroblock_address_increment + // abort(); + return; + } + if (increment > 1) { + // Skipped macroblocks reset DC predictors + self->dc_predictor_y = 128; + self->dc_predictor_cr = 128; + self->dc_predictor_cb = 128; + + // Skipped macroblocks in P-pictures reset motion vectors + if (self->picture_type == PICTURE_TYPE_PREDICTIVE) { + self->motion_fw_h = self->motion_fw_h_prev = 0; + self->motion_fw_v = self->motion_fw_v_prev = 0; + } + } + + // Predict skipped macroblocks + while (increment > 1) { + self->macroblock_address++; + self->mb_row = (self->macroblock_address / self->mb_width)|0; + self->mb_col = self->macroblock_address % self->mb_width; + copy_macroblock( + self, + self->motion_fw_h, self->motion_fw_v, + self->planes_forward.y, self->planes_forward.cr, self->planes_forward.cb + ); + increment--; + } + self->macroblock_address++; + } + + self->mb_row = (self->macroblock_address / self->mb_width)|0; + self->mb_col = self->macroblock_address % self->mb_width; + + // Process the current macroblock + // static const s16 *mbTable = MACROBLOCK_TYPE[self->picture_type]; + // macroblock_type = read_huffman(self->bits, mbTable); + if (self->picture_type == PICTURE_TYPE_INTRA) { + self->macroblock_type = read_huffman(self->bits, MACROBLOCK_TYPE_INTRA); + } + else if (self->picture_type == PICTURE_TYPE_PREDICTIVE) { + self->macroblock_type = read_huffman(self->bits, MACROBLOCK_TYPE_PREDICTIVE); + } + else { + // Unhandled picture type + // abort(); + } + self->macroblock_intra = (self->macroblock_type & 0x01); + self->macroblock_motion_fw = (self->macroblock_type & 0x08); + + // Quantizer scale + if ((self->macroblock_type & 0x10) != 0) { + self->quantizer_scale = bit_buffer_read(self->bits, 5); + } + + if (self->macroblock_intra) { + // Intra-coded macroblocks reset motion vectors + self->motion_fw_h = self->motion_fw_h_prev = 0; + self->motion_fw_v = self->motion_fw_v_prev = 0; + } + else { + // Non-intra macroblocks reset DC predictors + self->dc_predictor_y = 128; + self->dc_predictor_cr = 128; + self->dc_predictor_cb = 128; + + decode_motion_vectors(self); + copy_macroblock( + self, + self->motion_fw_h, self->motion_fw_v, + self->planes_forward.y, self->planes_forward.cr, self->planes_forward.cb + ); + } + + // Decode blocks + int cbp = ((self->macroblock_type & 0x02) != 0) + ? read_huffman(self->bits, CODE_BLOCK_PATTERN) + : (self->macroblock_intra ? 0x3f : 0); + + for (int block = 0, mask = 0x20; block < 6; block++) { + if ((cbp & mask) != 0) { + decode_block(self, block); + } + mask >>= 1; + } +}; + + +void decode_motion_vectors(mpeg1_decoder_t *self) { + int code, d, r = 0; + + // Forward + if (self->macroblock_motion_fw) { + // Horizontal forward + code = read_huffman(self->bits, MOTION); + if ((code != 0) && (self->forward_f != 1)) { + r = bit_buffer_read(self->bits, self->forward_r_size); + d = ((abs(code) - 1) << self->forward_r_size) + r + 1; + if (code < 0) { + d = -d; + } + } + else { + d = code; + } + + self->motion_fw_h_prev += d; + if (self->motion_fw_h_prev > (self->forward_f << 4) - 1) { + self->motion_fw_h_prev -= self->forward_f << 5; + } + else if (self->motion_fw_h_prev < ((-self->forward_f) << 4)) { + self->motion_fw_h_prev += self->forward_f << 5; + } + + self->motion_fw_h = self->motion_fw_h_prev; + if (self->full_pel_forward) { + self->motion_fw_h <<= 1; + } + + // Vertical forward + code = read_huffman(self->bits, MOTION); + if ((code != 0) && (self->forward_f != 1)) { + r = bit_buffer_read(self->bits, self->forward_r_size); + d = ((abs(code) - 1) << self->forward_r_size) + r + 1; + if (code < 0) { + d = -d; + } + } + else { + d = code; + } + + self->motion_fw_v_prev += d; + if (self->motion_fw_v_prev > (self->forward_f << 4) - 1) { + self->motion_fw_v_prev -= self->forward_f << 5; + } + else if (self->motion_fw_v_prev < ((-self->forward_f) << 4)) { + self->motion_fw_v_prev += self->forward_f << 5; + } + + self->motion_fw_v = self->motion_fw_v_prev; + if (self->full_pel_forward) { + self->motion_fw_v <<= 1; + } + } + else if (self->picture_type == PICTURE_TYPE_PREDICTIVE) { + // No motion information in P-picture, reset vectors + self->motion_fw_h = self->motion_fw_h_prev = 0; + self->motion_fw_v = self->motion_fw_v_prev = 0; + } +} + + +void copy_macroblock(mpeg1_decoder_t *self, int motion_h, int motion_v, uint8_t *s_y, uint8_t *s_cr, uint8_t *s_cb) { + int + width, scan, + H, V, + src, dest, last; + bool odd_h, odd_v; + + // We use 32bit writes here + int *d_y = (int*)self->planes_current.y; + int *d_cb = (int*)self->planes_current.cb; + int *d_cr = (int*)self->planes_current.cr; + + // Luminance + width = self->coded_width; + scan = width - 16; + + H = motion_h >> 1; + V = motion_v >> 1; + odd_h = (motion_h & 1) == 1; + odd_v = (motion_v & 1) == 1; + + src = ((self->mb_row << 4) + V) * width + (self->mb_col << 4) + H; + dest = (self->mb_row * width + self->mb_col) << 2; + last = dest + (width << 2); + + int x, y1, y2, y; + if (odd_h) { + if (odd_v) { + while (dest < last) { + y1 = s_y[src] + s_y[src+width]; src++; + for (x = 0; x < 4; x++) { + y2 = s_y[src] + s_y[src+width]; src++; + y = (((y1 + y2 + 2) >> 2) & 0xff); + + y1 = s_y[src] + s_y[src+width]; src++; + y |= (((y1 + y2 + 2) << 6) & 0xff00); + + y2 = s_y[src] + s_y[src+width]; src++; + y |= (((y1 + y2 + 2) << 14) & 0xff0000); + + y1 = s_y[src] + s_y[src+width]; src++; + y |= (((y1 + y2 + 2) << 22) & 0xff000000); + + d_y[dest++] = y; + } + dest += scan >> 2; src += scan-1; + } + } + else { + while (dest < last) { + y1 = s_y[src++]; + for (x = 0; x < 4; x++) { + y2 = s_y[src++]; + y = (((y1 + y2 + 1) >> 1) & 0xff); + + y1 = s_y[src++]; + y |= (((y1 + y2 + 1) << 7) & 0xff00); + + y2 = s_y[src++]; + y |= (((y1 + y2 + 1) << 15) & 0xff0000); + + y1 = s_y[src++]; + y |= (((y1 + y2 + 1) << 23) & 0xff000000); + + d_y[dest++] = y; + } + dest += scan >> 2; src += scan-1; + } + } + } + else { + if (odd_v) { + while (dest < last) { + for (x = 0; x < 4; x++) { + y = (((s_y[src] + s_y[src+width] + 1) >> 1) & 0xff); src++; + y |= (((s_y[src] + s_y[src+width] + 1) << 7) & 0xff00); src++; + y |= (((s_y[src] + s_y[src+width] + 1) << 15) & 0xff0000); src++; + y |= (((s_y[src] + s_y[src+width] + 1) << 23) & 0xff000000); src++; + + d_y[dest++] = y; + } + dest += scan >> 2; src += scan; + } + } + else { + while (dest < last) { + for (x = 0; x < 4; x++) { + y = s_y[src]; src++; + y |= s_y[src] << 8; src++; + y |= s_y[src] << 16; src++; + y |= s_y[src] << 24; src++; + + d_y[dest++] = y; + } + dest += scan >> 2; src += scan; + } + } + } + + // Chrominance + + width = self->half_width; + scan = width - 8; + + H = (motion_h/2) >> 1; + V = (motion_v/2) >> 1; + odd_h = ((motion_h/2) & 1) == 1; + odd_v = ((motion_v/2) & 1) == 1; + + src = ((self->mb_row << 3) + V) * width + (self->mb_col << 3) + H; + dest = (self->mb_row * width + self->mb_col) << 1; + last = dest + (width << 1); + + int cr1, cr2, cr, + cb1, cb2, cb; + if (odd_h) { + if (odd_v) { + while (dest < last) { + cr1 = s_cr[src] + s_cr[src+width]; + cb1 = s_cb[src] + s_cb[src+width]; + src++; + for (x = 0; x < 2; x++) { + cr2 = s_cr[src] + s_cr[src+width]; + cb2 = s_cb[src] + s_cb[src+width]; src++; + cr = (((cr1 + cr2 + 2) >> 2) & 0xff); + cb = (((cb1 + cb2 + 2) >> 2) & 0xff); + + cr1 = s_cr[src] + s_cr[src+width]; + cb1 = s_cb[src] + s_cb[src+width]; src++; + cr |= (((cr1 + cr2 + 2) << 6) & 0xff00); + cb |= (((cb1 + cb2 + 2) << 6) & 0xff00); + + cr2 = s_cr[src] + s_cr[src+width]; + cb2 = s_cb[src] + s_cb[src+width]; src++; + cr |= (((cr1 + cr2 + 2) << 14) & 0xff0000); + cb |= (((cb1 + cb2 + 2) << 14) & 0xff0000); + + cr1 = s_cr[src] + s_cr[src+width]; + cb1 = s_cb[src] + s_cb[src+width]; src++; + cr |= (((cr1 + cr2 + 2) << 22) & 0xff000000); + cb |= (((cb1 + cb2 + 2) << 22) & 0xff000000); + + d_cr[dest] = cr; + d_cb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan-1; + } + } + else { + while (dest < last) { + cr1 = s_cr[src]; + cb1 = s_cb[src]; + src++; + for (x = 0; x < 2; x++) { + cr2 = s_cr[src]; + cb2 = s_cb[src++]; + cr = (((cr1 + cr2 + 1) >> 1) & 0xff); + cb = (((cb1 + cb2 + 1) >> 1) & 0xff); + + cr1 = s_cr[src]; + cb1 = s_cb[src++]; + cr |= (((cr1 + cr2 + 1) << 7) & 0xff00); + cb |= (((cb1 + cb2 + 1) << 7) & 0xff00); + + cr2 = s_cr[src]; + cb2 = s_cb[src++]; + cr |= (((cr1 + cr2 + 1) << 15) & 0xff0000); + cb |= (((cb1 + cb2 + 1) << 15) & 0xff0000); + + cr1 = s_cr[src]; + cb1 = s_cb[src++]; + cr |= (((cr1 + cr2 + 1) << 23) & 0xff000000); + cb |= (((cb1 + cb2 + 1) << 23) & 0xff000000); + + d_cr[dest] = cr; + d_cb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan-1; + } + } + } + else { + if (odd_v) { + while (dest < last) { + for (x = 0; x < 2; x++) { + cr = (((s_cr[src] + s_cr[src+width] + 1) >> 1) & 0xff); + cb = (((s_cb[src] + s_cb[src+width] + 1) >> 1) & 0xff); src++; + + cr |= (((s_cr[src] + s_cr[src+width] + 1) << 7) & 0xff00); + cb |= (((s_cb[src] + s_cb[src+width] + 1) << 7) & 0xff00); src++; + + cr |= (((s_cr[src] + s_cr[src+width] + 1) << 15) & 0xff0000); + cb |= (((s_cb[src] + s_cb[src+width] + 1) << 15) & 0xff0000); src++; + + cr |= (((s_cr[src] + s_cr[src+width] + 1) << 23) & 0xff000000); + cb |= (((s_cb[src] + s_cb[src+width] + 1) << 23) & 0xff000000); src++; + + d_cr[dest] = cr; + d_cb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan; + } + } + else { + while (dest < last) { + for (x = 0; x < 2; x++) { + cr = s_cr[src]; + cb = s_cb[src]; src++; + + cr |= s_cr[src] << 8; + cb |= s_cb[src] << 8; src++; + + cr |= s_cr[src] << 16; + cb |= s_cb[src] << 16; src++; + + cr |= s_cr[src] << 24; + cb |= s_cb[src] << 24; src++; + + d_cr[dest] = cr; + d_cb[dest] = cb; + dest++; + } + dest += scan >> 2; src += scan; + } + } + } +} + + +// Block layer + +void decode_block(mpeg1_decoder_t *self, int block) { + + int n = 0; + uint8_t *quant_matrix; + + // Decode DC coefficient of intra-coded blocks + if (self->macroblock_intra) { + int predictor; + int dctSize; + + // DC prediction + + if (block < 4) { + predictor = self->dc_predictor_y; + dctSize = read_huffman(self->bits, DCT_DC_SIZE_LUMINANCE); + } + else { + predictor = (block == 4 ? self->dc_predictor_cr : self->dc_predictor_cb); + dctSize = read_huffman(self->bits, DCT_DC_SIZE_CHROMINANCE); + } + + // Read DC coeff + if (dctSize > 0) { + int differential = bit_buffer_read(self->bits, dctSize); + if ((differential & (1 << (dctSize - 1))) != 0) { + self->block_data[0] = predictor + differential; + } + else { + self->block_data[0] = predictor + ((-1 << dctSize)|(differential+1)); + } + } + else { + self->block_data[0] = predictor; + } + + // Save predictor value + if (block < 4) { + self->dc_predictor_y = self->block_data[0]; + } + else if (block == 4) { + self->dc_predictor_cr = self->block_data[0]; + } + else { + self->dc_predictor_cb = self->block_data[0]; + } + + // Dequantize + premultiply + self->block_data[0] <<= (3 + 5); + + quant_matrix = self->intra_quant_matrix; + n = 1; + } + else { + quant_matrix = self->non_intra_quant_matrix; + } + + // Decode AC coefficients (+DC for non-intra) + int level = 0; + while (true) { + int run = 0; + int coeff = read_huffman(self->bits, DCT_COEFF); + + if ((coeff == 0x0001) && (n > 0) && (bit_buffer_read(self->bits, 1) == 0)) { + // end_of_block + break; + } + if (coeff == 0xffff) { + // escape + run = bit_buffer_read(self->bits, 6); + level = bit_buffer_read(self->bits, 8); + if (level == 0) { + level = bit_buffer_read(self->bits, 8); + } + else if (level == 128) { + level = bit_buffer_read(self->bits, 8) - 256; + } + else if (level > 128) { + level = level - 256; + } + } + else { + run = coeff >> 8; + level = coeff & 0xff; + if (bit_buffer_read(self->bits, 1)) { + level = -level; + } + } + + n += run; + int dezigZagged = ZIG_ZAG[n]; + n++; + + // Dequantize, oddify, clip + level <<= 1; + if (!self->macroblock_intra) { + level += (level < 0 ? -1 : 1); + } + level = (level * self->quantizer_scale * quant_matrix[dezigZagged]) >> 4; + if ((level & 1) == 0) { + level -= level > 0 ? 1 : -1; + } + if (level > 2047) { + level = 2047; + } + else if (level < -2048) { + level = -2048; + } + + // Save premultiplied coefficient + self->block_data[dezigZagged] = level * PREMULTIPLIER_MATRIX[dezigZagged]; + } + + // Move block to its place + uint8_t *dest_array; + int dest_index; + int scan; + + if (block < 4) { + dest_array = self->planes_current.y; + scan = self->coded_width - 8; + dest_index = (self->mb_row * self->coded_width + self->mb_col) << 4; + if ((block & 1) != 0) { + dest_index += 8; + } + if ((block & 2) != 0) { + dest_index += self->coded_width << 3; + } + } + else { + dest_array = (block == 4) ? self->planes_current.cb : self->planes_current.cr; + scan = (self->coded_width >> 1) - 8; + dest_index = ((self->mb_row * self->coded_width) << 2) + (self->mb_col << 3); + } + + if (self->macroblock_intra) { + // Overwrite (no prediction) + if (n == 1) { + copy_value_to_destination((self->block_data[0] + 128) >> 8, dest_array, dest_index, scan); + self->block_data[0] = 0; + } + else { + idct(self->block_data); + copy_block_to_destination(self->block_data, dest_array, dest_index, scan); + zero_block_data(self); + } + } + else { + // Add data to the predicted macroblock + if (n == 1) { + add_value_to_destination((self->block_data[0] + 128) >> 8, dest_array, dest_index, scan); + self->block_data[0] = 0; + } + else { + idct(self->block_data); + add_block_to_destination(self->block_data, dest_array, dest_index, scan); + zero_block_data(self); + } + } + + n = 0; +} + + +// ----------------------------------------------------------------------------- +// Private functions + +void zero_block_data(mpeg1_decoder_t *self) { + for (int i = 0; i < 64; i++) { + self->block_data[i] = 0; + } +} + +inline uint8_t clamp_to_uint8(int n) { + return n > 255 + ? 255 + : (n < 0 ? 0 : n); +} + +void copy_block_to_destination(int *block, uint8_t *dest, int index, int scan) { + for (int n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] = clamp_to_uint8(block[n+0]); + dest[index+1] = clamp_to_uint8(block[n+1]); + dest[index+2] = clamp_to_uint8(block[n+2]); + dest[index+3] = clamp_to_uint8(block[n+3]); + dest[index+4] = clamp_to_uint8(block[n+4]); + dest[index+5] = clamp_to_uint8(block[n+5]); + dest[index+6] = clamp_to_uint8(block[n+6]); + dest[index+7] = clamp_to_uint8(block[n+7]); + } +} + +void add_block_to_destination(int *block, uint8_t *dest, int index, int scan) { + for (int n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] = clamp_to_uint8(dest[index+0] + block[n+0]); + dest[index+1] = clamp_to_uint8(dest[index+1] + block[n+1]); + dest[index+2] = clamp_to_uint8(dest[index+2] + block[n+2]); + dest[index+3] = clamp_to_uint8(dest[index+3] + block[n+3]); + dest[index+4] = clamp_to_uint8(dest[index+4] + block[n+4]); + dest[index+5] = clamp_to_uint8(dest[index+5] + block[n+5]); + dest[index+6] = clamp_to_uint8(dest[index+6] + block[n+6]); + dest[index+7] = clamp_to_uint8(dest[index+7] + block[n+7]); + } +} + +void copy_value_to_destination(int value, uint8_t *dest, int index, int scan) { + value = clamp_to_uint8(value); + for (int n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] = value; + dest[index+1] = value; + dest[index+2] = value; + dest[index+3] = value; + dest[index+4] = value; + dest[index+5] = value; + dest[index+6] = value; + dest[index+7] = value; + } +} + +void add_value_to_destination(int value, uint8_t *dest, int index, int scan) { + for (int n = 0; n < 64; n += 8, index += scan+8) { + dest[index+0] = clamp_to_uint8(dest[index+0] + value); + dest[index+1] = clamp_to_uint8(dest[index+1] + value); + dest[index+2] = clamp_to_uint8(dest[index+2] + value); + dest[index+3] = clamp_to_uint8(dest[index+3] + value); + dest[index+4] = clamp_to_uint8(dest[index+4] + value); + dest[index+5] = clamp_to_uint8(dest[index+5] + value); + dest[index+6] = clamp_to_uint8(dest[index+6] + value); + dest[index+7] = clamp_to_uint8(dest[index+7] + value); + } +} + +void idct(int *block) { + // See http://vsr.informatik.tu-chemnitz.de/~jan/MPEG/HTML/IDCT.html + // for more info. + + int + b1, b3, b4, b6, b7, tmp1, tmp2, m0, + x0, x1, x2, x3, x4, y3, y4, y5, y6, y7; + + // Transform columns + for (int i = 0; i < 8; ++i) { + b1 = block[4*8+i]; + b3 = block[2*8+i] + block[6*8+i]; + b4 = block[5*8+i] - block[3*8+i]; + tmp1 = block[1*8+i] + block[7*8+i]; + tmp2 = block[3*8+i] + block[5*8+i]; + b6 = block[1*8+i] - block[7*8+i]; + b7 = tmp1 + tmp2; + m0 = block[0*8+i]; + x4 = ((b6*473 - b4*196 + 128) >> 8) - b7; + x0 = x4 - (((tmp1 - tmp2)*362 + 128) >> 8); + x1 = m0 - b1; + x2 = (((block[2*8+i] - block[6*8+i])*362 + 128) >> 8) - b3; + x3 = m0 + b1; + y3 = x1 + x2; + y4 = x3 + b3; + y5 = x1 - x2; + y6 = x3 - b3; + y7 = -x0 - ((b4*473 + b6*196 + 128) >> 8); + block[0*8+i] = b7 + y4; + block[1*8+i] = x4 + y3; + block[2*8+i] = y5 - x0; + block[3*8+i] = y6 - y7; + block[4*8+i] = y6 + y7; + block[5*8+i] = x0 + y5; + block[6*8+i] = y3 - x4; + block[7*8+i] = y4 - b7; + } + + // Transform rows + for (int i = 0; i < 64; i += 8) { + b1 = block[4+i]; + b3 = block[2+i] + block[6+i]; + b4 = block[5+i] - block[3+i]; + tmp1 = block[1+i] + block[7+i]; + tmp2 = block[3+i] + block[5+i]; + b6 = block[1+i] - block[7+i]; + b7 = tmp1 + tmp2; + m0 = block[0+i]; + x4 = ((b6*473 - b4*196 + 128) >> 8) - b7; + x0 = x4 - (((tmp1 - tmp2)*362 + 128) >> 8); + x1 = m0 - b1; + x2 = (((block[2+i] - block[6+i])*362 + 128) >> 8) - b3; + x3 = m0 + b1; + y3 = x1 + x2; + y4 = x3 + b3; + y5 = x1 - x2; + y6 = x3 - b3; + y7 = -x0 - ((b4*473 + b6*196 + 128) >> 8); + block[0+i] = (b7 + y4 + 128) >> 8; + block[1+i] = (x4 + y3 + 128) >> 8; + block[2+i] = (y5 - x0 + 128) >> 8; + block[3+i] = (y6 - y7 + 128) >> 8; + block[4+i] = (y6 + y7 + 128) >> 8; + block[5+i] = (x0 + y5 + 128) >> 8; + block[6+i] = (y3 - x4 + 128) >> 8; + block[7+i] = (y4 - b7 + 128) >> 8; + } +} + +int read_huffman(bit_buffer_t *bits, const int *code_table) { + int state = 0; + do { + state = code_table[state + bit_buffer_read(bits, 1)]; + } while (state >= 0 && code_table[state] != 0); + return code_table[state+2]; +} diff --git a/public/static/jsmpeg-master/src/wasm/mpeg1.h b/public/static/jsmpeg-master/src/wasm/mpeg1.h new file mode 100644 index 0000000..2f9d8a3 --- /dev/null +++ b/public/static/jsmpeg-master/src/wasm/mpeg1.h @@ -0,0 +1,27 @@ +#ifndef MPEG1_H +#define MPEG1_H + +#include +#include +#include "buffer.h" + +typedef struct mpeg1_decoder_t mpeg1_decoder_t; + +mpeg1_decoder_t *mpeg1_decoder_create(unsigned int buffer_size, bit_buffer_mode_t buffer_mode); +void mpeg1_decoder_destroy(mpeg1_decoder_t *self); +void *mpeg1_decoder_get_write_ptr(mpeg1_decoder_t *self, unsigned int byte_size); +int mpeg1_decoder_get_index(mpeg1_decoder_t *self); +void mpeg1_decoder_set_index(mpeg1_decoder_t *self, unsigned int index); +void mpeg1_decoder_did_write(mpeg1_decoder_t *self, unsigned int byte_size); + +int mpeg1_decoder_has_sequence_header(mpeg1_decoder_t *self); +float mpeg1_decoder_get_frame_rate(mpeg1_decoder_t *self); +int mpeg1_decoder_get_coded_size(mpeg1_decoder_t *self); +int mpeg1_decoder_get_width(mpeg1_decoder_t *self); +int mpeg1_decoder_get_height(mpeg1_decoder_t *self); +void *mpeg1_decoder_get_y_ptr(mpeg1_decoder_t *self); +void *mpeg1_decoder_get_cr_ptr(mpeg1_decoder_t *self); +void *mpeg1_decoder_get_cb_ptr(mpeg1_decoder_t *self); +bool mpeg1_decoder_decode(mpeg1_decoder_t *self); + +#endif diff --git a/public/static/jsmpeg-master/src/webaudio.js b/public/static/jsmpeg-master/src/webaudio.js new file mode 100644 index 0000000..bbdc3af --- /dev/null +++ b/public/static/jsmpeg-master/src/webaudio.js @@ -0,0 +1,145 @@ +JSMpeg.AudioOutput.WebAudio = (function() { "use strict"; + +var WebAudioOut = function(options) { + this.context = WebAudioOut.CachedContext = + WebAudioOut.CachedContext || + new (window.AudioContext || window.webkitAudioContext)(); + + this.gain = this.context.createGain(); + this.destination = this.gain; + + // Keep track of the number of connections to this AudioContext, so we + // can safely close() it when we're the only one connected to it. + this.gain.connect(this.context.destination); + this.context._connections = (this.context._connections || 0) + 1; + + this.startTime = 0; + this.buffer = null; + this.wallclockStartTime = 0; + this.volume = 1; + this.enabled = true; + + this.unlocked = !WebAudioOut.NeedsUnlocking(); + + Object.defineProperty(this, 'enqueuedTime', {get: this.getEnqueuedTime}); +}; + +WebAudioOut.prototype.destroy = function() { + this.gain.disconnect(); + this.context._connections--; + + if (this.context._connections === 0) { + this.context.close(); + WebAudioOut.CachedContext = null; + } +}; + +WebAudioOut.prototype.play = function(sampleRate, left, right) { + if (!this.enabled) { + return; + } + + // If the context is not unlocked yet, we simply advance the start time + // to "fake" actually playing audio. This will keep the video in sync. + if (!this.unlocked) { + var ts = JSMpeg.Now() + if (this.wallclockStartTime < ts) { + this.wallclockStartTime = ts; + } + this.wallclockStartTime += left.length / sampleRate; + return; + } + + + this.gain.gain.value = this.volume; + + var buffer = this.context.createBuffer(2, left.length, sampleRate); + buffer.getChannelData(0).set(left); + buffer.getChannelData(1).set(right); + + var source = this.context.createBufferSource(); + source.buffer = buffer; + source.connect(this.destination); + + var now = this.context.currentTime; + var duration = buffer.duration; + if (this.startTime < now) { + this.startTime = now; + this.wallclockStartTime = JSMpeg.Now(); + } + + source.start(this.startTime); + this.startTime += duration; + this.wallclockStartTime += duration; +}; + +WebAudioOut.prototype.stop = function() { + // Meh; there seems to be no simple way to get a list of currently + // active source nodes from the Audio Context, and maintaining this + // list ourselfs would be a pain, so we just set the gain to 0 + // to cut off all enqueued audio instantly. + this.gain.gain.value = 0; +}; + +WebAudioOut.prototype.getEnqueuedTime = function() { + // The AudioContext.currentTime is only updated every so often, so if we + // want to get exact timing, we need to rely on the system time. + return Math.max(this.wallclockStartTime - JSMpeg.Now(), 0) +}; + +WebAudioOut.prototype.resetEnqueuedTime = function() { + this.startTime = this.context.currentTime; + this.wallclockStartTime = JSMpeg.Now(); +}; + +WebAudioOut.prototype.unlock = function(callback) { + if (this.unlocked) { + if (callback) { + callback(); + } + return; + } + + this.unlockCallback = callback; + + // Create empty buffer and play it + var buffer = this.context.createBuffer(1, 1, 22050); + var source = this.context.createBufferSource(); + source.buffer = buffer; + source.connect(this.destination); + source.start(0); + + setTimeout(this.checkIfUnlocked.bind(this, source, 0), 0); +}; + +WebAudioOut.prototype.checkIfUnlocked = function(source, attempt) { + if ( + source.playbackState === source.PLAYING_STATE || + source.playbackState === source.FINISHED_STATE + ) { + this.unlocked = true; + if (this.unlockCallback) { + this.unlockCallback(); + this.unlockCallback = null; + } + } + else if (attempt < 10) { + // Jeez, what a shit show. Thanks iOS! + setTimeout(this.checkIfUnlocked.bind(this, source, attempt+1), 100); + } +}; + +WebAudioOut.NeedsUnlocking = function() { + return /iPhone|iPad|iPod/i.test(navigator.userAgent); +}; + +WebAudioOut.IsSupported = function() { + return (window.AudioContext || window.webkitAudioContext); +}; + +WebAudioOut.CachedContext = null; + +return WebAudioOut; + +})(); + diff --git a/public/static/jsmpeg-master/src/webgl.js b/public/static/jsmpeg-master/src/webgl.js new file mode 100644 index 0000000..5d94ffd --- /dev/null +++ b/public/static/jsmpeg-master/src/webgl.js @@ -0,0 +1,308 @@ +JSMpeg.Renderer.WebGL = (function(){ "use strict"; + +var WebGLRenderer = function(options) { + if (options.canvas) { + this.canvas = options.canvas; + this.ownsCanvasElement = false; + } + else { + this.canvas = document.createElement('canvas'); + this.ownsCanvasElement = true; + } + this.width = this.canvas.width; + this.height = this.canvas.height; + this.enabled = true; + + this.hasTextureData = {}; + + var contextCreateOptions = { + preserveDrawingBuffer: !!options.preserveDrawingBuffer, + alpha: false, + depth: false, + stencil: false, + antialias: false, + premultipliedAlpha: false + }; + + this.gl = + this.canvas.getContext('webgl', contextCreateOptions) || + this.canvas.getContext('experimental-webgl', contextCreateOptions); + + if (!this.gl) { + throw new Error('Failed to get WebGL Context'); + } + + this.handleContextLostBound = this.handleContextLost.bind(this); + this.handleContextRestoredBound = this.handleContextRestored.bind(this); + + this.canvas.addEventListener('webglcontextlost', this.handleContextLostBound, false); + this.canvas.addEventListener('webglcontextrestored', this.handleContextRestoredBound, false); + + this.initGL(); +}; + +WebGLRenderer.prototype.initGL = function() { + this.hasTextureData = {}; + + var gl = this.gl; + var vertexAttr = null; + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + + // Init buffers + this.vertexBuffer = gl.createBuffer(); + var vertexCoords = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]); + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexCoords, gl.STATIC_DRAW); + + // Setup the main YCrCbToRGBA shader + this.program = this.createProgram( + WebGLRenderer.SHADER.VERTEX_IDENTITY, + WebGLRenderer.SHADER.FRAGMENT_YCRCB_TO_RGBA + ); + vertexAttr = gl.getAttribLocation(this.program, 'vertex'); + gl.enableVertexAttribArray(vertexAttr); + gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0); + + this.textureY = this.createTexture(0, 'textureY'); + this.textureCb = this.createTexture(1, 'textureCb'); + this.textureCr = this.createTexture(2, 'textureCr'); + + + // Setup the loading animation shader + this.loadingProgram = this.createProgram( + WebGLRenderer.SHADER.VERTEX_IDENTITY, + WebGLRenderer.SHADER.FRAGMENT_LOADING + ); + vertexAttr = gl.getAttribLocation(this.loadingProgram, 'vertex'); + gl.enableVertexAttribArray(vertexAttr); + gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0); + + this.shouldCreateUnclampedViews = !this.allowsClampedTextureData(); +}; + +WebGLRenderer.prototype.handleContextLost = function(ev) { + ev.preventDefault(); +}; + +WebGLRenderer.prototype.handleContextRestored = function(ev) { + this.initGL(); +}; + +WebGLRenderer.prototype.destroy = function() { + var gl = this.gl; + + this.deleteTexture(gl.TEXTURE0, this.textureY); + this.deleteTexture(gl.TEXTURE1, this.textureCb); + this.deleteTexture(gl.TEXTURE2, this.textureCr); + + gl.useProgram(null); + gl.deleteProgram(this.program); + gl.deleteProgram(this.loadingProgram); + + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.deleteBuffer(this.vertexBuffer); + + this.canvas.removeEventListener('webglcontextlost', this.handleContextLostBound, false); + this.canvas.removeEventListener('webglcontextrestored', this.handleContextRestoredBound, false); + + if (this.ownsCanvasElement) { + this.canvas.remove(); + } +}; + +WebGLRenderer.prototype.resize = function(width, height) { + this.width = width|0; + this.height = height|0; + + this.canvas.width = this.width; + this.canvas.height = this.height; + + this.gl.useProgram(this.program); + + var codedWidth = ((this.width + 15) >> 4) << 4; + this.gl.viewport(0, 0, codedWidth, this.height); +}; + +WebGLRenderer.prototype.createTexture = function(index, name) { + var gl = this.gl; + var texture = gl.createTexture(); + + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.uniform1i(gl.getUniformLocation(this.program, name), index); + + return texture; +}; + +WebGLRenderer.prototype.createProgram = function(vsh, fsh) { + var gl = this.gl; + var program = gl.createProgram(); + + gl.attachShader(program, this.compileShader(gl.VERTEX_SHADER, vsh)); + gl.attachShader(program, this.compileShader(gl.FRAGMENT_SHADER, fsh)); + gl.linkProgram(program); + gl.useProgram(program); + + return program; +}; + +WebGLRenderer.prototype.compileShader = function(type, source) { + var gl = this.gl; + var shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + throw new Error(gl.getShaderInfoLog(shader)); + } + + return shader; +}; + +WebGLRenderer.prototype.allowsClampedTextureData = function() { + var gl = this.gl; + var texture = gl.createTexture(); + + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.LUMINANCE, 1, 1, 0, + gl.LUMINANCE, gl.UNSIGNED_BYTE, new Uint8ClampedArray([0]) + ); + return (gl.getError() === 0); +}; + +WebGLRenderer.prototype.renderProgress = function(progress) { + var gl = this.gl; + + gl.useProgram(this.loadingProgram); + + var loc = gl.getUniformLocation(this.loadingProgram, 'progress'); + gl.uniform1f(loc, progress); + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); +}; + +WebGLRenderer.prototype.render = function(y, cb, cr, isClampedArray) { + if (!this.enabled) { + return; + } + + var gl = this.gl; + var w = ((this.width + 15) >> 4) << 4, + h = this.height, + w2 = w >> 1, + h2 = h >> 1; + + // In some browsers WebGL doesn't like Uint8ClampedArrays (this is a bug + // and should be fixed soon-ish), so we have to create a Uint8Array view + // for each plane. + if (isClampedArray && this.shouldCreateUnclampedViews) { + y = new Uint8Array(y.buffer), + cb = new Uint8Array(cb.buffer), + cr = new Uint8Array(cr.buffer); + } + + gl.useProgram(this.program); + + this.updateTexture(gl.TEXTURE0, this.textureY, w, h, y); + this.updateTexture(gl.TEXTURE1, this.textureCb, w2, h2, cb); + this.updateTexture(gl.TEXTURE2, this.textureCr, w2, h2, cr); + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); +}; + +WebGLRenderer.prototype.updateTexture = function(unit, texture, w, h, data) { + var gl = this.gl; + gl.activeTexture(unit); + gl.bindTexture(gl.TEXTURE_2D, texture); + + if (this.hasTextureData[unit]) { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.LUMINANCE, gl.UNSIGNED_BYTE, data); + } + else { + this.hasTextureData[unit] = true; + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, + gl.LUMINANCE, gl.UNSIGNED_BYTE, data + ); + } +}; + +WebGLRenderer.prototype.deleteTexture = function(unit, texture) { + var gl = this.gl; + gl.activeTexture(unit); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.deleteTexture(texture); +}; + +WebGLRenderer.IsSupported = function() { + try { + if (!window.WebGLRenderingContext) { + return false; + } + + var canvas = document.createElement('canvas'); + return !!( + canvas.getContext('webgl') || + canvas.getContext('experimental-webgl') + ); + } + catch (err) { + return false; + } +}; + +WebGLRenderer.SHADER = { + FRAGMENT_YCRCB_TO_RGBA: [ + 'precision mediump float;', + 'uniform sampler2D textureY;', + 'uniform sampler2D textureCb;', + 'uniform sampler2D textureCr;', + 'varying vec2 texCoord;', + + 'mat4 rec601 = mat4(', + '1.16438, 0.00000, 1.59603, -0.87079,', + '1.16438, -0.39176, -0.81297, 0.52959,', + '1.16438, 2.01723, 0.00000, -1.08139,', + '0, 0, 0, 1', + ');', + + 'void main() {', + 'float y = texture2D(textureY, texCoord).r;', + 'float cb = texture2D(textureCb, texCoord).r;', + 'float cr = texture2D(textureCr, texCoord).r;', + + 'gl_FragColor = vec4(y, cr, cb, 1.0) * rec601;', + '}' + ].join('\n'), + + FRAGMENT_LOADING: [ + 'precision mediump float;', + 'uniform float progress;', + 'varying vec2 texCoord;', + + 'void main() {', + 'float c = ceil(progress-(1.0-texCoord.y));', + 'gl_FragColor = vec4(c,c,c,1);', + '}' + ].join('\n'), + + VERTEX_IDENTITY: [ + 'attribute vec2 vertex;', + 'varying vec2 texCoord;', + + 'void main() {', + 'texCoord = vertex;', + 'gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);', + '}' + ].join('\n') +}; + +return WebGLRenderer; + +})(); + diff --git a/public/static/jsmpeg-master/src/websocket.js b/public/static/jsmpeg-master/src/websocket.js new file mode 100644 index 0000000..4b356e6 --- /dev/null +++ b/public/static/jsmpeg-master/src/websocket.js @@ -0,0 +1,88 @@ +JSMpeg.Source.WebSocket = (function(){ "use strict"; + +var WSSource = function(url, options) { + this.url = url; + this.options = options; + this.socket = null; + this.streaming = true; + + this.callbacks = {connect: [], data: []}; + this.destination = null; + + this.reconnectInterval = options.reconnectInterval !== undefined + ? options.reconnectInterval + : 5; + this.shouldAttemptReconnect = !!this.reconnectInterval; + + this.completed = false; + this.established = false; + this.progress = 0; + + this.reconnectTimeoutId = 0; + + this.onEstablishedCallback = options.onSourceEstablished; + this.onCompletedCallback = options.onSourceCompleted; // Never used +}; + +WSSource.prototype.connect = function(destination) { + this.destination = destination; +}; + +WSSource.prototype.destroy = function() { + clearTimeout(this.reconnectTimeoutId); + this.shouldAttemptReconnect = false; + this.socket.close(); +}; + +WSSource.prototype.start = function() { + this.shouldAttemptReconnect = !!this.reconnectInterval; + this.progress = 0; + this.established = false; + + if (this.options.protocols) { + this.socket = new WebSocket(this.url, this.options.protocols); + } + else { + this.socket = new WebSocket(this.url); + } + this.socket.binaryType = 'arraybuffer'; + this.socket.onmessage = this.onMessage.bind(this); + this.socket.onopen = this.onOpen.bind(this); + this.socket.onerror = this.onClose.bind(this); + this.socket.onclose = this.onClose.bind(this); +}; + +WSSource.prototype.resume = function(secondsHeadroom) { + // Nothing to do here +}; + +WSSource.prototype.onOpen = function() { + this.progress = 1; +}; + +WSSource.prototype.onClose = function() { + if (this.shouldAttemptReconnect) { + clearTimeout(this.reconnectTimeoutId); + this.reconnectTimeoutId = setTimeout(function(){ + this.start(); + }.bind(this), this.reconnectInterval*1000); + } +}; + +WSSource.prototype.onMessage = function(ev) { + var isFirstChunk = !this.established; + this.established = true; + + if (isFirstChunk && this.onEstablishedCallback) { + this.onEstablishedCallback(this); + } + + if (this.destination) { + this.destination.write(ev.data); + } +}; + +return WSSource; + +})(); + diff --git a/public/static/jsmpeg-master/view-stream.html b/public/static/jsmpeg-master/view-stream.html new file mode 100644 index 0000000..81a737d --- /dev/null +++ b/public/static/jsmpeg-master/view-stream.html @@ -0,0 +1,22 @@ + + + + JSMpeg Stream Client + + + + + + + + + diff --git a/public/static/jsmpeg-master/websocket-relay.js b/public/static/jsmpeg-master/websocket-relay.js new file mode 100644 index 0000000..0b9ab57 --- /dev/null +++ b/public/static/jsmpeg-master/websocket-relay.js @@ -0,0 +1,92 @@ +// Use the websocket-relay to serve a raw MPEG-TS over WebSockets. You can use +// ffmpeg to feed the relay. ffmpeg -> websocket-relay -> browser +// Example: +// node websocket-relay yoursecret 8081 8082 +// ffmpeg -i -f mpegts http://localhost:8081/yoursecret + +var fs = require('fs'), + http = require('http'), + WebSocket = require('ws'); + +if (process.argv.length < 3) { + console.log( + 'Usage: \n' + + 'node websocket-relay.js [ ]' + ); + process.exit(); +} + +var STREAM_SECRET = process.argv[2], + STREAM_PORT = process.argv[3] || 8081, + WEBSOCKET_PORT = process.argv[4] || 8082, + RECORD_STREAM = false; + +// Websocket Server +var socketServer = new WebSocket.Server({port: WEBSOCKET_PORT, perMessageDeflate: false}); +socketServer.connectionCount = 0; +socketServer.on('connection', function(socket, upgradeReq) { + socketServer.connectionCount++; + console.log( + 'New WebSocket Connection: ', + (upgradeReq || socket.upgradeReq).socket.remoteAddress, + (upgradeReq || socket.upgradeReq).headers['user-agent'], + '('+socketServer.connectionCount+' total)' + ); + socket.on('close', function(code, message){ + socketServer.connectionCount--; + console.log( + 'Disconnected WebSocket ('+socketServer.connectionCount+' total)' + ); + }); +}); +socketServer.broadcast = function(data) { + socketServer.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(data); + } + }); +}; + +// HTTP Server to accept incomming MPEG-TS Stream from ffmpeg +var streamServer = http.createServer( function(request, response) { + var params = request.url.substr(1).split('/'); + + if (params[0] !== STREAM_SECRET) { + console.log( + 'Failed Stream Connection: '+ request.socket.remoteAddress + ':' + + request.socket.remotePort + ' - wrong secret.' + ); + response.end(); + } + + response.connection.setTimeout(0); + console.log( + 'Stream Connected: ' + + request.socket.remoteAddress + ':' + + request.socket.remotePort + ); + request.on('data', function(data){ + socketServer.broadcast(data); + if (request.socket.recording) { + request.socket.recording.write(data); + } + }); + request.on('end',function(){ + console.log('close'); + if (request.socket.recording) { + request.socket.recording.close(); + } + }); + + // Record the stream to a local file? + if (RECORD_STREAM) { + var path = 'recordings/' + Date.now() + '.ts'; + request.socket.recording = fs.createWriteStream(path); + } +}) +// Keep the socket open for streaming +streamServer.headersTimeout = 0; +streamServer.listen(STREAM_PORT); + +console.log('Listening for incomming MPEG-TS Stream on http://127.0.0.1:'+STREAM_PORT+'/'); +console.log('Awaiting WebSocket connections on ws://127.0.0.1:'+WEBSOCKET_PORT+'/'); diff --git a/public/xlogo.png b/public/xlogo.png new file mode 100644 index 0000000..beabe3f Binary files /dev/null and b/public/xlogo.png differ diff --git a/serverConf/000-default.conf b/serverConf/000-default.conf new file mode 100644 index 0000000..5e24d84 --- /dev/null +++ b/serverConf/000-default.conf @@ -0,0 +1,79 @@ + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + #ServerName www.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /home/turingvideo/dist/localApp + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + Require all granted + RewriteEngine on + # Don't rewrite files or directories + RewriteCond %{REQUEST_FILENAME} -f [OR] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^ - [L] + # Rewrite everything else to index.html to allow html5 state links + RewriteRule ^ index.html [L] + + + + + + ServerAdmin webmaster@localhost + DocumentRoot /home/turingvideo/dist/dongshi + + + Header set Access-Control-Allow-Origin "*" + Header set Access-Control-Allow-Methods "GET, POST, OPTIONS" + Header set Access-Control-Allow-Headers "Content-Type, Authorization" + Header set Access-Control-Allow-Credentials "true" + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + SSLProxyEngine on + SSLProxyVerify none + SSLProxyCheckPeerCN off + SSLProxyCheckPeerName off + + ProxyPass "/hkapi/" "https://api2.hik-cloud.com/" + ProxyPassReverse "/hkapi/" "https://api2.hik-cloud.com/" + +# AddType text/html .html +# AddType text/html .htm + + +# AllowOverride All + Require all granted + DirectoryIndex index.html +# RewriteEngine on +# RewriteCond %{REQUEST_FILENAME} -f [OR] +# RewriteCond %{REQUEST_FILENAME} -d +# RewriteRule ^ - [L] +# RewriteRule ^ index.html [L] + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/serverConf/bk_default-ssl.conf b/serverConf/bk_default-ssl.conf new file mode 100644 index 0000000..adc56a0 --- /dev/null +++ b/serverConf/bk_default-ssl.conf @@ -0,0 +1,46 @@ + + + ServerAdmin webmaster@localhost + ServerName 192.168.28.32 + + DocumentRoot /home/turingvideo/dist/localApp + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + ProxyPass /api/ "http://127.0.0.1:8000/api/" + ProxyPassReverse /api/ "http://127.0.0.1:8000/api/" + + ProxyPass /ws/ "ws://127.0.0.1:8080/ws/" upgrade=websocket + ProxyPassReverse /ws/ "ws://127.0.0.1:8080/ws/" + +# ProxyPass /media/ "http://127.0.0.1:8000/media/" +# ProxyPassReverse /media/ "http://127.0.0.1:8000/media/" + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + RewriteEngine on + RewriteCond %{REQUEST_FILENAME} -f [OR] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^ - [L] + RewriteRule ^ index.html [L] + + + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + + + + diff --git a/serverConf/dashboard.conf b/serverConf/dashboard.conf new file mode 100644 index 0000000..8ec1238 --- /dev/null +++ b/serverConf/dashboard.conf @@ -0,0 +1,43 @@ +Listen 8081 + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + #ServerName www.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /home/turingvideo/dist/webapp + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + Require all granted + RewriteEngine on + # Don't rewrite files or directories + RewriteCond %{REQUEST_FILENAME} -f [OR] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^ - [L] + # Rewrite everything else to index.html to allow html5 state links + RewriteRule ^ index.html [L] + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/serverConf/default-ssl.conf b/serverConf/default-ssl.conf new file mode 100644 index 0000000..a9cf24c --- /dev/null +++ b/serverConf/default-ssl.conf @@ -0,0 +1,62 @@ + + + ServerAdmin webmaster@localhost + ServerName 192.168.28.32 + + DocumentRoot /home/turingvideo/dist/localApp + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + ProxyPass /api/ "http://127.0.0.1:8000/api/" + ProxyPassReverse /api/ "http://127.0.0.1:8000/api/" + + RewriteEngine On + RewriteCond %{HTTP:Upgrade} websocket [NC] + RewriteCond %{HTTP:Connection} upgrade [NC] + RewriteRule ^/ws/event/(.*)$ ws://127.0.0.1:8080/ws/event/$1 [P,L] + +# RewriteCond %{HTTP:Upgrade} websocket [NC] +# RewriteCond %{HTTP:Connection} upgrade [NC] +# RewriteRule ^/ws/(\d+)(/?.*)$ ws://127.0.0.1:$1$2 [P,L] + +# ProxyPass /ws/ "ws://127.0.0.1:8080/ws/" upgrade=websocket +# ProxyPassReverse /ws/ "ws://127.0.0.1:8080/ws/" + +# ProxyPassMatch ^/ws/(\d+)/.*$ ws://127.0.0.1:$1/ upgrade=websocket +# ProxyPassReverse ^/ws/(\d+)/.*$ ws://127.0.0.1:$1/ + +# ProxyPreserveHost On +# RequestHeader set X-Forwarded-Proto "https" +# RequestHeader set X-Forwarded-Host "192.168.28.32" + +# ProxyPass /media/ "http://127.0.0.1:8000/media/" +# ProxyPassReverse /media/ "http://127.0.0.1:8000/media/" + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + RewriteEngine on + RewriteCond %{REQUEST_FILENAME} -f [OR] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^ - [L] + RewriteRule ^ index.html [L] + + + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + + + + diff --git a/serverConf/superbox.conf b/serverConf/superbox.conf new file mode 100644 index 0000000..72bb5d2 --- /dev/null +++ b/serverConf/superbox.conf @@ -0,0 +1,39 @@ +Define PROJECT_NAME superbox +Define PROJECT_USER turingvideo +Define PROJECT_DIR /home/turingvideo/dist/superbox +Define PYTHON_HOME /home/turingvideo/dist/venv +Define MEDIA_DIR /home/turingvideo/dist/media + +Listen 8000 + + + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/${PROJECT_NAME}-error.log + CustomLog ${APACHE_LOG_DIR}/${PROJECT_NAME}-access.log combined + + Alias /static/ ${PROJECT_DIR}/static/ + + Require all granted + + + Alias /media/ ${MEDIA_DIR}/ + + Require all granted + + + WSGIDaemonProcess ${PROJECT_NAME}-jobs lang='en_US.UTF-8' locale='en_US.UTF-8' user=${PROJECT_USER} processes=1 threads=1 python-home=${PYTHON_HOME} python-path=${PROJECT_DIR} + WSGIImportScript ${PROJECT_DIR}/superbox/wsgi.py process-group=${PROJECT_NAME}-jobs application-group=%{GLOBAL} + + WSGIDaemonProcess ${PROJECT_NAME} lang='en_US.UTF-8' locale='en_US.UTF-8' user=${PROJECT_USER} processes=3 python-home=${PYTHON_HOME} python-path=${PROJECT_DIR} + WSGIScriptAlias / ${PROJECT_DIR}/superbox/wsgi.py + + WSGIProcessGroup ${PROJECT_NAME} + WSGIApplicationGroup %{GLOBAL} + + + + Require all granted + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..8442b11 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/assets/global.css b/src/assets/global.css new file mode 100644 index 0000000..487536f --- /dev/null +++ b/src/assets/global.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + +} \ No newline at end of file diff --git a/src/assets/viewListStyle.css b/src/assets/viewListStyle.css new file mode 100644 index 0000000..f5cb90d --- /dev/null +++ b/src/assets/viewListStyle.css @@ -0,0 +1,217 @@ +.list-view { + display: flex; + justify-content: center; + margin: 0; + height: 100vh; + width: 100vw; + padding: 10vh 10vw 10vh 1vw; + /* background-color: rgb(121, 184, 243); */ + /* background-image: url('/bg01.png'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; */ + position: relative; + color: black; + box-sizing: border-box; +} + +.background-overlay { + position: absolute; + /* 绝对定位 */ + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: url('/bg01.png'); + /* 设置背景图片 */ + background-size: cover; + /* 使背景图覆盖整个区域 */ + background-position: center; + /* 背景图居中显示 */ + background-repeat: no-repeat; + /* 不重复背景图 */ + opacity: 0.8; + /* 设置透明度(0 到 1 之间的值) */ + z-index: 0; + /* 确保在其他内容下方 */ +} + +.container { + display: flex; + flex-direction: column; + width: 80vw; + height: 100%; + gap: 1vh; + position: relative; + /* 为了在背景下显示 */ + z-index: 1; +} + +.header { + height: 6vh; + width: 81vw; + background-color: #333; + color: white; + text-align: center; + line-height: 6vh; +} + +.main-section { + height: 72vh; + display: flex; + flex-direction: row; + gap: 1vw; +} + +.left-section, +.right-section { + display: flex; + flex-direction: column; + width: 22vw; + height: 70vh; + gap: 2vh; + +} + +.top-left, +.middle-left, +.bottom-left, +.top-right, +.middle-right, +.bottom-right { + color: white; + text-align: center; + width: 22vw; + height: 22vh; + background-color: rgba(255, 255, 255, 0.1); + /* border: 3px solid rgba(0, 255, 255, 0.5); */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); +} + +.corner-style { + border: 2px solid rgba(73, 1, 95, 0.5); + position: relative; + + &::before { + position: absolute; + content: ""; + top: 0; + left: 0; + width: 20px; + height: 20px; + border-left: 5px solid rgba(40, 241, 241, 0.986); + border-top: 5px solid rgba(40, 241, 241, 0.986); + } + + &::after { + position: absolute; + content: ""; + top: 0; + right: 0; + width: 20px; + height: 20px; + border-right: 5px solid rgba(40, 241, 241, 0.986); + border-top: 5px solid rgba(40, 241, 241, 0.986); + } + + .hiden { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + + &::before { + position: absolute; + content: ""; + bottom: 0; + left: 0; + width: 20px; + height: 20px; + border-left: 5px solid rgba(40, 241, 241, 0.986); + border-bottom: 5px solid rgba(40, 241, 241, 0.986); + } + + &::after { + position: absolute; + content: ""; + bottom: 0; + right: 0; + width: 20px; + height: 20px; + border-right: 5px solid rgba(40, 241, 241, 0.986); + border-bottom: 5px solid rgba(40, 241, 241, 0.986); + } + } +} + + + +/* .top-left, .top-right { + margin-bottom: 1vh; +} + + +.bottom-left, .bottom-right { + margin-top: 1vh; +} */ + +.center-section { + display: flex; + flex-direction: column; + width: 35vw; + height: 70vh; + gap: 1vh; +} + +.center-top { + height: 47vh; + background-color: #555; + color: white; + display: flex; + flex-direction: column; +} + +.center-top-header { + height: 3vh; + width: 35vw; + text-align: center; + line-height: 3vh; + margin-bottom: 1vh; + background-color: rgba(0, 51, 102, 0.8); + border-radius: 3px; + /* 圆角 */ + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5); + /* 添加阴影 */ +} + +.center-top-grids { + display: grid; + grid-template-columns: 17vw 17vw; + gap: 2vh 1vw; +} + +.grid-item { + width: 17vw; + height: 20vh; + background-color: #777; + color: white; + display: flex; + align-items: center; + justify-content: center; +} + +.center-bottom { + display: flex; + gap: 1vw; + flex-direction: row; +} + +.center-bottom-left, +.center-bottom-right { + width: 17vw; + height: 22vh; + background-color: #444; + color: white; + text-align: center; + /* margin-top: 1vh; */ +} \ No newline at end of file diff --git a/src/bk/CenterBottom告警分布.vue b/src/bk/CenterBottom告警分布.vue new file mode 100644 index 0000000..74aa09c --- /dev/null +++ b/src/bk/CenterBottom告警分布.vue @@ -0,0 +1,408 @@ + + + + + + + + \ No newline at end of file diff --git a/src/bk/CenterTop警戒点位.vue b/src/bk/CenterTop警戒点位.vue new file mode 100644 index 0000000..90412a1 --- /dev/null +++ b/src/bk/CenterTop警戒点位.vue @@ -0,0 +1,347 @@ + + + + + + \ No newline at end of file diff --git a/src/bk/DataStatics新数据统计.vue b/src/bk/DataStatics新数据统计.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/bk/DataStatistics原始版.vue b/src/bk/DataStatistics原始版.vue new file mode 100644 index 0000000..98d6208 --- /dev/null +++ b/src/bk/DataStatistics原始版.vue @@ -0,0 +1,127 @@ + + + + + + + + \ No newline at end of file diff --git a/src/bk/LeftMiddle点位告警数量.vue b/src/bk/LeftMiddle点位告警数量.vue new file mode 100644 index 0000000..36fffa7 --- /dev/null +++ b/src/bk/LeftMiddle点位告警数量.vue @@ -0,0 +1,310 @@ + + + + + \ No newline at end of file diff --git a/src/bk/LeftTop告警数据统计.vue b/src/bk/LeftTop告警数据统计.vue new file mode 100644 index 0000000..d7f6969 --- /dev/null +++ b/src/bk/LeftTop告警数据统计.vue @@ -0,0 +1,366 @@ + + + + + + + \ No newline at end of file diff --git a/src/bk/RightTop类型分布.vue b/src/bk/RightTop类型分布.vue new file mode 100644 index 0000000..8d5a072 --- /dev/null +++ b/src/bk/RightTop类型分布.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/src/bk/StaticData图表简易版.vue b/src/bk/StaticData图表简易版.vue new file mode 100644 index 0000000..d73fa45 --- /dev/null +++ b/src/bk/StaticData图表简易版.vue @@ -0,0 +1,205 @@ + + + + + + \ No newline at end of file diff --git a/src/bk/告警列表(letf-bottom).vue b/src/bk/告警列表(letf-bottom).vue new file mode 100644 index 0000000..00c7d69 --- /dev/null +++ b/src/bk/告警列表(letf-bottom).vue @@ -0,0 +1,424 @@ + + + + + + + \ No newline at end of file diff --git a/src/bk/告警数量分布问题代码.vue b/src/bk/告警数量分布问题代码.vue new file mode 100644 index 0000000..c5f3665 --- /dev/null +++ b/src/bk/告警数量分布问题代码.vue @@ -0,0 +1,410 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/bk/时间段内告警类型数量.vue b/src/bk/时间段内告警类型数量.vue new file mode 100644 index 0000000..019b76b --- /dev/null +++ b/src/bk/时间段内告警类型数量.vue @@ -0,0 +1,111 @@ + + + + + + \ No newline at end of file diff --git a/src/bk/时间段告警数量分布.vue b/src/bk/时间段告警数量分布.vue new file mode 100644 index 0000000..7597a84 --- /dev/null +++ b/src/bk/时间段告警数量分布.vue @@ -0,0 +1,514 @@ + + + + + + \ No newline at end of file diff --git a/src/bk/点播页面原版.vue b/src/bk/点播页面原版.vue new file mode 100644 index 0000000..f069240 --- /dev/null +++ b/src/bk/点播页面原版.vue @@ -0,0 +1,411 @@ + + + + + + \ No newline at end of file diff --git a/src/bk/设置页面点播墙组件代码.vue b/src/bk/设置页面点播墙组件代码.vue new file mode 100644 index 0000000..1bf720a --- /dev/null +++ b/src/bk/设置页面点播墙组件代码.vue @@ -0,0 +1,595 @@ + + + + + + \ No newline at end of file diff --git a/src/components/AlertChart.vue b/src/components/AlertChart.vue new file mode 100644 index 0000000..9140f06 --- /dev/null +++ b/src/components/AlertChart.vue @@ -0,0 +1,264 @@ + + + + + + + + + diff --git a/src/components/Cameras.vue b/src/components/Cameras.vue new file mode 100644 index 0000000..b85432a --- /dev/null +++ b/src/components/Cameras.vue @@ -0,0 +1,337 @@ + + + + + + \ No newline at end of file diff --git a/src/components/Channel.vue b/src/components/Channel.vue new file mode 100644 index 0000000..8b6cbdc --- /dev/null +++ b/src/components/Channel.vue @@ -0,0 +1,617 @@ + + + + + diff --git a/src/components/GlobalDialog.vue b/src/components/GlobalDialog.vue new file mode 100644 index 0000000..d671a59 --- /dev/null +++ b/src/components/GlobalDialog.vue @@ -0,0 +1,220 @@ + + + + + + \ No newline at end of file diff --git a/src/components/Layout.vue b/src/components/Layout.vue new file mode 100644 index 0000000..4161fdf --- /dev/null +++ b/src/components/Layout.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/src/components/Max/CenterBottom.vue b/src/components/Max/CenterBottom.vue new file mode 100644 index 0000000..6698887 --- /dev/null +++ b/src/components/Max/CenterBottom.vue @@ -0,0 +1,287 @@ + + + + + + + + \ No newline at end of file diff --git a/src/components/Max/CenterTop.vue b/src/components/Max/CenterTop.vue new file mode 100644 index 0000000..57bb2bb --- /dev/null +++ b/src/components/Max/CenterTop.vue @@ -0,0 +1,356 @@ + + + + + + \ No newline at end of file diff --git a/src/components/Max/LeftBottom.vue b/src/components/Max/LeftBottom.vue new file mode 100644 index 0000000..ba00c68 --- /dev/null +++ b/src/components/Max/LeftBottom.vue @@ -0,0 +1,451 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/Max/LeftMiddle.vue b/src/components/Max/LeftMiddle.vue new file mode 100644 index 0000000..38a6788 --- /dev/null +++ b/src/components/Max/LeftMiddle.vue @@ -0,0 +1,326 @@ + + + + + \ No newline at end of file diff --git a/src/components/Max/LeftTop.vue b/src/components/Max/LeftTop.vue new file mode 100644 index 0000000..67b6d7c --- /dev/null +++ b/src/components/Max/LeftTop.vue @@ -0,0 +1,403 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/Max/RightTop.vue b/src/components/Max/RightTop.vue new file mode 100644 index 0000000..64c533e --- /dev/null +++ b/src/components/Max/RightTop.vue @@ -0,0 +1,418 @@ + + + + + diff --git a/src/components/Settings.vue b/src/components/Settings.vue new file mode 100644 index 0000000..0deeac5 --- /dev/null +++ b/src/components/Settings.vue @@ -0,0 +1,346 @@ + + + + + diff --git a/src/components/Statistics.vue b/src/components/Statistics.vue new file mode 100644 index 0000000..3a776cb --- /dev/null +++ b/src/components/Statistics.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/src/html/AlertManagement.vue b/src/html/AlertManagement.vue new file mode 100644 index 0000000..17e4cf5 --- /dev/null +++ b/src/html/AlertManagement.vue @@ -0,0 +1,778 @@ + + + + + diff --git a/src/html/CameraRules.vue b/src/html/CameraRules.vue new file mode 100644 index 0000000..5557986 --- /dev/null +++ b/src/html/CameraRules.vue @@ -0,0 +1,365 @@ + + + + + + \ No newline at end of file diff --git a/src/html/DataStatistics.vue b/src/html/DataStatistics.vue new file mode 100644 index 0000000..225e254 --- /dev/null +++ b/src/html/DataStatistics.vue @@ -0,0 +1,702 @@ + + + + + diff --git a/src/html/Home.vue b/src/html/Home.vue new file mode 100644 index 0000000..d6b4342 --- /dev/null +++ b/src/html/Home.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/src/html/LoginView.vue b/src/html/LoginView.vue new file mode 100644 index 0000000..68756f3 --- /dev/null +++ b/src/html/LoginView.vue @@ -0,0 +1,221 @@ + + + + + + diff --git a/src/html/Test.vue b/src/html/Test.vue new file mode 100644 index 0000000..7bbd9ce --- /dev/null +++ b/src/html/Test.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/html/UserList.vue b/src/html/UserList.vue new file mode 100644 index 0000000..37ceb55 --- /dev/null +++ b/src/html/UserList.vue @@ -0,0 +1,321 @@ + + + + + + \ No newline at end of file diff --git a/src/html/ViewList.vue b/src/html/ViewList.vue new file mode 100644 index 0000000..9a5735e --- /dev/null +++ b/src/html/ViewList.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/src/icons/CameraAll.vue b/src/icons/CameraAll.vue new file mode 100644 index 0000000..0df5e15 --- /dev/null +++ b/src/icons/CameraAll.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/icons/CameraOffline.vue b/src/icons/CameraOffline.vue new file mode 100644 index 0000000..21b8a84 --- /dev/null +++ b/src/icons/CameraOffline.vue @@ -0,0 +1,30 @@ + + + \ No newline at end of file diff --git a/src/icons/CameraOnline.vue b/src/icons/CameraOnline.vue new file mode 100644 index 0000000..480847c --- /dev/null +++ b/src/icons/CameraOnline.vue @@ -0,0 +1,30 @@ + + + \ No newline at end of file diff --git a/src/icons/EventAll.vue b/src/icons/EventAll.vue new file mode 100644 index 0000000..fef8021 --- /dev/null +++ b/src/icons/EventAll.vue @@ -0,0 +1,29 @@ + + \ No newline at end of file diff --git a/src/icons/EventClosed.vue b/src/icons/EventClosed.vue new file mode 100644 index 0000000..a6a85b5 --- /dev/null +++ b/src/icons/EventClosed.vue @@ -0,0 +1,29 @@ + + \ No newline at end of file diff --git a/src/icons/EventPending.vue b/src/icons/EventPending.vue new file mode 100644 index 0000000..f641c02 --- /dev/null +++ b/src/icons/EventPending.vue @@ -0,0 +1,29 @@ + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..a18382f --- /dev/null +++ b/src/main.ts @@ -0,0 +1,97 @@ +import { createApp,ref, onBeforeUnmount} from 'vue' +import App from './App.vue' +import router from './router'; +import ElementPlus from 'element-plus'; +import 'element-plus/dist/index.css'; +import zhCn from 'element-plus/es/locale/lang/zh-cn'; +// import axiosInstance from '@/utils/axios-config'; +import axios from 'axios'; +import '@/assets/global.css' +import { useGlobalWebSocket } from './utils/useGlobalWebSocket'; +import { startTokenCheckTimer, stopTokenCheckTimer } from './utils/boxApi'; +import DataVVue3 from '@kjgl77/datav-vue3'; +import '@kjgl77/datav-vue3/dist/style.css'; +import Antd from 'ant-design-vue'; +import 'ant-design-vue/dist/reset.css'; +import { createPinia } from 'pinia'; +import eventBus from './utils/eventBus'; +import { onCrossWindowMessage } from './utils/crossWindowChannel'; + + +const app = createApp(App) +const globalWebSocket = useGlobalWebSocket(); +const pinia = createPinia(); + +app.provide('globalWebSocket', globalWebSocket); +// app.provide('axios', axiosInstance); +app.use(ElementPlus, { locale: zhCn }); +app.use(router); +app.use(DataVVue3); +app.use(Antd); +app.use(pinia) + +// 导航守卫,检查登录状态 +router.beforeEach((to, from, next) => { + const token = localStorage.getItem('alertToken'); + if (to.matched.some(record => record.meta.requiresAuth)) { + if (!token) { + stopTokenCheckTimer(); + next({ + path: '/login', + query: { redirect: to.fullPath } + }); + } else { + startTokenCheckTimer(); + next(); + } + } else { + next(); + } +}); + +// 定义转换函数 +app.config.globalProperties.xToh = (px: number): string => { + const vh = (px / window.innerHeight) * 100; + return `${vh}vh`; +}; + +app.config.globalProperties.xTow = (px: number): string => { + const vw = (px / window.innerWidth) * 100; + return `${vw}vw`; +}; + +app.config.globalProperties.vhToPx = (vh: number): number => { + return (vh / 100) * window.innerHeight; +}; + +app.config.globalProperties.vwToPx = (vw: number): number => { + return (vw / 100) * window.innerWidth; +}; + +// 响应式处理 +const updateDimensions = () => { + app.config.globalProperties.windowHeight = window.innerHeight; + app.config.globalProperties.windowWidth = window.innerWidth; +}; + +window.addEventListener('resize', updateDimensions); +updateDimensions(); // 初始化 + +// 清理事件监听器 +onBeforeUnmount(() => { + window.removeEventListener('resize', updateDimensions); + stopTokenCheckTimer(); +}); + +app.config.globalProperties.$eventBus = eventBus; + +onCrossWindowMessage((msg) => { + if (msg.type === 'showDialog') { + eventBus.emit('showDialog', msg.payload); + } +}); + +// 浏览器通知权限处理(在 useGlobalWebSocket 模块加载时已自动调用) +// 如需用户交互时重新请求,可在 App.vue 中添加处理逻辑 + +app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..53892a2 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,98 @@ +import { createRouter, createWebHashHistory } from 'vue-router' + +import Layout from '@/components/Layout.vue'; +import AlertChart from '@/components/AlertChart.vue'; +import Statistics from '@/components/Statistics.vue'; +import AlertManagement from '@/html/AlertManagement.vue'; +import Test from '@/html/Test.vue'; +import Login from '@/html/LoginView.vue'; +import UserList from '@/html/UserList.vue'; +import Home from '@/html/Home.vue'; +import DataStatistics from '@/html/DataStatistics.vue'; +import Cameras from '@/components/Cameras.vue'; +import Settings from '@/components/Settings.vue'; +import ViewList from '@/html/ViewList.vue'; + +const routes = [ + { + path: '/', + name: 'Layout', + component: Layout, + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'Home', + component: Home, + meta: { requiresAuth: true } + }, + { + path: '/alertManagement', + name: 'AlertManagement', + component: AlertManagement, + meta: { requiresAuth: true } + }, + { + path: '/test', + name: 'Test', + component: Test, + meta: { requiresAuth: true } + }, + { + path: '/alertChart', + name: 'AlertChart', + component: AlertChart, + meta: { requiresAuth: true } + }, + { + path: '/statistics', + name: 'Statistics', + component: Statistics, + meta: { requiresAuth: true } + }, + { + path:'/userList', + name: 'UserList', + component: UserList, + meta: { requiresAuth: true } + }, + { + path:'/dataStatistics', + name: 'DataStatistics', + component: DataStatistics, + meta: { requiresAuth: true } + }, + { + path:'/cameras', + name: 'Cameras', + component: Cameras, + meta: { requiresAuth: true } + }, + { + path:'/settings', + name: 'Settings', + component: Settings, + meta: { requiresAuth: true } + }, + { + path:'/viewList', + name: 'ViewList', + component: ViewList, + meta: { requiresAuth: true } + } + ] + }, + { + path: '/login', + name: 'Login', + component: Login, + meta: { requiresAuth: false } + } +]; + +const router = createRouter({ + history: createWebHashHistory(), + routes +}); + +export default router; diff --git a/src/stores/globalTimerStore.ts b/src/stores/globalTimerStore.ts new file mode 100644 index 0000000..c0510d1 --- /dev/null +++ b/src/stores/globalTimerStore.ts @@ -0,0 +1,59 @@ +import {ref} from 'vue' +import { defineStore } from 'pinia'; + +// 定义回调类型 +type TimerCallback = () => void; + +export const useGlobalTimerStore = defineStore('globalTimer', () => { + const intervalId = ref(null); // 定时器 ID + const refreshIntervalMs = ref(300000); // 默认刷新间隔(5 分钟) + const registeredCallbacks = ref([]); // 注册的回调函数列表 + + // 注册回调 + const registerCallback = (callback: TimerCallback) => { + if (typeof callback === 'function' && !registeredCallbacks.value.includes(callback)) { + registeredCallbacks.value.push(callback); + } + }; + + // 注销回调 + const unregisterCallback = (callback: TimerCallback) => { + const index = registeredCallbacks.value.indexOf(callback); + if (index !== -1) { + registeredCallbacks.value.splice(index, 1); + } + }; + + // 启动定时器 + const startTimer = () => { + if (!intervalId.value) { + intervalId.value = window.setInterval(() => { + registeredCallbacks.value.forEach((callback) => callback()); + }, refreshIntervalMs.value); + } + }; + + // 停止定时器 + const stopTimer = () => { + if (intervalId.value) { + clearInterval(intervalId.value); + intervalId.value = null; + } + }; + + // 设置定时器间隔 + const setRefreshInterval = (interval: number) => { + refreshIntervalMs.value = interval; + stopTimer(); + startTimer(); + }; + + return { + refreshIntervalMs, + registerCallback, + unregisterCallback, + startTimer, + stopTimer, + setRefreshInterval, + }; +}); diff --git a/src/types/CameraData.ts b/src/types/CameraData.ts new file mode 100644 index 0000000..26d41b1 --- /dev/null +++ b/src/types/CameraData.ts @@ -0,0 +1,21 @@ +import type { RuleData } from '@/types/RuleData'; + +export interface CameraData { + id?: number; + name?: string; + // uri?: string; + mode?: "on" | "off"; + // status?: string; + // detect_params?: object; + // default_params?: object; + // should_push?: boolean; + // config_params?: object; + // sampling?: boolean; + // note?: object; + // snapshot?: string; + // remote_id?: number; + // raw_address?: string; + // ip?: string; + // port?: number; + rules?: RuleData[] | {}; +} diff --git a/src/types/RuleData.ts b/src/types/RuleData.ts new file mode 100644 index 0000000..d1b7b91 --- /dev/null +++ b/src/types/RuleData.ts @@ -0,0 +1,18 @@ + +export interface RuleData { + id: number; + camera: number; + name?: string; + // mode?: string; + mode?: 'on' | 'off' | 'schedule'; + // algo?: string; + // params?: object; + // params_base?: string; + // unique_id?: string; + // event_types?: Record; + schedule?: { + type: string; + time_slots?: Array<[number, number]>; + // week_day?: string; + } | {}; +} diff --git a/src/utils/axios-config.ts b/src/utils/axios-config.ts new file mode 100644 index 0000000..2a202cf --- /dev/null +++ b/src/utils/axios-config.ts @@ -0,0 +1,69 @@ +import axios from 'axios'; + +// 从 localStorage 中获取地址和端口 +const rememberedAddress = localStorage.getItem('rememberedAddress') || '127.0.0.1'; +const rememberedPort = localStorage.getItem('rememberedPort') || '8000'; + + +const TOKEN_EXPIRY_MS = 60*1000; +const TOKEN_WARNING_MS = 10*60*1000; + +function isTokenExpired(): boolean { + const loginTime = localStorage.getItem('loginTime'); + if(!loginTime) return true; + return Date.now() - parseInt(loginTime) > TOKEN_EXPIRY_MS; +} + +function getTokenRemainingTime(): number { + const loginTime = localStorage.getItem('loginTime'); + if(!loginTime) return 0; + return parseInt(loginTime) + TOKEN_EXPIRY_MS -Date.now(); +} + +// 动态拼接 baseURL +const baseURL = `http://${rememberedAddress}:${rememberedPort}/api/v1`; + +// 创建 axios 实例 +const axiosInstance = axios.create({ + baseURL: baseURL, // 使用动态生成的 baseURL + timeout: 10000, // 超时时间 + withCredentials: true, // 使用cookie跨域请求 +}); + +// 请求拦截器 +axiosInstance.interceptors.request.use( + config => { + if(isTokenExpired()){ + localStorage.removeItem('alertToken'); + localStorage.removeItem('loginTime'); + window.location.href = '/#/login'; + return Promise.reject(new Error('Token 已过期')) + } + + const token = localStorage.getItem('alertToken'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + error => { + return Promise.reject(error); + } +); + +// 响应拦截器 +axiosInstance.interceptors.response.use( + response => { + return response; + }, + error => { + if (error.response && error.response.status === 401) { + localStorage.removeItem('alertToken'); + localStorage.removeItem('loginTime'); + window.location.href = '/#/login'; + } + return Promise.reject(error); + } +); + +export { axiosInstance, isTokenExpired,getTokenRemainingTime}; diff --git a/src/utils/boxApi.ts b/src/utils/boxApi.ts new file mode 100644 index 0000000..171053e --- /dev/null +++ b/src/utils/boxApi.ts @@ -0,0 +1,741 @@ +import axios from 'axios'; +import { ElMessage } from 'element-plus'; +import type { CameraData } from '@/types/CameraData' +import type { RuleData } from '@/types/RuleData' + +const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; + +function checkTokenExpiry(): boolean { + const token = localStorage.getItem('alertToken'); + if (!token) return false; + const loginTime = localStorage.getItem('loginTime'); + if (!loginTime) return true; + return Date.now() - parseInt(loginTime) > TOKEN_EXPIRY_MS; +} + +function clearTokenAndRedirect(): void { + localStorage.removeItem('alertToken'); + localStorage.removeItem('loginTime'); + ElMessage.error({ + message: '登录已过期,请重新登录', + duration: 10000 + }); + window.location.href = '/#/login'; +} + +class BoxApi { + + private readonly defaultPort: number = 8000; + + private readonly apiLogin: string = "/auth/login"; + private readonly apiLogout: string = "/auth/logout"; + private readonly apiAdduser: string = "/auth/adduser"; + private readonly apiRmuser: string = "/auth/rmuser"; + private readonly apiAllusers: string = "/auth/allusers"; + private readonly apiCameras: string = "/cameras"; + private readonly apiEvents: string = "/events"; + private readonly apiAlgorithms: string = "/algorithms"; + private readonly apiResetUser: string = "/auth/resetuser"; + private readonly apiResetPassword: string = "/auth/reset_password"; + private readonly apiAllCameras: string = "/camera/cameras/get_all"; + private readonly superCamera: string = "/camera/cameras"; + private readonly superRule: string = "/rules"; + private readonly superEvents: string = "/event/events"; + + private normalizeBackendUrls(data: any): any { + // 强制使用 http + 8000 端口 + const baseUrl = `http://${window.location.hostname}:8000`; + + const replaceHost = (url: string | null | undefined): string | null => { + if (!url) return null; + return url + .replace(/https?:\/\/127\.0\.0\.1(?::8000)?/gi, baseUrl) + .replace(/https?:\/\/localhost(?::8000)?/gi, baseUrl); + }; + + // ==================== 情况1: 事件相关数据(包含 results 的是事件列表) ==================== + if (data.results && Array.isArray(data.results)) { + data.results.forEach((item: any) => { + if (item.mediums && Array.isArray(item.mediums)) { + item.mediums = item.mediums.map((medium: any) => ({ + ...medium, + file: replaceHost(medium.file) + })); + } + if (item.camera?.snapshot) { + item.camera.snapshot = replaceHost(item.camera.snapshot); + } + }); + if (data.next) data.next = replaceHost(data.next); + if (data.previous) data.previous = replaceHost(data.previous); + } + + // ==================== 情况2: 单个事件/告警对象(包含 mediums 但没有 results) ==================== + if (data.mediums && Array.isArray(data.mediums)) { + data.mediums = data.mediums.map((medium: any) => ({ + ...medium, + file: replaceHost(medium.file) + })); + } + if (data.snapshot) data.snapshot = replaceHost(data.snapshot); + if (data.file) data.file = replaceHost(data.file); + + // ==================== 情况3: 摄像头列表(数组) ==================== + if (Array.isArray(data)) { + return data.map((camera: any) => { + const newCamera = { ...camera }; + if (newCamera.snapshot) { + newCamera.snapshot = replaceHost(newCamera.snapshot); + } + if (newCamera.camera?.snapshot) { + newCamera.camera.snapshot = replaceHost(newCamera.camera.snapshot); + } + return newCamera; + }); + } + + // ==================== 情况4: 单个摄像头对象 ==================== + if (data && typeof data === 'object' && !Array.isArray(data) && !data.results) { + const newData = { ...data }; + if (newData.snapshot) { + newData.snapshot = replaceHost(newData.snapshot); + } + if (newData.camera?.snapshot) { + newData.camera.snapshot = replaceHost(newData.camera.snapshot); + } + return newData; + } + + return data; + } + + private readonly loginConfig: object = { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + } + + private address: string = ""; + private port: number = this.defaultPort; + private token: string = ""; + + private codemap: Map = new Map(); + private axios: any = null; + + public constructor( + address: string = "", + port: number = 0 + ) { + // 获取本地存储的address和port + // const rememberedAddress = localStorage.getItem('rememberedAddress') || '127.0.0.1'; + // const rememberedPort = localStorage.getItem('rememberedPort') || '8000'; + + // this.setAddress(address || rememberedAddress); + // this.setPort(port || parseInt(rememberedPort)); + + // 动态创建axios实例,基于当前的address和port + this.createAxiosInstance(); + } + + private createAxiosInstance() { + const protocol = window.location.protocol; + this.axios = axios.create({ + // baseURL: `${protocol}//${this.address}:${this.port}/api/v1`, + // baseURL: `https://${this.address}:${this.port}/api/v1`, + baseURL: '/api/v1', + withCredentials: true + }); + + this.axios.interceptors.request.use( + (config: any) => { + if (checkTokenExpiry()) { + clearTokenAndRedirect(); + return Promise.reject(new Error('Token 已过期')); + } + return config; + }, + (error: any) => Promise.reject(error) + ); + + this.axios.interceptors.response.use( + (response: any) => response, + (error: any) => { + if (error.response?.status === 401) { + clearTokenAndRedirect(); + } + return Promise.reject(error); + } + ); + } + + public setAddress(address: string) { + this.address = address === "" ? this._boxAddr() : address; + this.createAxiosInstance(); + } + + + public setPort(port: number) { + this.port = port === 0 ? this.defaultPort : port; + this.createAxiosInstance(); + } + + public setToken(token: string) { + this.token = token; + } + + public async login(username: string, password: string, cookieLess: boolean = false): Promise { + const loginData = { + username: username, + password: password, + cookieless: cookieLess ? "True" : "False" + }; + + try { + const res = await this.axios.post(this.apiLogin, loginData, this.loginConfig) + // console.log(res) + if (res.data.err.ec === 0) { + this.token = res.data.ret.token; + await this.updateCodemap(this.token); + return res.data.ret.token; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async logout(token: string | null = null): Promise { + try { + const res = await this.axios.post(this.apiLogout, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + + public async resetPassword(newPassword: string, token: string | null = null): Promise { + try { + const data = { + new_password: newPassword + }; + + const res = await this.axios.post(this.apiResetPassword, data, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async resetUser(username: string, password: string, email: string, token: string | null = null): Promise { + try { + const data = { + username: username, + password: password, + email: email + }; + + const res = await this.axios.post(this.apiResetUser, data, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async addUser(username: string, password: string, email: string = "", token: string | null = null): Promise { + try { + const data = { + username: username, + password: password, + email: email + }; + + const res = await this.axios.post(this.apiAdduser, data, this._authHeader(token)); + + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw res.data.err; + } + } catch (error: any) { + if (error.response && error.response.data && error.response.data.err) { + throw error.response.data.err; + } else { + throw new Error("网络错误或服务器未响应"); + } + } + } + + public async rmUser(username: string, token: string | null = null): Promise { + try { + const data = { + username: username + }; + + const res = await this.axios.post(this.apiRmuser, data, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getAllUsers(token: string | null = null): Promise { + try { + const res = await this.axios.get(this.apiAllusers, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret.users; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getCamerasByUrl(url: string, token: string | null = null): Promise { + try { + const res = await this.axios.get(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getCameras(limit: number, offset: number, token: string | null = null): Promise { + const url = `${this.apiCameras}?limit=${limit}&offset=${offset}`; + return await this.getCamerasByUrl(url, token); + } + + public async getAllCameras(token: string | null = null): Promise { + const url = `${this.apiAllCameras}`; + try { + const res = await this.axios.get(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return this.normalizeBackendUrls(res.data.ret); + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + + + public async getCameraById(token: string | null = null, cameraId: number): Promise { + const url = `${this.superCamera}/${cameraId}`; + try { + const res = await this.axios.get(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return this.normalizeBackendUrls(res.data.ret); + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async updateCamera(token: string | null = null, cameraId: number, jsonData: CameraData): Promise { + const url = `${this.superCamera}/${cameraId}`; + // const { rules, ...cameraData } = jsonData; + // console.log("接口接收的摄像设置>>>>>>>>>>>>>>", jsonData); + + try { + const newCamera = { + name: jsonData.name, + mode: jsonData.mode, + } + const res = await this.axios.patch(url, newCamera); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.dm); + } + } catch (error) { + throw error; + } + } + + // public async updateCamera( + // token: string | null = null, + // cameraId: number, + // jsonData: CameraData + // ): Promise { + // const url = `${this.superCamera}/${cameraId}`; + + // // 确保 mode 为小写 + // const cameraData = { + // name: jsonData.name, + // mode: jsonData.mode?.toLowerCase() === "on" || jsonData.mode?.toLowerCase() === "off" + // ? jsonData.mode.toLowerCase() + // : undefined, // 非法值转为 undefined + // }; + + // console.log("接口接收的摄像头更新数据:", cameraData); + + // try { + // const res = await this.axios.patch(url, cameraData, this._authHeader(token)); + // if (res.data.err.ec === 0) { + // return res.data.ret; // 返回后端成功结果 + // } else { + // throw new Error(res.data.err.dm); // 抛出后端返回的错误消息 + // } + // } catch (error) { + // console.error("更新摄像头失败:", error); + // throw error; + // } + // } + public async delEvents(token: string | null = null, eventIds: number[]): Promise<{ id: number, success: boolean, message?: string }[]> { + const results: { id: number, success: boolean, message?: string }[] = []; + + for (const eventId of eventIds) { + const url = `${this.superEvents}/${eventId}`; + try { + await this.axios.delete(url, this._authHeader(token)); + results.push({ id: eventId, success: true }); + } catch (error: any) { + results.push({ + id: eventId, + success: false, + message: error.response?.data?.err?.dm || error.message + }); + } + } + + return results; + } + + + + public async updateRule(token: string | null = null, rules: RuleData[]): Promise { + const results: any[] = []; + + for (const rule of rules) { + const url = `${this.superRule}/${rule.id}`; + try { + const cleanedRule = { + ...rule, + schedule: rule.schedule || {}, + }; + // console.log("接口接收的规则设置>>>>>>>>>>>>>>", cleanedRule); + const res = await this.axios.patch(url, cleanedRule, this._authHeader(token)); + if (res.data.err.ec === 0) { + results.push({ id: rule.id, success: true, data: res.data.ret }); + } else { + results.push({ id: rule.id, success: false, message: res.data.err.dm }); + } + } catch (error: any) { + console.error(`更新规则失败: rule.id=${rule.id}`, error); + results.push({ id: rule.id, success: false, message: error.message }); + } + } + + return results; // 返回所有规则更新结果 + } + + + + + + public async startCameraStream(token: string | null = null, cameraId: number): Promise { + const url = `${this.superCamera}/${cameraId}/start_stream`; + try { + const res = await this.axios.post(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async stopCameraStream(token: string | null = null, cameraId: number): Promise { + const url = `${this.superCamera}/${cameraId}/stop_stream`; + try { + const res = await this.axios.post(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.dm); + } + } catch (error) { + throw error; + } + } + + public async getEventsByUrl(url: string, token: string | null = null): Promise { + try { + const res = await this.axios.get(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + const normalizedData = this.normalizeBackendUrls(res.data.ret); + return normalizedData; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + // public async getEvents(limit: number, offset: number, token: string | null = null): Promise { + // const url = `${this.apiEvents}?limit=${limit}&offset=${offset}`; + // return await this.getEventsByUrl(url, token); + // } + + // public async getEvents(token: string | null = null, pageSize: number = 10, currentPage: number = 1): Promise { + // const offset = (currentPage - 1) * pageSize; + + // const url = `${this.apiEvents}?limit=${pageSize}&offset=${offset}`; + + // try { + // const res = await this.axios.get(url, this._authHeader(token)); + // if (res.data.err.ec === 0) { + // return res.data.ret; + // } else { + // throw new Error(res.data.err.msg); + // } + // } catch (error) { + // throw error; + // } + // } + + public async getEvents(token: string | null = null, pageSize: number = 20, currentPage: number = 1): Promise { + // 计算 offset + const offset = (currentPage - 1) * pageSize; + + try { + // 发送请求,携带 limit 和 offset 参数 + const res = await this.axios.get(`${this.apiEvents}?limit=${pageSize}&offset=${offset}`, this._authHeader(token)); + + // 请求成功,返回数据 + if (res.data.err.ec === 0) { + const normalizedData = this.normalizeBackendUrls(res.data.ret); + return { + tableData: normalizedData.results, // 告警数据 + totalItems: normalizedData.count // 总条数 + }; + } else { + // 处理请求失败情况 + throw new Error(res.data.err); + } + } catch (error) { + // 抛出异常以便前端捕获 + throw error; + } + } + + + public async getEventsByParams( + token: string | null = null, + pageSize: number = 20, + currentPage: number = 1, + timeBefore: string | null = null, + timeAfter: string | null = null, + types: string | null = null, + camera_id: number | null = null, + status?: 'pending' | 'closed' | null + ): Promise { + // 计算 offset + const offset = (currentPage - 1) * pageSize; + + // 构建请求的 URL + let url = `${this.apiEvents}?limit=${pageSize}&offset=${offset}`; + + // 如果有时间范围,则添加到 URL 中 + if (timeBefore && timeAfter) { + url += `&time_before=${encodeURIComponent(timeBefore)}&time_after=${encodeURIComponent(timeAfter)}`; + } + + // 如果 types 不为空,则添加到 URL 中 + if (types) { + url += `&types=${encodeURIComponent(types)}`; + } + if (camera_id) { + url += `&camera_id=${camera_id}`; + } + if (status) { + url += `&status=${encodeURIComponent(status)}`; + } + + try { + // 发送 GET 请求 + const res = await this.axios.get(url, this._authHeader(token)); + + // 判断请求是否成功 + if (res.data.err.ec === 0) { + const normalizedData = this.normalizeBackendUrls(res.data.ret); + return { + count: normalizedData.count, + next: normalizedData.next, + previous: normalizedData.previous, + results: normalizedData.results + }; + } else { + // 处理请求失败的情况 + throw new Error(res.data.err.dm); + } + } catch (error) { + // 抛出异常以便调用者处理 + throw error; + } + } + + public async getEventById(id: number, token: string | null = null): Promise { + try { + const url = `${this.superEvents}/retrieves`; + const params = { id }; + const res = await this.axios.get(url, { + ...this._authHeader(token), + params: params + }); + + if (res.data.err.ec === 0 && res.data.ret.objects.length > 0) { + const normalizedData = this.normalizeBackendUrls(res.data.ret.objects[0]); + return normalizedData; + } else { + throw new Error(res.data.err.dm || '未获取到有效数据'); + } + } catch (error) { + console.error(`getEventById error for ID ${id}:`, error); + throw error; + } + } + + + // public async getOneEvent(token: string | null = null): Promise { + // try { + // return await this.getEvents(1, 0, token); + // } catch (error) { + // throw error; + // } + // } + public async getOneEvent(token: string | null = null): Promise { + try { + // 调用 getEvents 方法,设置 pageSize 为 1,currentPage 为 1 + return await this.getEvents(token, 1, 1); + } catch (error) { + throw error; + } + } + + public async setEventStatus(eventId: number, status: string, remark: string | null = null, token: string | null = null): Promise { + const url = `${this.apiEvents}/${eventId}`; + + + const data: { status: string; remark?: string } = { status: status }; + if (remark && remark.trim() !== "") { + data.remark = remark; + } + + try { + const res = await this.axios.patch(url, data, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getAlgorithms(token: string | null = null): Promise { + try { + const res = await this.axios.get(this.apiAlgorithms, this._authHeader(token)) + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async updateCodemap(token: string | null = null): Promise { + try { + this.codemap.clear() + const algorithms = await this.getAlgorithms(token); + algorithms.forEach((algorithm: { code_name: string, name: string }) => { + this.codemap.set(algorithm.code_name, algorithm.name) + }); + return true; + } catch (error) { + return false; + } + } + + public getAlgorithmName(code_name: string): string { + return this.codemap.get(code_name) || code_name; + } + + private _authHeader(token: string | null = null): object { + const accessToken = token || this.token || localStorage.getItem('alertToken') || ""; + return { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${accessToken}`, + // 'X-CSRFToken': this.getCsrfToken() + } + }; + } + private getCsrfToken(): string { + const cookie = document.cookie.split(';').find(cookie => cookie.trim().startsWith('csrftoken=')); + if (cookie) { + return cookie.split('=')[1]; + } + return ''; + } + + + private _boxAddr() { + return window.location.host.replace(/:\d+/, ""); + } + +} + +let tokenCheckTimer: ReturnType | null = null; + +function startTokenCheckTimer(): void { + if (tokenCheckTimer) return; + tokenCheckTimer = setInterval(() => { + if (checkTokenExpiry()) { + clearTokenAndRedirect(); + } + }, 1800000); +} + +function stopTokenCheckTimer(): void { + if (tokenCheckTimer) { + clearInterval(tokenCheckTimer); + tokenCheckTimer = null; + } +} + +export { BoxApi, checkTokenExpiry, clearTokenAndRedirect, startTokenCheckTimer, stopTokenCheckTimer }; diff --git a/src/utils/crossWindowChannel.ts b/src/utils/crossWindowChannel.ts new file mode 100644 index 0000000..581789f --- /dev/null +++ b/src/utils/crossWindowChannel.ts @@ -0,0 +1,38 @@ +const CHANNEL_NAME = 'alert-channel'; + +const currentWindowId = `window-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; + +const channel = new BroadcastChannel(CHANNEL_NAME); + +export interface CrossWindowMessage { + type: 'showDialog'; + payload: any; + timestamp: number; + sourceWindowId: string; +} + +type Handler = (msg: CrossWindowMessage) => void; +const handlers: Set = new Set(); + +channel.onmessage = (event: MessageEvent) => { + if (event.data.sourceWindowId === currentWindowId) return; + handlers.forEach(h => h(event.data)); +}; + +export const broadcast = { + sendDialog: (data: any) => { + channel.postMessage({ + type: 'showDialog', + payload: data, + timestamp: Date.now(), + sourceWindowId: currentWindowId + }); + } +}; + +export const onCrossWindowMessage = (handler: Handler) => { + handlers.add(handler); + return () => handlers.delete(handler); +}; + +export const getCurrentWindowId = () => currentWindowId; diff --git a/src/utils/eventBus.ts b/src/utils/eventBus.ts new file mode 100644 index 0000000..6227fcf --- /dev/null +++ b/src/utils/eventBus.ts @@ -0,0 +1,5 @@ + +import mitt from 'mitt'; +// 创建事件总线实例 +const eventBus = mitt(); +export default eventBus; diff --git a/src/utils/message_channel.js b/src/utils/message_channel.js new file mode 100644 index 0000000..3948074 --- /dev/null +++ b/src/utils/message_channel.js @@ -0,0 +1,127 @@ +import { ref } from 'vue' + +const access_token = ref(null) +const consumerId = ref(null) +const data = ref(null) + +// 计数器 +let consumeMessageCount = 0 + +const getAccessToken = async () => { + try { + const response = await fetch('/api/oauth/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: '88651e249e734a5290b00345961a7458', + client_secret: 'fbd457384ba0423cb5d6b86e4c7d3afc' + }) + }) + if (!response.ok) throw new Error('Network response was not ok') + const result = await response.json() + console.log("access_token:>>>>>>>>>", result) + access_token.value = result.access_token + } catch (error) { + console.error('Error fetching access token:', error) + } +} + +const createConsumer = async () => { + try { + const token = access_token.value + if (!token) throw new Error('Access token not found') + + const response = await fetch('/api/api/v1/mq/consumer/group1', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': `Bearer ${token}` + } + }) + if (!response.ok) throw new Error('Network response was not ok') + const result = await response.json() + console.log("createConsumer:>>>>>>>>>", result.data) + consumerId.value = result.data.consumerId + } catch (error) { + console.error('Error creating consumer:', error) + } +} + +const consumeMessage = async () => { + try { + const token = access_token.value + const cid = consumerId.value + + if (!token || !cid) throw new Error('Token or Consumer ID not found') + + const response = await fetch('/api/api/v1/mq/consumer/messages', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': `Bearer ${token}` + }, + body: new URLSearchParams({ + autoCommit: 'true', + consumerId: cid + }) + }) + if (!response.ok) throw new Error('Network response was not ok') + const result = await response.json() + + // 解析 content 数据 + const parsedData = result.data.map(item => { + const content = JSON.parse(item.content.replace(/\\/g, '')) // 去除反斜杠 + return { + ...item, + content + } + }) + + data.value = parsedData + console.log("consumeMessage:>>>>>>>>>", parsedData) + + consumeMessageCount++ + console.log(`consumeMessage request count: ${consumeMessageCount}`) + + return parsedData + } catch (error) { + console.error('Error consuming message:', error) + return [] + } +} + +// 定时器管理 +let refreshTokenIntervalId = null +let consumeMessageIntervalId = null + +const startAutoRefresh = () => { + // 1小时刷新一次 getAccessToken 和 createConsumer + refreshTokenIntervalId = setInterval(async () => { + await getAccessToken() + await createConsumer() + }, 3600000) // 3600000ms = 1小时 + + // 每25秒触发一次 consumeMessage + consumeMessageIntervalId = setInterval(async () => { + await consumeMessage() + }, 25000) // 25000ms = 25秒 +} + +const stopAutoRefresh = () => { + if (refreshTokenIntervalId) clearInterval(refreshTokenIntervalId) + if (consumeMessageIntervalId) clearInterval(consumeMessageIntervalId) +} + +export { + access_token, + consumerId, + data, + getAccessToken, + createConsumer, + consumeMessage, + startAutoRefresh, + stopAutoRefresh +} diff --git a/src/utils/misc.ts b/src/utils/misc.ts new file mode 100644 index 0000000..7789859 --- /dev/null +++ b/src/utils/misc.ts @@ -0,0 +1,30 @@ +const JWT_TOKEN = 'token'; + +const useGetToken = (failedProc: () => void) => { + const getToken = (): string => { + const token = sessionStorage.getItem(JWT_TOKEN); + if (!token) { + failedProc(); + return "" + } + return token + } + + return getToken +} + +const useSetToken = () => { + const setToken = (token: string) => { + sessionStorage.setItem(JWT_TOKEN, token); + } + return setToken +} + +const useClearToken = () => { + const clearToken = () => { + sessionStorage.removeItem(JWT_TOKEN); + } + return clearToken +} + +export { useGetToken, useSetToken, useClearToken } \ No newline at end of file diff --git a/src/utils/superbox.js b/src/utils/superbox.js new file mode 100644 index 0000000..f61b6b3 --- /dev/null +++ b/src/utils/superbox.js @@ -0,0 +1,167 @@ +import axios from "axios"; + +export const getSuperboxApiConfig = (address) => { + const PORT = 8000; + const BASE_ROUTE = "/api/v1"; + const LOGIN_ROUTE = BASE_ROUTE + "/auth/login"; + const LOGOUT_ROUTE = BASE_ROUTE + "/auth/logout"; + const CAMERA_ROUTE = BASE_ROUTE + "/camera/cameras"; + const EVENTS_ROUTE = BASE_ROUTE + "/event/events"; + const ALGORITHM_ROUTE = BASE_ROUTE + "/algorithms"; + + let addr = address; + if (!addr) { + addr = window.location.host.replace(/:\d+/, ""); + console.log("No address provided, using " + addr); + } + const superboxAddress = "http://" + addr + ":" + PORT.toString(); + + return { + base: superboxAddress + BASE_ROUTE, + login: superboxAddress + LOGIN_ROUTE, + logout: superboxAddress + LOGOUT_ROUTE, + cameras: superboxAddress + CAMERA_ROUTE, + events: superboxAddress + EVENTS_ROUTE, + algorithms: superboxAddress + ALGORITHM_ROUTE, + }; + /*return { + base: "/api/v1", + login: "/api/v1/auth/login", + logout: "/api/v1/auth/logout", + cameras: "/api/v1/camera/cameras", + events: "/api/v1/event/events", + };*/ +}; + +axios.defaults.withCredentials = true; +export const login = (username, password, address = null) => { + return new Promise((resolve, reject) => { + const api = getSuperboxApiConfig(address); + axios + .post( + api.login, + { + username: username, + password: password, + cookieless: false, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ) + .then((res) => { + if (res.data.err.ec === 0) { + resolve(res.data.ret.token); + } + reject(res.data.err); + }) + .catch((err) => { + reject(err); + }); + }); +}; + +export const getCameras = (token, address = null) => { + return new Promise((resolve, reject) => { + const api = getSuperboxApiConfig(address); + axios + .get(api.cameras, { + withCredentials: true, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + if (res.data.err.ec === 0) { + resolve(res.data.ret.results); + } + reject(res.data.err); + }) + .catch((err) => { + reject(err); + }); + }); +}; + +export const getEvents = (token, address = null, pageSize = 20, currentPage = 1) => { + return new Promise((resolve, reject) => { + const api = getSuperboxApiConfig(address); + const offset = (currentPage - 1) * pageSize; + const params = new URLSearchParams({ limit: pageSize, offset }); + const urlWithParams = `${api.events}?${params.toString()}`; + // console.log("urlWithParams>>>>>>>>>>>>>>>>>", urlWithParams); + axios + .get(urlWithParams, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + if (res.data.err.ec === 0) { + resolve({ + tableData: res.data.ret.results, + totalItems: res.data.ret.count + }); + } + reject(res.data.err); + }) + .catch((err) => { + reject(err); + }); + }); +}; + +export const getAlgorithms = (token, address = null) => { + return new Promise((resolve, reject) => { + const api = getSuperboxApiConfig(address); + axios + .get(api.algorithms, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + if (res.data.err.ec === 0) { + resolve(res.data.ret); + } + reject(res.data.err); + }) + .catch((err) => { + reject(err); + }); + }); +}; + +var codeNameMap = {}; + +export const initCodeNameMap = async (token, address = null) => { + try { + const algorithms = await getAlgorithms(token, address); + algorithms.forEach((algorithm) => { + codeNameMap[algorithm.code_name] = algorithm.name; + }); + return true; + } catch (err) { + console.log(err); + return false; + } +}; + +export const codenameTranslate = (codeName) => { + return codeNameMap[codeName]; +}; + +export default { + login, + getCameras, + getEvents, + getSuperboxApiConfig, + initCodeNameMap, + codenameTranslate, + getAlgorithms, +}; diff --git a/src/utils/superboxApi.ts b/src/utils/superboxApi.ts new file mode 100644 index 0000000..4ec17f7 --- /dev/null +++ b/src/utils/superboxApi.ts @@ -0,0 +1,250 @@ +import axios from 'axios'; + +class SuperboxApi { + + private readonly defaultPort: number = 8000; + + private readonly apiLogin: string = "/auth/login"; + private readonly apiLogout: string = "/auth/logout"; + private readonly apiAdduser: string = "/auth/adduser"; + private readonly apiRmuser: string = "/auth/rmuser"; + private readonly apiAllusers: string = "/auth/allusers"; + private readonly apiCameras: string = "/cameras"; + private readonly apiEvents: string = "/events"; + private readonly apiAlgorithms: string = "/algorithms"; + + private readonly loginConfig: object = { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + } + + private address: string = ""; + private port: number = this.defaultPort; + private token: string = ""; + + private codemap: Map = new Map(); + private axios: any = null; + + public constructor( + address: string = "", + port: number = 0 + ) { + this.setAddress(address); + this.setPort(port); + this.axios = axios.create({ + baseURL: `http://${this.address}:${this.port}/api/v1`, + withCredentials: true + }) + } + + public setAddress(address: string) { + this.address = address === "" ? this._boxAddr() : address; + } + + public setPort(port: number) { + this.port = port === 0 ? this.defaultPort : port; + } + + public setToken(token: string) { + this.token = token; + } + + public async login(username: string, password: string, cookieLess: boolean = false): Promise { + const loginData = { + username: username, + password: password, + cookieless: cookieLess ? "true" : "false" // 这里会根据 cookieLess 的值决定是 "true" 还是 "false" + }; + + try { + const res = await this.axios.post(this.apiLogin, loginData, this.loginConfig) + if (res.data.err.ec === 0) { + this.token = res.data.ret.token; + await this.updateCodemap(this.token); + return res.data.ret.token; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async logout(token: string | null = null): Promise { + try { + const res = await this.axios.post(this.apiLogout, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async addUser(username: string, password: string, email: string="", token: string | null = null): Promise { + try { + const data = { + username: username, + password: password, + email: email + }; + + const res = await this.axios.post(this.apiAdduser, data, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async rmUser(username: string, token: string | null = null): Promise { + try { + const data = { + username: username + }; + + const res = await this.axios.post(this.apiRmuser, data, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getAllUsers(token: string | null = null): Promise { + try { + const res = await this.axios.get(this.apiAllusers, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret.users; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getCamerasByUrl(url: string, token: string | null = null): Promise { + try { + const res = await this.axios.get(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getCameras(limit: number, offset: number, token: string | null = null): Promise { + const url = `${this.apiCameras}?limit=${limit}&offset=${offset}`; + return await this.getCamerasByUrl(url, token); + } + + public async getEventsByUrl(url: string, token: string | null = null): Promise { + try { + const res = await this.axios.get(url, this._authHeader(token)); + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getEvents(limit: number, offset: number, token: string | null = null): Promise { + const url = `${this.apiEvents}?limit=${limit}&offset=${offset}`; + return await this.getEventsByUrl(url, token); + } + + public async getOneEvent(token: string | null = null): Promise { + try { + return await this.getEvents(1, 0, token); + } catch (error) { + throw error; + } + } + + + public async setEventStatus(eventId: number, status: string, remark: string | null = null, token: string | null = null): Promise { + const url = `${this.apiEvents}/${eventId}`; + const newRemark = remark ? remark : ""; + + const data = { + status: status, + remark: newRemark + }; + + try { + const res = await this.axios.patch(url, data, this._authHeader(token)) + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async getAlgorithms(token: string | null = null): Promise { + try { + const res = await this.axios.get(this.apiAlgorithms, this._authHeader(token)) + if (res.data.err.ec === 0) { + return res.data.ret; + } else { + throw new Error(res.data.err.msg); + } + } catch (error) { + throw error; + } + } + + public async updateCodemap(token: string | null = null): Promise { + try { + this.codemap.clear() + const algorithms = await this.getAlgorithms(token); + algorithms.forEach((algorithm: { code_name: string, name: string }) => { + this.codemap.set(algorithm.code_name, algorithm.name) + }); + return true; + } catch (error) { + return false; + } + } + + public getAlgorithmName(code_name: string): string { + return this.codemap.get(code_name) || code_name; + } + + private _authHeader(token: string | null = null): object { + const access_token = token === "" ? this.token : token; + return { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${access_token}`, + } + } + } + + private _boxAddr() { + return window.location.host.replace(/:\d+/, ""); + } + +} + +export { SuperboxApi }; diff --git a/src/utils/useGlobalWebSocket.ts b/src/utils/useGlobalWebSocket.ts new file mode 100644 index 0000000..329ff3f --- /dev/null +++ b/src/utils/useGlobalWebSocket.ts @@ -0,0 +1,381 @@ +import { ref, reactive } from 'vue'; +import { ElMessage, ElNotification } from 'element-plus'; +import eventBus from '@/utils/eventBus'; +import dayjs from 'dayjs'; +import { BoxApi } from '@/utils/boxApi'; +import { broadcast } from './crossWindowChannel'; +const websocket = ref(null); // WebSocket 实例 +const isWebSocketConnected = ref(false); // WebSocket 连接状态 +let heartbeatInterval: number | null = null; // 心跳定时器 +const formatDateTime = (isoString: string): string => dayjs(isoString).format('YYYY-MM-DD HH:mm:ss'); + +const apiInstance = new BoxApi(); +const algorithmMap = ref(new Map()); + +const maxVisibleNotifications = 2; // 最多同时显示的通知数量 +const visibleNotifications: Map = new Map(); // 当前显示的通知 +const notificationQueue: any[] = []; // 等待显示的通知队列 + +declare global { + interface Window { + webkitAudioContext?: typeof AudioContext; + } +} +// let notificationSoundParams = { volume: 0.5, frequency: 440 }; +// const setNotificationSoundParams = (params: { volume: number; frequency: number }) => { +// notificationSoundParams = params; +// }; + +const notificationSoundParams = reactive({ + volume: 0.5, + frequency: 440, + isSoundEnabled: false, +}); + +const setNotificationSoundParams = (params: { volume?: number; frequency?: number; isSoundEnabled?: boolean }) => { + Object.assign(notificationSoundParams, params); + + // 更新 localStorage + if (params.volume !== undefined) localStorage.setItem('volume', String(notificationSoundParams.volume)); + if (params.frequency !== undefined) localStorage.setItem('frequency', String(notificationSoundParams.frequency)); + if (params.isSoundEnabled !== undefined) localStorage.setItem('isSoundEnabled', String(notificationSoundParams.isSoundEnabled)); +}; + +// 初始化:从 localStorage 加载参数 +const initializeNotificationSoundParams = () => { + notificationSoundParams.volume = parseFloat(localStorage.getItem('volume') || '0.5'); + notificationSoundParams.frequency = parseFloat(localStorage.getItem('frequency') || '440'); + notificationSoundParams.isSoundEnabled = localStorage.getItem('isSoundEnabled') === 'true'; +}; +initializeNotificationSoundParams(); + +// 加载算法映射表 +const loadAlgorithms = async () => { + const token = localStorage.getItem('alertToken'); + if (token) { + const algorithms = await apiInstance.getAlgorithms(token); + algorithmMap.value = new Map( + algorithms.map((algo: { code_name: string; name: string }) => [algo.code_name, algo.name]) + ); + } else { + console.error('Token 未找到,请登录'); + } +}; + + +// 连接 WebSocket +const connectWebSocket = async () => { + // 防止重复连接:如果已有连接,先关闭 + if (websocket.value) { + if (websocket.value.readyState === WebSocket.OPEN || websocket.value.readyState === WebSocket.CONNECTING) { + return; // 已有连接,无需重复创建 + } + websocket.value.close(); + websocket.value = null; + } + const rememberedAddress = localStorage.getItem('rememberedAddress'); + if (rememberedAddress) { + // websocket.value = new WebSocket(`ws://172.19.7.9:8080/ws/event`); + // websocket.value = new WebSocket(`ws://192.168.28.32:8080/ws/event`); + // const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + // const wsHost = rememberedAddress; + // const wsHost = "192.168.28.32"; + // const wsPort = localStorage.getItem('wsPort') || '8080'; + // websocket.value = new WebSocket(`${wsProtocol}//${wsHost}:${wsPort}/ws/event`); + // websocket.value = new WebSocket('/ws/event'); + if(window.location.protocol === 'https'){ + websocket.value = new WebSocket('/ws/event'); + }else{ + websocket.value = new WebSocket(`ws://${rememberedAddress}:8080/ws/event`); + } + + websocket.value.onopen = () => { + ElMessage.success('全局 WebSocket 连接成功'); + isWebSocketConnected.value = true; + + }; + websocket.value.onmessage = (event) => { + const data = JSON.parse(event.data); // 解析收到的数据 + + const isInteractive = localStorage.getItem('isInteractivePopupEnabled') === 'true'; + const isResponsive = localStorage.getItem('isResponsivePopupEnabled') === 'true'; + if (!isInteractive && !isResponsive) { + return; // 弹窗关闭,直接返回 + } + + let num = 0; + num += 1; + // console.log(`${new Date().toISOString()}收到新的第${num}弹窗信息:`, data); + playNotificationSound(); + handlePopupNotification(data); // 根据模式处理弹窗通知 + }; + await loadAlgorithms(); // 每次连接都确保加载算法映射表 + websocket.value.onclose = handleClose; // 处理连接关闭 + websocket.value.onerror = handleError; // 处理连接错误 + } else { + ElMessage.error('主机地址获取失败,请重新登录'); + } +}; + + + +const playNotificationSound = () => { + // if (!isWebSocketConnected.value) return; + if (!notificationSoundParams.isSoundEnabled) return; + try { + const audioContext = new (window.AudioContext || window.webkitAudioContext!)(); + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + + oscillator.type = 'triangle'; + oscillator.frequency.setValueAtTime(notificationSoundParams.frequency, audioContext.currentTime); + gainNode.gain.setValueAtTime(notificationSoundParams.volume, audioContext.currentTime); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillator.start(); + setTimeout(() => { + oscillator.stop(); + audioContext.close(); + }, 350); // 持续播放 200 毫秒 + } catch (error) { + console.error('播放提示音时出错:', error); + } +}; + +// 关闭 WebSocket +const closeWebSocket = () => { + if (websocket.value) { + websocket.value.close(); + websocket.value = null; + ElMessage.info('全局 WebSocket 已关闭'); + stopHeartbeat(); // 停止心跳 + isWebSocketConnected.value = false; + } +}; + +// 启动心跳 +const startHeartbeat = () => { + if (heartbeatInterval) return; + heartbeatInterval = window.setInterval(() => { + if (websocket.value && websocket.value.readyState === WebSocket.OPEN) { + websocket.value.send('ping'); // 发送心跳数据 + } + }, 5000); +}; + +// 停止心跳 +const stopHeartbeat = () => { + if (heartbeatInterval) { + clearInterval(heartbeatInterval); + heartbeatInterval = null; + } +}; + +// 处理连接关闭 +const handleClose = () => { + ElMessage.warning('WebSocket 连接已关闭'); + isWebSocketConnected.value = false; + stopHeartbeat(); // 停止心跳 +}; + +// 处理连接错误 +const handleError = () => { + ElMessage.error('WebSocket 连接出错'); + isWebSocketConnected.value = false; + stopHeartbeat(); +}; + +// 显示通知,返回 notification 实例以便手动关闭 +const showNotification = (notification: any): any => { + const id = `${Date.now()}-${Math.random()}`; // 唯一 ID + notification.id = id; + + if (visibleNotifications.size < maxVisibleNotifications) { + // 如果未达到最大显示数量,直接显示 + const elNotification = ElNotification({ + ...notification, + onClose: () => { + // 从显示列表中移除 + visibleNotifications.delete(id); + + // 显示下一条队列中的通知 + if (notificationQueue.length > 0) { + showNotification(notificationQueue.shift()); + } + + // 调用用户定义的关闭回调(如果有) + notification.onClose?.(); + }, + }); + + visibleNotifications.set(id, elNotification); + return elNotification; + } else { + // 如果达到最大显示数量,存入队列 + notificationQueue.push(notification); + return null; + } +}; + +const loadNextNotification = () => { + // 清空当前所有显示的通知 + visibleNotifications.forEach((notif) => notif.close()); + visibleNotifications.clear(); + + // 从队列中加载下一条通知 + if (notificationQueue.length > 0) { + const nextNotification = notificationQueue.shift(); + if (nextNotification) { + showNotification(nextNotification); + } + } else { + // console.log('通知队列为空'); + } +}; +// 手动清空所有通知 +const clearAllNotifications = () => { + visibleNotifications.forEach((notif) => notif.close()); + visibleNotifications.clear(); + notificationQueue.length = 0; +}; + + +const seenNotificationIds = new Set(); +let lastNotificationTime = 0; +const NOTIFICATION_COOLDOWN = 1000; // 通知冷却时间,防止过快 + +const sendBrowserNotification = (data: any) => { + // 防止同一告警重复发送(冷却时间内) + const now = Date.now(); + if (now - lastNotificationTime < NOTIFICATION_COOLDOWN) { + // console.log('[Notification] 冷却中,跳过'); + return; + } + lastNotificationTime = now; + + console.log('[Notification] 尝试发送浏览器通知', data); + console.log('[Notification] 权限状态:', Notification.permission); + + if (!('Notification' in window)) { + console.log('[Notification] 浏览器不支持 Notification API'); + return; + } + + if (Notification.permission === 'granted') { + console.log('[Notification] 权限已授予,发送通知'); + const cameraName = data.camera?.name || '未知点位'; + const alertType = algorithmMap.value.get(data.types) || data.types || '未知类型'; + console.log('[Notification] 告警信息:', cameraName, alertType); + + try { + // 使用告警ID作为tag,防止通知被替换 + const notification = new Notification('告警通知', { + body: `点位: ${cameraName}\n类型: ${alertType}`, + icon: '/favicon.ico', + tag: `alert-${data.id}`, + requireInteraction: true + }); + + notification.onclick = () => { + window.focus(); + notification.close(); + eventBus.emit('showDialog', data); + }; + console.log('[Notification] 通知发送成功'); + } catch (e) { + console.error('[Notification] 通知发送失败:', e); + } + } else if (Notification.permission !== 'denied') { + console.log('[Notification] 请求通知权限'); + Notification.requestPermission().then(permission => { + console.log('[Notification] 权限请求结果:', permission); + if (permission === 'granted') { + sendBrowserNotification(data); + } + }); + } else { + console.log('[Notification] 通知权限被拒绝'); + } +}; + +// 初始化请求通知权限 +const initNotificationPermission = () => { + if ('Notification' in window && Notification.permission === 'default') { + Notification.requestPermission().then(permission => { + console.log('[Notification] 初始权限请求结果:', permission); + }); + } +}; +initNotificationPermission(); + +// 根据弹窗模式处理通知 +const handlePopupNotification = (data: any) => { + const isInteractive = localStorage.getItem('isInteractivePopupEnabled') === 'true'; // 是否为交互式弹窗 + const isResponsive = localStorage.getItem('isResponsivePopupEnabled') === 'true'; // 是否为响应式弹窗 + + if (seenNotificationIds.has(data.id)) return; + seenNotificationIds.add(data.id); + setTimeout(() => seenNotificationIds.delete(data.id), 30000); + + sendBrowserNotification(data); + + if (isResponsive) { + // 响应式模式:直接显示对话框 + // console.log('handlePopupNotification triggered:', data); + eventBus.emit('showDialog', data); + broadcast.sendDialog(data); + } else if (isInteractive) { + + const formattedTime = formatDateTime(data.started_at); + const notificationInstance = showNotification({ + title: '新告警', + message: ` +
+

告警编号:${data.id || '未知'}

+

告警点位:${data.camera?.name || '未知'}

+

告警类型:${algorithmMap.value.get(data.types) || '未知'}

+

告警时间:${formattedTime || '未知'}

+
+ `, + dangerouslyUseHTMLString: true, + duration: 10000, + customClass: 'custom-notification', + position: 'bottom-right', + type: 'info', + onClick: () => { + notificationInstance?.close(); + eventBus.emit('showDialog', data); + broadcast.sendDialog(data); + } + }); + } +}; + +// 导出类型和方法 +export interface GlobalWebSocket { + connectWebSocket: () => void; // 连接 WebSocket + closeWebSocket: () => void; // 关闭 WebSocket + isWebSocketConnected: typeof isWebSocketConnected; // WebSocket 连接状态 + loadNextNotification: () => void; // 加载下一条通知 + clearAllNotifications: () => void; // 清空所有通知 + setNotificationSoundParams: (params: { volume?: number; frequency?: number; isSoundEnabled?: boolean }) => void; + notificationSoundParams: { + volume: number; + frequency: number; + isSoundEnabled: boolean; + }; +} + +export const useGlobalWebSocket = (): GlobalWebSocket => ({ + connectWebSocket, + closeWebSocket, + isWebSocketConnected, + loadNextNotification, + clearAllNotifications, + notificationSoundParams, + setNotificationSoundParams, +}); + +export { sendBrowserNotification, initNotificationPermission }; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..e14c754 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7c6c941 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + } + + +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6a02633 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,41 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + server:{ + // host:'192.168.28.44', + // host:'192.168.28.44', + host: '127.0.0.1', + port: 8001, + open:true, + cors: true, + // proxy: { + // '/api': "192.168.28.44:8000" + // } + proxy: { + '/api': { + target: 'http://192.168.28.32:8000', + changeOrigin: true, + }, + '/media': { + target: 'http://192.168.28.32:8000', + changeOrigin: true, + }, + '/ws': { + target: 'ws://192.168.28.32:8080', + ws: true, + } + } + } +}) diff --git a/vite.config.ts.timestamp-1737683780033-5ed8b0d47952e.mjs b/vite.config.ts.timestamp-1737683780033-5ed8b0d47952e.mjs new file mode 100644 index 0000000..00d62ab --- /dev/null +++ b/vite.config.ts.timestamp-1737683780033-5ed8b0d47952e.mjs @@ -0,0 +1,37 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///D:/java/demo/turing/alarm/local_alert/node_modules/vite/dist/node/index.js"; +import vue from "file:///D:/java/demo/turing/alarm/local_alert/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +var __vite_injected_original_import_meta_url = "file:///D:/java/demo/turing/alarm/local_alert/vite.config.ts"; +var vite_config_default = defineConfig({ + plugins: [ + vue() + ], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url)) + } + }, + server: { + // host:'192.168.28.44', + // host:'192.168.28.44', + host: "127.0.0.1", + port: 5173, + open: true, + cors: true + // proxy: { + // '/api': "192.168.28.44:8000" + // } + // proxy: { + // '/api': { + // target: 'http://192.168.28.33:8000', + // changeOrigin: true, + // rewrite: path => path.replace('^/api/', '') + // } + // } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxqYXZhXFxcXGRlbW9cXFxcdHVyaW5nXFxcXGFsYXJtXFxcXGxvY2FsX2FsZXJ0XCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJEOlxcXFxqYXZhXFxcXGRlbW9cXFxcdHVyaW5nXFxcXGFsYXJtXFxcXGxvY2FsX2FsZXJ0XFxcXHZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9EOi9qYXZhL2RlbW8vdHVyaW5nL2FsYXJtL2xvY2FsX2FsZXJ0L3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgXSxcclxuICByZXNvbHZlOiB7XHJcbiAgICBhbGlhczoge1xyXG4gICAgICAnQCc6IGZpbGVVUkxUb1BhdGgobmV3IFVSTCgnLi9zcmMnLCBpbXBvcnQubWV0YS51cmwpKVxyXG4gICAgfVxyXG4gIH0sXHJcbiAgc2VydmVyOntcclxuICAgIC8vIGhvc3Q6JzE5Mi4xNjguMjguNDQnLFxyXG4gICAgLy8gaG9zdDonMTkyLjE2OC4yOC40NCcsXHJcbiAgICBob3N0OiAnMTI3LjAuMC4xJyxcclxuICAgIHBvcnQ6IDUxNzMsXHJcbiAgICBvcGVuOnRydWUsXHJcbiAgICBjb3JzOiB0cnVlLFxyXG4gICAgLy8gcHJveHk6IHtcclxuICAgIC8vICAgJy9hcGknOiBcIjE5Mi4xNjguMjguNDQ6ODAwMFwiXHJcbiAgICAvLyB9XHJcbiAgICAvLyBwcm94eToge1xyXG4gICAgLy8gICAnL2FwaSc6IHtcclxuICAgIC8vICAgICB0YXJnZXQ6ICdodHRwOi8vMTkyLjE2OC4yOC4zMzo4MDAwJywgXHJcbiAgICAvLyAgICAgY2hhbmdlT3JpZ2luOiB0cnVlLCAgXHJcbiAgICAvLyAgICAgcmV3cml0ZTogcGF0aCA9PiBwYXRoLnJlcGxhY2UoJ14vYXBpLycsICcnKVxyXG4gICAgLy8gICB9XHJcbiAgICAvLyB9XHJcbiAgfVxyXG59KVxyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQTZTLFNBQVMsZUFBZSxXQUFXO0FBRWhWLFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUg2SyxJQUFNLDJDQUEyQztBQU05TyxJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxJQUFJO0FBQUEsRUFDTjtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ1AsT0FBTztBQUFBLE1BQ0wsS0FBSyxjQUFjLElBQUksSUFBSSxTQUFTLHdDQUFlLENBQUM7QUFBQSxJQUN0RDtBQUFBLEVBQ0Y7QUFBQSxFQUNBLFFBQU87QUFBQTtBQUFBO0FBQUEsSUFHTCxNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixNQUFLO0FBQUEsSUFDTCxNQUFNO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVdSO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K diff --git a/接口文档.md b/接口文档.md new file mode 100644 index 0000000..7c129d5 --- /dev/null +++ b/接口文档.md @@ -0,0 +1,1186 @@ +# 告警管理系统 API 接口文档(Django 版) + +> 基础路径:`/api/v1` +> 服务器地址:`http://192.168.28.32:8000` +> 认证方式:Bearer Token(24h 有效期) +> 统一响应信封:`{ "err": { "ec": 0, "dm": "ok" }, "ret": }` + +--- + +## 目录 + +1. [认证接口](#1-认证接口) +2. [事件(告警)接口](#2-事件告警接口) +3. [摄像头接口](#3-摄像头接口) +4. [规则接口](#4-规则接口) +5. [算法接口](#5-算法接口) +6. [WebSocket 接口](#6-websocket-接口) +7. [通用响应格式](#7-通用响应格式) +8. [认证与 Token 管理](#8-认证与-token-管理) +9. [前端数据转换说明](#9-前端数据转换说明) +10. [完整调用示例](#10-完整调用示例) +11. [附录:前端 API 方法速查表](#11-附录前端-api-方法速查表) + +--- + +## 1. 认证接口 + +--- + +### 1.1 登录 + +- **方法**:`POST` +- **路径**:`/auth/login` +- **描述**:用户登录,获取 Token +- **鉴权**:无(公开) + +**请求头**: +```http +Content-Type: application/json +Accept: application/json +``` + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `username` | `string` | 是 | 用户名 | +| `password` | `string` | 是 | 密码 | +| `cookieless` | `string` | 是 | `"True"` 或 `"False"` | + +```json +{ + "username": "admin", + "password": "123456", + "cookieless": "False" +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { + "token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +| 返回字段 | 类型 | 说明 | +|---------|------|------| +| `ret.token` | `string` | JWT Token,有效期 24h | + +**失败响应**: +```json +{ + "err": { "ec": 1001, "dm": "用户名或密码错误" } +} +``` + +--- + +### 1.2 登出 + +- **方法**:`POST` +- **路径**:`/auth/logout` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**请求体**:无 + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": {} +} +``` + +--- + +### 1.3 添加用户 + +- **方法**:`POST` +- **路径**:`/auth/adduser` +- **鉴权**:Bearer Token(需管理员权限) + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `username` | `string` | 是 | 用户名 | +| `password` | `string` | 是 | 密码 | +| `email` | `string` | 否 | 邮箱,默认为空 | + +```json +{ + "username": "operator1", + "password": "pass123456", + "email": "" +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": {} +} +``` + +--- + +### 1.4 删除用户 + +- **方法**:`POST` +- **路径**:`/auth/rmuser` +- **鉴权**:Bearer Token(需管理员权限) + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `username` | `string` | 是 | 要删除的用户名 | + +```json +{ + "username": "operator1" +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": {} +} +``` + +--- + +### 1.5 获取所有用户 + +- **方法**:`GET` +- **路径**:`/auth/allusers` +- **鉴权**:Bearer Token(需管理员权限) + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**请求参数**:无 + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { + "users": [ + { + "username": "admin", + "email": "" + }, + { + "username": "operator1", + "email": "op1@example.com" + } + ] + } +} +``` + +| 返回字段 | 类型 | 说明 | +|---------|------|------| +| `ret.users` | `array` | 用户列表 | +| `ret.users[].username` | `string` | 用户名 | +| `ret.users[].email` | `string` | 邮箱 | + +--- + +### 1.6 重置用户信息 + +- **方法**:`POST` +- **路径**:`/auth/resetuser` +- **鉴权**:Bearer Token(需管理员权限) + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `username` | `string` | 是 | 用户名 | +| `password` | `string` | 是 | 新密码 | +| `email` | `string` | 是 | 邮箱 | + +```json +{ + "username": "operator1", + "password": "newpass123", + "email": "op1@example.com" +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": {} +} +``` + + +--- + +## 2. 事件(告警)接口 + +--- + +### 2.1 获取事件列表(分页) + +- **方法**:`GET` +- **路径**:`/events` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**查询参数(Query)**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `limit` | `number` | 否 | `20` | 每页条数 | +| `offset` | `number` | 否 | `0` | 偏移量 | + +**请求示例**: +``` +GET /api/v1/events?limit=20&offset=0 +Authorization: Bearer +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { + "count": 1617, + "next": "http://192.168.28.32:8000/api/v1/events?limit=20&offset=20", + "previous": null, + "results": [ + { + "id": 90908, + "camera_id": 1, + "started_at": "2026-04-01T09:30:00Z", + "ended_at": "2026-04-01T09:30:10Z", + "mediums": [ + { + "id": 109656, + "name": "snapshot", + "file": "http://192.168.28.32:8000/media/mediums/2026/04/02/camera1_xxx.jpg", + "event_id": 90908 + } + ], + "camera": { + "id": 1, + "name": "421枪机", + "uri": "rtsp://admin:1234qwer@192.168.28.102:554/Streaming/Channels/202", + "mode": "on", + "status": "online", + "detect_params": { "threshold": 0.5 }, + "default_params": {}, + "rules": [ /* RuleItem[] 见规则结构 */ ], + "should_push": false, + "config_params": {}, + "sampling": false, + "note": {}, + "snapshot": "http://192.168.28.32:8000/media/camera/camera1_snapshot.jpg", + "remote_id": -1, + "raw_address": "0b8342ec52e62d01dd3273f583d326ec", + "ip": "admin:1234qwer@192.168.28.102:554", + "port": 8082 + }, + "types": "wander", + "types_bits": 0, + "obj_types": {}, + "uuid": "461aeb6e-10f3-4173-9b33-d540ce59511e", + "status": "pending", + "remark": null, + "should_push": false, + "metadata": {} + } + ] + } +} +``` + +**前端返回**: +```typescript +{ + tableData: EventItem[], // ret.results + totalItems: number // ret.count +} +``` + +--- + +### 2.2 按参数筛选事件列表 + +- **方法**:`GET` +- **路径**:`/events` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**查询参数(Query)**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `limit` | `number` | 否 | `20` | 每页条数 | +| `offset` | `number` | 否 | `0` | 偏移量 | +| `time_before` | `string` | 否 | — | 结束时间(ISO 8601) | +| `time_after` | `string` | 否 | — | 开始时间(ISO 8601) | +| `types` | `string` | 否 | — | 告警类型编码(如 `wander`) | +| `camera_id` | `number` | 否 | — | 摄像头 ID | +| `status` | `string` | 否 | — | 状态:`pending` / `closed` | + +**请求示例**: +``` +GET /api/v1/events?limit=20&offset=0&time_after=2026-04-01T00%3A00%3A00Z&time_before=2026-04-02T00%3A00%3A00Z&types=wander&camera_id=1&status=pending +Authorization: Bearer +``` + +**成功响应(200 OK)**:同 2.1 响应结构。 + +--- + + + +--- + +### 2.3 获取单个事件详情 + +- **方法**:`GET` +- **路径**:`/event/events/retrieves` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**查询参数(Query)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `id` | `number` | 是 | 事件 ID | + +**请求示例**: +``` +GET /api/v1/event/events/retrieves?id=90908 +Authorization: Bearer +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { + "objects": [ + { + "id": 90908, + "camera_id": 1, + "started_at": "2026-04-01T09:30:00Z", + "ended_at": "2026-04-01T09:30:10Z", + "mediums": [ + { + "id": 109656, + "name": "snapshot", + "file": "http://192.168.28.32:8000/media/mediums/2026/04/02/camera1_xxx.jpg", + "event_id": 90908 + } + ], + "camera": { /* CameraItem 见摄像头结构 */ }, + "types": "wander", + "types_bits": 0, + "obj_types": {}, + "uuid": "461aeb6e-10f3-4173-9b33-d540ce59511e", + "status": "pending", + "remark": null, + "should_push": false, + "metadata": {} + } + ] + } +} +``` + +**前端取值**:`res.data.ret.objects[0]` + +--- + +### 2.4 获取最新一条事件 + +- **方法**:内部调用 `getEvents(token, 1, 1)` +- **路径**:`/events?limit=1&offset=0` +- **鉴权**:Bearer Token + +**成功响应**:同 2.1,但 `results` 只有一条,`count` 为总条数。 + +--- + +### 2.5 修改事件状态 + +- **方法**:`PATCH` +- **路径**:`/events/{eventId}` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `eventId` | `number` | 事件 ID | + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `status` | `string` | 是 | 状态值:`pending` / `closed` | +| `remark` | `string` | 否 | 备注信息 | + +```json +{ + "status": "closed", + "remark": "已处理完毕" +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": {} +} +``` + +--- + +### 2.6 删除事件 + +- **方法**:`DELETE` +- **路径**:`/event/events/{eventId}` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `eventId` | `number` | 事件 ID | + +**请求体**:无 + +**成功响应**:`204 No Content`(无 body) + +**失败响应**(如事件不存在): +```json +{ + "err": { + "ec": 404, + "dm": "Not Found", + "em": "Not found." + } +} +``` + + + +--- + +### 2.7 事件对象完整结构(EventItem) + +```typescript +interface EventItem { + id: number; // 告警 ID + camera_id: number; // 摄像头 ID + started_at: string; // 开始时间(ISO 8601) + ended_at: string; // 结束时间(ISO 8601) + types: string; // 告警类型编码(如 "wander") + types_bits: number; // 类型位标记 + obj_types: Record; // 目标类型 + uuid: string; // 唯一标识 + status: 'pending' | 'closed'; // 处理状态 + remark: string | null; // 备注 + should_push: boolean; // 是否推送 + metadata: Record; // 元数据 + + // 多媒体 + mediums: Array<{ + id: number; + name: string; // 如 "snapshot" + file: string; // 图片 URL(已规范化) + event_id: number; + }>; + + // 关联摄像头 + camera: CameraItem; +} +``` + +--- + +## 3. 摄像头接口 + +--- + +### 3.1 获取摄像头列表 + +- **方法**:`GET` +- **路径**:`/camera/cameras/get_all` +- **鉴权**:Bearer Token + +**请求头**: + +```http +Authorization: Bearer +Accept: application/json +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { + "count": 10, + "results": [ + { + "id": 1, + "name": "421枪机", + "uri": "rtsp://{camerastream}", + "mode": "on", + "status": "online", + "detect_params": { + "threshold": 0.5 + }, + "default_params": {}, + "rules": [ + { + "id": 1, + "unique_id": "1268f3f2-e0c5-47ea-aa1f-5b97b6dfb95d", + "camera": 1, + "name": "规则名称", + "mode": "on", + "algo": "rule-name", + "params": {}, + "params_base": "", + "schedule": {}, + "event_types": { + "规则英文": "别名,中文映射" + } + } + ], + "should_push": false, + "config_params": {}, + "sampling": false, + "note": {}, + "snapshot": "URL***.jpg", + "remote_id": -1, + "raw_address": "0b8342ec52e62d01dd3273f583d326ec", + "ip": "camera-url", + "port": 8082 + } + ] + } +} +``` + +--- + + + +--- + +### 3.2 获取单个摄像头详情 + +- **方法**:`GET` +- **路径**:`/camera/cameras/{cameraId}` +- **鉴权**:Bearer Token + +**请求头**: + +```http +Authorization: Bearer +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `cameraId` | `number` | 摄像头 ID | + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { /* CameraItem */ } +} +``` + +--- + +### 3.3 更新摄像头信息 + +- **方法**:`PATCH` +- **路径**:`/camera/cameras/{cameraId}` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `cameraId` | `number` | 摄像头 ID | + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `name` | `string` | 否 | 摄像头名称 | +| `mode` | `"on"` / `"off"` | 是 | 检测模式 | + +```json +{ + "name": "421枪机", + "mode": "on" +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { /* CameraItem */ } +} +``` + +--- + +### 3.4 启动摄像头视频流 + +- **方法**:`POST` +- **路径**:`/camera/cameras/{cameraId}/start_stream` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `cameraId` | `number` | 摄像头 ID | + +**请求体**:无 + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { + "port": 8094 + } +} +``` + +| 返回字段 | 类型 | 说明 | +|---------|------|------| +| `ret.port` | `number` | 动态分配的 WebSocket 端口,用于 JSMpeg 视频播放 | + +--- + +### 3.5 停止摄像头视频流 + +- **方法**:`POST` +- **路径**:`/camera/cameras/{cameraId}/stop_stream` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `cameraId` | `number` | 摄像头 ID | + +**请求体**:无 + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": {} +} +``` + +--- + +### 3.8 摄像头对象完整结构(CameraItem) + +```typescript +interface CameraItem { + id: number; + name: string; // 点位名称(如 "421枪机") + uri: string; // RTSP 地址 + mode: 'on' | 'off'; // 检测全局开关 + status: 'online' | 'offline'; // 在线状态 + detect_params: { + threshold: number; // 检测阈值(如 0.5) + }; + default_params: Record; + rules: RuleItem[]; // 关联规则列表 + should_push: boolean; + config_params: Record; + sampling: boolean; + note: Record; + snapshot: string; // 快照图片 URL(已规范化) + remote_id: number; + raw_address: string; + ip: string; + port: number; +} +``` + +**前端发送的 CameraData 类型**(用于 updateCamera): +```typescript +interface CameraData { + id?: number; + name?: string; + mode?: "on" | "off"; + rules?: RuleData[] | {}; +} +``` + +--- + +## 4. 规则接口 + +--- + +### 4.1 更新规则 + +- **方法**:`PATCH` +- **路径**:`/rules/{ruleId}` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `ruleId` | `number` | 规则 ID | + +**请求体(Body JSON)**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `id` | `number` | 是 | 规则 ID | +| `name` | `string` | 否 | 规则名称 | +| `mode` | `"on"` / `"off"` / `"schedule"` | 否 | 规则模式 | +| `schedule` | `object` | 否 | 时间段配置 | + +```json +{ + "id": 1, + "name": "徘徊", + "mode": "on", + "schedule": {} +} +``` + +**带时间段的请求**: +```json +{ + "id": 1, + "name": "未穿反光衣", + "mode": "schedule", + "schedule": { + "type": "daily", + "time_slots": [ + [ + 960, + 1020 + ] + ] + } +} +``` + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": { /* RuleItem */ } +} +``` + + + +--- + +### 4.2 规则对象完整结构(RuleItem) + +```typescript +interface RuleItem { + id: number; + unique_id: string; // UUID + camera: number; // 关联摄像头 ID + name: string; // 规则名称(如 "徘徊") + mode: 'on' | 'off' | 'schedule'; + algo: string; // 算法编码(如 "wander") + params: Record; + params_base: string; + schedule: { + type: string; // 如 "daily" + time_slots?: Array<[number, number]>; + } | {}; + event_types: Record; // 如 { "wander": "人员徘徊" } +} +``` + +--- + +## 5. 算法接口 + +--- + +### 5.1 获取算法列表 + +- **方法**:`GET` +- **路径**:`/algorithms` +- **鉴权**:Bearer Token + +**请求头**: +```http +Authorization: Bearer +Accept: application/json +``` + +**请求参数**:无 + +**成功响应(200 OK)**: +```json +{ + "err": { "ec": 0, "dm": "ok" }, + "ret": [ + { "code_name": "wander", "name": "人员徘徊" }, + { "code_name": "intrusion", "name": "入侵" }, + { "code_name": "no_reflective_clothing", "name": "未佩戴反光衣" } + ] +} +``` + +| 返回字段 | 类型 | 说明 | +|---------|------|------| +| `ret[]` | `array` | 算法列表(无分页包装) | +| `ret[].code_name` | `string` | 算法编码(如 `wander`) | +| `ret[].name` | `string` | 算法中文名称(如 `人员徘徊`) | + +### 5.2 算法映射表 + +```typescript +interface AlgorithmItem { + code_name: string; // 算法编码(如 "wander") + name: string; // 算法中文名称(如 "人员徘徊") +} +``` + +登录时自动拉取并构建 `Map` 映射,供全局以编码查中文名。 + +--- + +## 6. WebSocket 接口 + +### 6.1 事件监听通道 WebSocket + +- **地址**:`ws://192.168.28.32:8080/ws/event` +- **代理地址(Vite 开发环境)**:`/ws/event` + +### 6.2 推送消息格式 + +```json +{ + "id": 90908, + "camera_id": 1, + "started_at": "2026-04-01T09:30:00Z", + "ended_at": "2026-04-01T09:30:10Z", + "types": "wander", + "status": "pending", + "camera": { "id": 1, "name": "421枪机" }, + "mediums": [ { "id": 109656, "name": "snapshot", "file": "http://192.168.28.32:8000/media/mediums/xxx.jpg" } ] +} +``` + +### 6.3 视频流 WebSocket + +- **地址**:`ws://{host}:{dynamicPort}/` +- **说明**:由 `startCameraStream` 返回动态端口,JSMpeg 直接连接播放 + +--- + +## 7. 通用响应格式 + +### 7.1 成功响应 + +```json +{ + "err": { + "ec": 0, + "dm": "ok" + }, + "ret": { /* 响应数据 */ } +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `err.ec` | `number` | 错误码,`0` 表示成功 | +| `err.dm` | `string` | 状态描述,成功时为 `"ok"` | +| `ret` | `object` / `array` | 返回的数据主体 | + +### 7.2 错误响应 + +```json +{ + "err": { + "ec": 1001, + "dm": "错误描述信息" + } +} +``` + +### 7.3 常见错误码 + +| 错误码 | 说明 | +|--------|------| +| `0` | 成功 | +| `1001` | 参数错误 | +| `1002` | 认证失败 | +| `1003` | 权限不足 | +| `1004` | 资源不存在 | +| `1005` | 操作失败 | + +### 7.4 HTTP 状态码 + +| 状态码 | 说明 | 前端处理 | +|--------|------|----------| +| `200 OK` | 请求成功 | 正常处理 | +| `201 Created` | POST 创建成功 | 正常处理 | +| `204 No Content` | DELETE / 无业务数据 PATCH | 视作成功 | +| `400 Bad Request` | 参数错误 | 提示错误信息 | +| `401 Unauthorized` | 未授权 / Token 过期 | 清除 Token,跳转登录页 | +| `403 Forbidden` | 权限不足 | 提示无权限 | +| `404 Not Found` | 资源不存在 | 提示未找到 | +| `500 Internal Server Error` | 服务器内部错误 | 提示服务器错误 | + +--- + +## 8. 认证与 Token 管理 + +### 8.1 Token 有效期 + +- Token 有效期:**24 小时** +- 前端每 **30 分钟** 检查一次 Token 是否过期 +- 过期后自动清除 Token 并跳转到登录页 + +### 8.2 请求头格式 + +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +### 8.3 拦截器行为 + +- **请求拦截**:每次请求前检查 Token 是否过期(`checkTokenExpiry`) +- **响应拦截**:收到 `401` 状态码时自动清除 Token 并跳转登录 + +--- + +## 9. 前端数据转换说明 + +### 9.1 URL 规范化 + +后端返回的 JSON 中包含内部地址(如 `http://127.0.0.1:8000/media/...`),前端自动转换为: +- `http://127.0.0.1:8000/xxx` → `http://<实际服务器IP>:8000/xxx` +- `http://localhost:8000/xxx` → `http://<实际服务器IP>:8000/xxx` + +适用字段: +- `mediums[].file`(事件图片) +- `camera.snapshot`(摄像头快照) +- 顶层 `snapshot` / `file` +- `next` / `previous` 分页链接 + +### 9.2 分页结构 + +所有列表接口统一分页格式: +```typescript +{ + count: number, // 总条数 + next: string|null, // 下一页 URL + previous: string|null, // 上一页 URL + results: T[] // 当前页数据 +} +``` + +--- + +## 10. 完整调用示例 + +### 10.1 登录并获取事件列表 + +```typescript +import { BoxApi } from '@/utils/boxApi'; + +const api = new BoxApi(); + +// 登录 +const token = await api.login('admin', 'password'); + +// 获取事件(第1页,每页20条) +const { tableData, totalItems } = await api.getEvents(token, 20, 1); + +// 按条件筛选 +const { results, count } = await api.getEventsByParams( + token, 20, 1, + '2026-04-01T00:00:00Z', '2026-04-02T00:00:00Z', + 'wander', null, 'pending' +); + +// 获取所有摄像头 +const cameras = await api.getAllCameras(token); + +// 更新事件状态 +await api.setEventStatus(90908, 'closed', '已处理', token); + +// 删除事件 +await api.delEvents(token, [90908, 90909]); +``` + +### 10.2 摄像头管理 + +```typescript +// 获取单个摄像头详情(含规则) +const camera = await api.getCameraById(token, 1); + +// 更新摄像头基本设置 +await api.updateCamera(token, 1, { name: '421枪机', mode: 'on' }); + +// 批量更新规则 +await api.updateRule(token, [ + { id: 1, camera: 1, name: '徘徊', mode: 'on' }, + { id: 32, camera: 1, name: '入侵', mode: 'schedule', schedule: { type: 'daily', time_slots: [[480, 720]] } } +]); + +// 启动视频流 +const { port } = await api.startCameraStream(token, 1); + +// 停止视频流 +await api.stopCameraStream(token, 1); +``` + +--- + +## 11. 附录:前端 API 方法速查表 + +| 方法 | HTTP 方法 | 路径 | 请求体/参数 | 响应体 | +|------|-----------|------|------------|--------| +| `login()` | POST | `/auth/login` | `{username, password, cookieless}` | `{token}` | +| `logout()` | POST | `/auth/logout` | 无 | `{}` | +| `addUser()` | POST | `/auth/adduser` | `{username, password, email?}` | `{}` | +| `rmUser()` | POST | `/auth/rmuser` | `{username}` | `{}` | +| `getAllUsers()` | GET | `/auth/allusers` | — | `{users: [...]}` | +| `resetUser()` | POST | `/auth/resetuser` | `{username, password, email}` | `{}` | +| `getEvents()` | GET | `/events?limit=&offset=` | — | `{count, next, prev, results}` | +| `getEventsByParams()` | GET | `/events?limit=&offset=&...` | — | `{count, next, prev, results}` | +| `getEventsByUrl()` | GET | 自定义 URL | — | `{count, next, prev, results}` | +| `getEventById()` | GET | `/event/events/retrieves?id=` | — | `{objects: [EventItem]}` | +| `getOneEvent()` | GET | `/events?limit=1&offset=0` | — | `{count, next, prev, results}` | +| `setEventStatus()` | PATCH | `/events/{id}` | `{status, remark?}` | `{}` | +| `delEvents()` | DELETE | `/event/events/{id}` | 无 | `[{id, success, message?}]` | +| `getCameras()` | GET | `/cameras?limit=&offset=` | — | `{count, results: [...]}` | +| `getCamerasByUrl()` | GET | 自定义 URL | — | `{count, results: [...]}` | +| `getAllCameras()` | GET | `/camera/cameras/get_all` | — | `CameraItem[]` | +| `getCameraById()` | GET | `/camera/cameras/{id}` | — | `CameraItem` | +| `updateCamera()` | PATCH | `/camera/cameras/{id}` | `{name, mode}` | `CameraItem` | +| `startCameraStream()` | POST | `/camera/cameras/{id}/start_stream` | 无 | `{port}` | +| `stopCameraStream()` | POST | `/camera/cameras/{id}/stop_stream` | 无 | `{}` | +| `updateRule()` | PATCH | `/rules/{id}` | `{id, camera, name?, mode?, schedule?}` | `RuleItem` | +| `getAlgorithms()` | GET | `/algorithms` | — | `AlgorithmItem[]` | +| `getAlgorithmName()` | — | 本地映射 | `code_name: string` | `string`(中文名) |