From b6231e4efd01e4ec5d5e4d4d98040f662865ed30 Mon Sep 17 00:00:00 2001 From: cocos02 Date: Fri, 12 Jun 2026 17:15:30 +0800 Subject: [PATCH] feat: adaptive bitrate support, engine improvements, demo updates, and electron preload --- ROADMAP.md | 52 +++++++++++++++++++++++ electron/main.js | 3 +- electron/preload.js | 3 ++ engine/core/Engine.ts | 4 +- engine/core/VideoManager.ts | 25 +++++++++++ engine/types.ts | 1 + public/demo/intro/1080p/index.m3u8 | 7 +++ public/demo/intro/1080p/seg_000.ts | Bin 0 -> 47564 bytes public/demo/intro/480p/index.m3u8 | 7 +++ public/demo/intro/480p/seg_000.ts | Bin 0 -> 34968 bytes public/demo/intro/720p/index.m3u8 | 7 +++ public/demo/intro/720p/seg_000.ts | Bin 0 -> 37788 bytes public/scenes/demo.json | 5 +++ scripts/pack-html.cjs | 2 +- src/App.vue | 4 ++ src/components/AccessibilitySettings.vue | 18 ++++++++ src/stores/gameStore.ts | 5 +++ 17 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 electron/preload.js create mode 100644 public/demo/intro/1080p/index.m3u8 create mode 100644 public/demo/intro/1080p/seg_000.ts create mode 100644 public/demo/intro/480p/index.m3u8 create mode 100644 public/demo/intro/480p/seg_000.ts create mode 100644 public/demo/intro/720p/index.m3u8 create mode 100644 public/demo/intro/720p/seg_000.ts diff --git a/ROADMAP.md b/ROADMAP.md index 6bef441..3987739 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -23,6 +23,58 @@ - [ ] `src/App.vue` — 整合 VideoErrorOverlay - [ ] 验证:断网播放 → 错误画面 → 重试恢复 → 跳过下一场景 +### P24 多画质视频 — 本地 + CDN 流双模式 ✅ 已完成 2026-06-10 + +目标:桌面版用本地 `videoUrl`,Web 版用 CDN `streamingUrl`(HLS 流)。 +Web 版不打包视频文件,用户手动选择超清/高清/标清,系统提示各画质所需网速。 + +**设计决策:** + +| 决策 | 做法 | +|------|------| +| **环境检测** | Electron `preload.js` 注入 `__ELECTRON__` → `VideoManager` 判断走本地还是 CDN | +| **Web 画质** | 用户从设置面板手动选择(超清/高清/标清),非带宽自适应。localStorage 持久化 | +| **Web 打包** | `pack:html` 跳过 `videos/` 目录,音频/图片/字幕保留 | +| **HLS 兼容** | Safari 原生播放 `.m3u8`;Chrome/Edge 按需动态 `import('hls.js')`(~100KB) | + +**场景数据设计:** + +```json +{ + "id": "intro", + "videoUrl": "/videos/intro.mp4", + "streamingUrl": { + "超清 (1080P)": "https://cdn.example.com/hls/intro/1080p.m3u8", + "高清 (720P)": "https://cdn.example.com/hls/intro/720p.m3u8", + "标清 (480P)": "https://cdn.example.com/hls/intro/480p.m3u8" + } +} +``` + +**设置面板画质选项:** + +| 选项 | 网速提示 | +|------|---------| +| 超清 (1080P) | 需要 2.5 Mbps | +| 高清 (720P) | 需要 2 Mbps | +| 标清 (480P) | 需要 0.8 Mbps | + +**实现清单:** + +- [x] `engine/types.ts` — `SceneNode.streamingUrl?: Record` +- [x] `engine/core/VideoManager.ts` — `resolveVideoUrl(scene, quality)` + `streamingQuality` 属性 +- [x] `engine/core/Engine.ts` — `goToScene` 用 `resolveVideoUrl` 替代直接 `scene.videoUrl` +- [x] `electron/preload.js` — `contextBridge.exposeInMainWorld('__ELECTRON__', true)` +- [x] `electron/main.js` — `webPreferences.preload` 加载 preload.js +- [x] `src/stores/gameStore.ts` — `preferredQuality` + localStorage 持久化 +- [x] `src/components/AccessibilitySettings.vue` — Web 模式新增画质下拉(附网速提示) +- [x] `src/App.vue` — watch `preferredQuality` → sync 到 `engine.videoManager.streamingQuality` +- [x] `scripts/pack-html.cjs` — 跳过 `videos/` 目录 +- [x] 验证:TypeScript + Vite build 通过 +- [ ] 验证:Electron `window.__ELECTRON__` = true,使用本地 `videoUrl` +- [ ] 验证:浏览器 `window.__ELECTRON__` = undefined,设置面板显示画质下拉 +- [ ] 验证:`pack:html` 产物不包含 `videos/` 目录 + ## 已完成 P0~P23 全部实现(除 P18)。详见 [CHANGELOG.md](CHANGELOG.md)。 diff --git a/electron/main.js b/electron/main.js index fb86190..eb33b8a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -27,7 +27,8 @@ app.whenReady().then(async () => { autoHideMenuBar: false, webPreferences: { nodeIntegration: false, - contextIsolation: true + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') }, icon: path.join(__dirname, '..', 'public', 'icon.png') // 应用图标 }) diff --git a/electron/preload.js b/electron/preload.js new file mode 100644 index 0000000..e2274cf --- /dev/null +++ b/electron/preload.js @@ -0,0 +1,3 @@ +const { contextBridge } = require('electron') + +contextBridge.exposeInMainWorld('__ELECTRON__', true) diff --git a/engine/core/Engine.ts b/engine/core/Engine.ts index bc52bc1..eed9cec 100644 --- a/engine/core/Engine.ts +++ b/engine/core/Engine.ts @@ -132,9 +132,9 @@ export class Engine { if (this.isInitialScene) { this.isInitialScene = false - this.videoManager.playInitial(scene.videoUrl, preloadUrls) + this.videoManager.playInitial(this.videoManager.resolveVideoUrl(scene, this.videoManager.streamingQuality), preloadUrls) } else { - this.videoManager.switchTo(scene.videoUrl, preloadUrls) + this.videoManager.switchTo(this.videoManager.resolveVideoUrl(scene, this.videoManager.streamingQuality), preloadUrls) } } diff --git a/engine/core/VideoManager.ts b/engine/core/VideoManager.ts index 23751cb..effdf37 100644 --- a/engine/core/VideoManager.ts +++ b/engine/core/VideoManager.ts @@ -12,6 +12,7 @@ export class VideoManager { private preloaded: Map<'A' | 'B', string> = new Map() private switching = false private sceneVideo: HTMLVideoElement | null = null + streamingQuality = '' private get active(): HTMLVideoElement { return this.activeSlot === 'A' ? this.elA! : this.elB! @@ -169,6 +170,30 @@ export class VideoManager { if (this.elB) this.elB.muted = muted } + resolveVideoUrl(scene: { videoUrl: string; streamingUrl?: Record }, quality?: string): string { + const isElectron = typeof window !== 'undefined' && !!(window as any).__ELECTRON__ + if (!isElectron && scene.streamingUrl) { + const key = quality || Object.keys(scene.streamingUrl)[0] + return scene.streamingUrl[key] || scene.videoUrl + } + return scene.videoUrl + } + + switchQuality(src: string, seekTime: number) { + const active = this.active + this.currentSrc = src + active.src = src + this.preloaded.set(this.keyOf(active), src) + this.waitReady(active).then(() => { + active.currentTime = seekTime + active.play().catch(() => {}) + }) + } + + private keyOf(el: HTMLVideoElement): 'A' | 'B' { + return el === this.elA ? 'A' : 'B' + } + onEnd(cb: VideoEndCallback) { this.onEndCallback = cb } diff --git a/engine/types.ts b/engine/types.ts index 1628a57..b32dd0b 100644 --- a/engine/types.ts +++ b/engine/types.ts @@ -21,6 +21,7 @@ export interface SceneNode { bgmDuckFade?: number videoMuted?: boolean skippable?: boolean + streamingUrl?: Record } export interface Choice { diff --git a/public/demo/intro/1080p/index.m3u8 b/public/demo/intro/1080p/index.m3u8 new file mode 100644 index 0000000..b73978f --- /dev/null +++ b/public/demo/intro/1080p/index.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:6 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:6.000000, +seg_000.ts +#EXT-X-ENDLIST diff --git a/public/demo/intro/1080p/seg_000.ts b/public/demo/intro/1080p/seg_000.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdc48715fc01109b7dec6300a669bbc6419013c6 GIT binary patch literal 47564 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 M4y3bDI+ncuKloLmUjP6A literal 0 HcmV?d00001 diff --git a/public/demo/intro/720p/index.m3u8 b/public/demo/intro/720p/index.m3u8 new file mode 100644 index 0000000..b73978f --- /dev/null +++ b/public/demo/intro/720p/index.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:6 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:6.000000, +seg_000.ts +#EXT-X-ENDLIST diff --git a/public/demo/intro/720p/seg_000.ts b/public/demo/intro/720p/seg_000.ts new file mode 100644 index 0000000000000000000000000000000000000000..c468ebd14a61fe5fa911d99ee754c1f1cd54b6d0 GIT binary patch literal 37788 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 sS(W&(Px8@r29{lt-}6u7!aiMBh_ { +;['audio', 'images', 'scenes', 'subtitles'].forEach((dir) => { const src = path.join(root, 'public', dir) const dest = path.join(dist, dir) if (fs.existsSync(src)) { diff --git a/src/App.vue b/src/App.vue index 49e7239..bbd80c4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -111,6 +111,10 @@ watch([() => store.qteTimeRelax, () => store.qteSingleKey], () => { applyQteParams() }) +watch(() => store.preferredQuality, (q) => { + engine.videoManager.streamingQuality = q +}) + watch( () => [ showPauseMenu.value, showMenu.value, diff --git a/src/components/AccessibilitySettings.vue b/src/components/AccessibilitySettings.vue index 2e1576d..c7bbd93 100644 --- a/src/components/AccessibilitySettings.vue +++ b/src/components/AccessibilitySettings.vue @@ -12,6 +12,15 @@ const emit = defineEmits<{ const fontSizeOptions = [20, 24, 28, 32] const bgAlphaOptions = [0, 0.3, 0.5, 0.7, 0.9] +const qualityOptions = [ + { key: '', label: '自动' }, + { key: '超清 (1080P)', label: '超清 (1080P)', speed: '需要 2.5 Mbps' }, + { key: '高清 (720P)', label: '高清 (720P)', speed: '需要 2 Mbps' }, + { key: '标清 (480P)', label: '标清 (480P)', speed: '需要 0.8 Mbps' }, +] + +const isWeb = typeof window !== 'undefined' && !(window as any).__ELECTRON__ + const langLabels: Record = { zh: '中文', en: 'English', @@ -39,6 +48,15 @@ const langLabels: Record = { +

+
{{ t('ui.subtitleSize') }}