From 1b24aebe77dfe26ad98077ec800d32d40090b359 Mon Sep 17 00:00:00 2001 From: BoatingCloud <1759888028@qq.com> Date: Mon, 31 Aug 2026 23:34:32 +0800 Subject: [PATCH] fix: pass emby tls ca bundle to core --- include/mpv/harmony_playback.h | 1 + memory/2026-08-31.md | 6 ++++++ memory/MEMORY.md | 2 +- memory/projects.md | 6 +++--- playback_lab/entry/src/main/cpp/bridge.cpp | 2 ++ playback_lab/entry/src/main/cpp/player.cpp | 1 + playback_lab/entry/src/main/cpp/player.h | 1 + player/harmony_playback.c | 20 ++++++++++++++++++-- sdk/native/product_binding.cpp | 2 +- 9 files changed, 34 insertions(+), 7 deletions(-) diff --git a/include/mpv/harmony_playback.h b/include/mpv/harmony_playback.h index 7e2e25cacd..c84d81576f 100644 --- a/include/mpv/harmony_playback.h +++ b/include/mpv/harmony_playback.h @@ -107,6 +107,7 @@ typedef struct mp_harmony_playback_request { int subtitle_track_id; /* < 0 means auto. */ double playback_speed; /* <= 0 means unset. */ double network_timeout; /* <= 0 means mpv default. */ + const char *tls_ca_file; /* Optional PEM/CA bundle path for TLS verification. */ } mp_harmony_playback_request; typedef struct mp_harmony_track { diff --git a/memory/2026-08-31.md b/memory/2026-08-31.md index ddbbb2d75f..49176ceae4 100644 --- a/memory/2026-08-31.md +++ b/memory/2026-08-31.md @@ -21,3 +21,9 @@ 文件变更: ../Velix/entry/src/main/ets/domain/emby/{EmbyApiClient,EmbyMediaProvider,EmbyModels,EmbyRuntimeProbe,EmbySecureTokenStore}.ets, ../Velix/entry/src/main/ets/pages/HomePage.ets, ../Velix/docs/emby/{EMBY_RUNTIME_VALIDATION,EMBY_API_RISKS}.md, ../Velix/Velix/DOCS/ALPHA_STATUS.md 教训: Emby 运行时 body/响应形状可能偏离 OpenAPI;嵌套 CollectionFolder 需要 Recursive=true,Hints 空数组应回退 Items 搜索;HTTPS 不能通过关闭证书校验规避。 标签: #emby #alpha #directplay #tls #emulator #runtime + +[PROJECT:Velix Alpha] HTTPS Core 复测与 Alpha 文档更新 +结论: 修复并重建 libmpv 后,应用 CA bundle 已使真实 Emby HTTPS Direct Play 在模拟器通过;首帧约 10.5 秒,Playing/Progress/Stopped 均返回 204。 +文件变更: ../Velix/entry/src/main/resources/rawfile/ca-certificates.pem, ../Velix/entry/src/main/ets/domain/emby/EmbyTlsTrustStore.ets, ../Velix/entry/src/main/ets/entryability/EntryAbility.ets, ../Velix/entry/src/main/ets/domain/playback/{PlaybackTypes,MpvPlaybackEngine}.ets, ../Velix/entry/src/main/ets/pages/PlayerPage.ets, ../Velix-MPV-Core/{include/mpv/harmony_playback.h,player/harmony_playback.c,sdk/native/product_binding.cpp,playback_lab/entry/src/main/cpp/{bridge,player,player.h}}, ../docs/emby/{EMBY_RUNTIME_VALIDATION,EMBY_API_RISKS}.md, ../Velix/DOCS/ALPHA_STATUS.md, ../alpha.md +教训: 修改 MPV 播放核心后必须显式重建 libmpv,单独重打绑定层不会更新预编译播放库;证书资源必须用 openssl 校验后再打包。 +标签: #emby #https #tls #directplay #alpha #emulator diff --git a/memory/MEMORY.md b/memory/MEMORY.md index bdb1d2451d..94f21405f8 100644 --- a/memory/MEMORY.md +++ b/memory/MEMORY.md @@ -16,4 +16,4 @@ - Alpha diagnostics:主 App 提交 `33c39ff`,已接入脱敏 VelixLogger、1000 条 ring、2 MiB/5 级轮转、日志查看/筛选/复制、15/30 分钟诊断模式、系统文件选择器 ZIP 导出及播放异常标记;debug/release unsigned HAP 构建并在模拟器无崩溃启动。真实 Emby、真机和正式签名仍待完成。 - Alpha playback/settings:主 App 提交 `30aa760`,Emby Resume 位置、音轨/字幕选择、暂停/Seek/切轨强制 Progress 已接入 Core;Settings/About 页面可用,快进/快退设置持久化并由 PlayerPage 读取。Debug/Release 构建及模拟器启动无崩溃;真机、正式签名和 HTTPS Core TLS 验证仍待完成。 - Alpha interactions:主 App 提交 `b9380df`,播放器 Slider/速度/音轨字幕/双击/长按临时倍速、Ability 前后台桥接、详情骨架与重试、服务器管理和媒体卡长按菜单已接入;debug 构建、安装、详情跳转与模拟器崩溃窗口检查通过。 -- Emby runtime:主 App 当前修复 `Pw` 登录字段、ArkUI HTTP headers 对象和 Asset Token 明文读取;真实登录/Token 恢复、Views/Items/详情/Series Seasons-Episodes/Search 回退、PlaybackInfo 已在模拟器通过。HTTP Direct Play 首帧约 3.5 秒且 Playing/Progress/Stopped 返回 204;HTTPS API 可用但原生 Core 播放受 TLS 信任链阻塞,服务器为 Emby 4.8.11.0,尚未宣称 Alpha 完成。 +- Emby runtime:主 App 当前修复 `Pw` 登录字段、ArkUI HTTP headers 对象和 Asset Token 明文读取;真实登录/Token 恢复、Views/Items/详情/Series Seasons-Episodes/Search 回退、PlaybackInfo 已在模拟器通过。应用将受控 PEM CA bundle 传入 Core,HTTP Direct Play 首帧约 3.5 秒、HTTPS 首帧约 10.5 秒,Playing/Progress/Stopped 均返回 204;服务器为 Emby 4.8.11.0,4.9.x/真机/正式签名仍待完成。 diff --git a/memory/projects.md b/memory/projects.md index 3ccd27f937..83f4a9f740 100644 --- a/memory/projects.md +++ b/memory/projects.md @@ -9,7 +9,7 @@ - Phase 8 UI:主 App `../Velix` 已初始化独立 Git,提交 `058c7a8`;`HomeSource`、Discovery/Media Provider 边界、Mock/TMDB skeleton、RemoteImage/cache、Source Capsule/Sheet、Floating Navigation、Home/Detail、Player token integration 已完成。TMDB live credentials 已在本地注入并完成模拟器验证;Core 未修改。 - Phase 8 模拟器:`127.0.0.1:5555` 已安装主 App HAP;首页远程图片、来源 Sheet、Hero → Detail、PlayerPage 本地 MP4 均已回归。截图见 `../Velix/design-qa.md`。 - Alpha 诊断基础:主 App 提交 `33c39ff`;`VelixLogger` 统一接入并自动脱敏,内存 ring 1000 条、本地日志 2 MiB/5 级轮转;LogViewer 支持实时刷新、暂停、Level/Category/关键词筛选、复制与清空;诊断模式支持 15/30 分钟;ZIP 导出已通过系统文件选择器在模拟器保存验证;播放异常结束 marker 已接入。debug/release unsigned HAP 均构建成功并启动无崩溃。 -- Alpha 当前阻塞:真实 Emby 4.8.11.0 账号已完成认证、库/详情/搜索/PlaybackInfo 及 HTTP Direct Play+Session E2E;HTTPS 原生 Core 播放仍受模拟器 TLS 信任链阻塞,4.9.x 目标版本、AGC `.p7b` Profile、真机与压力回归仍待完成。 +- Alpha 当前阻塞:真实 Emby 4.8.11.0 账号已完成认证、库/详情/搜索/PlaybackInfo 及 HTTP/HTTPS Direct Play+Session E2E;4.9.x 目标版本、其他 CA 链、AGC `.p7b` Profile、真机与压力回归仍待完成。 - VUI1:主 App 沉浸式视觉重构已完成;`AmbientPalette` 驱动全屏 Hero/页面氛围色,浮层共用 1–4 级 `MaterialSurface`,底部导航使用 HDS `HdsTabs` immersive material。Pura 90 Pro Max 已回归首页、Sheet、详情、播放器自动隐藏和深色;MatePad Pro 13 已回归宽屏首页与详情分栏。Core 未修改,主 App 等待提交。 - VUI1 修正:首页的来源胶囊、搜索与个人入口已从 Scroll 抽离,与底部导航一样作为固定浮层;Pura 90 Pro Max 上滑回归通过。 - VUI1 首页重构:首页改为 Full-Bleed Immersive Home;Hero 的 backdrop/transition/ambient browsing surface 三层连续,环境色改用语义 Ambient Palette,顶部与 HDS 底栏统一使用动态氛围材质;Pura 90 Pro Max 初始态与滚动回归通过,主 App 待提交。 @@ -26,10 +26,10 @@ - VUI1 HTTP/打包验证:emulator 已播放公开 HTTP MP4;`111.cer` 已导入匹配 `.p12`,Release HAP 已构建,但正式签名包还缺 AGC 对应 `.p7b Profile`。 - Emby 阶段:已完整解析项目根 `embyapi.json`(OpenAPI 3.0.1、356 paths、447 operations),生成 `../docs/emby/` 五份清单/模型/播放链路/风险文档;主 App 提交 `2d3c255`/`ff28ae1` 加入 EmbyApiClient、EmbyRequestContext、EmbyAuthProvider 与 DTO 基础骨架,未实现具体业务 endpoint;debug HAP 构建通过。 - Emby Phase 8.1:主 App 提交 `1cd396d`。Source Capsule 已可打开添加 Emby 表单;`EmbyServerUrlNormalizer` 支持 host/host:port、http(s) 与单一 `/emby` 后缀;`VelixDeviceIdentity` 持久化安装级 DeviceId;`EmbySecureTokenStore` 使用 Asset persistent/accessibility tags(API 24 SDK 需目标签名确认 `STORE_PERSISTENT_DATA`);`EmbyServerManager` 支持多服务器、活动源、登出/移除与 Token 失效;`EmbyRuntimeProbe` 执行 PublicSystemInfo、AuthenticateByName、User、System、Views、MediaFolders、Items 和条件 PlaybackInfo,输出脱敏 `EMBY_RUNTIME_VALIDATION.md`。模拟器已安装并启动 debug HAP,表单必填错误态可见、无新增崩溃;真实 Emby 4.9.x 网络/ProviderIds/ticks/PlaybackInfo 仍待运行时验证。 -- Emby 连接实测:服务器返回 `SaturDay.Lite`、Emby `4.8.11.0`;该实例要求 `Pw` 登录字段,修复后 `/Users/AuthenticateByName`、用户/库/Items/Resume/Latest/NextUp 均成功,Token 可在应用重启后恢复。`/Search/Hints` 空数组由 Items 搜索回退;Series Seasons/Episodes 与 PlaybackInfo 已验证。HTTPS API 正常但 Core 播放报 `NETWORK: loading failed`,HTTP Direct Play 首帧/Session 204 已验证,禁止关闭 TLS 校验。 +- Emby 连接实测:服务器返回 `SaturDay.Lite`、Emby `4.8.11.0`;该实例要求 `Pw` 登录字段,修复后 `/Users/AuthenticateByName`、用户/库/Items/Resume/Latest/NextUp 均成功,Token 可在应用重启后恢复。`/Search/Hints` 空数组由 Items 搜索回退;Series Seasons/Episodes 与 PlaybackInfo 已验证。应用 CA bundle 已接入 Core,HTTP/HTTPS Direct Play 与 Playing/Progress/Stopped 204 均已在模拟器验证。 - Alpha Emby foundation:主 App 提交 `540aee5`,补齐 Emby Resume/Latest/Next Up、Series Seasons/Episodes、`/Search/Hints` 分页搜索、ProviderIds 优先资源匹配、DirectPlay PlaybackInfo/URL/headers;`PlaybackLaunch.remoteSession` 已接入 PlayerPage 的 Start/8 秒节流 Progress/Stop。后续兼容修复尚未提交,当前产物仍为 unsigned debug HAP。 - Alpha playback/settings:主 App 提交 `30aa760`,`UserData.PlaybackPositionTicks` 进入 PlaybackLaunch/Core 起播位置,详情页提供继续/从头,音轨/字幕选择会同步 Emby Session;暂停、Seek、切轨强制上报 Progress。新增持久化 Settings/About 入口,PlayerPage 读取快进/快退时长;Debug/Release HAP 与模拟器启动通过。 -- Alpha interactions:主 App 提交 `b9380df`;播放器加入 Slider、速度、音轨/字幕、双击快进/快退和长按临时倍速,前后台桥接按 Alpha 策略暂停并补报进度;详情骨架/错误重试、搜索/连接诊断入口、Emby 源退出/移除、媒体卡长按上下文菜单已完成。模拟器已验证长按菜单与详情跳转,无新增崩溃;真实 Emby HTTP Direct Play 已验证,HTTPS Core、真机横屏、压力和正式签名仍待完成。 +- Alpha interactions:主 App 提交 `b9380df`;播放器加入 Slider、速度、音轨/字幕、双击快进/快退和长按临时倍速,前后台桥接按 Alpha 策略暂停并补报进度;详情骨架/错误重试、搜索/连接诊断入口、Emby 源退出/移除、媒体卡长按上下文菜单已完成。模拟器已验证长按菜单、详情跳转及真实 Emby HTTP/HTTPS Direct Play,无新增崩溃;真机横屏、压力和正式签名仍待完成。 - Phase 6 产物:`benchmark/runner/run_emulator.py` 与场景 manifest;启动 P50/P95 506/1150ms,seek recovery P50/P95 277/631ms;100 load/stop、200 play/pause 无错误,PSS/精确 heap/CPU permille unavailable。 - Phase 6 文档:`DOCS/PHASE6_STATUS.md`、`PERFORMANCE_METRICS.md`、`PERFORMANCE_CHANGES.md`、`BENCHMARK_METHODOLOGY.md`、`STARTUP_PIPELINE.md`、`SEEK_PIPELINE.md`、`BUFFERING_MODEL.md`。 diff --git a/playback_lab/entry/src/main/cpp/bridge.cpp b/playback_lab/entry/src/main/cpp/bridge.cpp index 18a3a6fb21..a6aebc18d8 100644 --- a/playback_lab/entry/src/main/cpp/bridge.cpp +++ b/playback_lab/entry/src/main/cpp/bridge.cpp @@ -128,6 +128,8 @@ bool getRequest(napi_env env, napi_callback_info info, PlaybackRequestModel& req else if (getNamedDouble(env, argv[0], "playback_speed", number)) request.playbackSpeed = number; if (getNamedDouble(env, argv[0], "networkTimeout", number)) request.networkTimeout = number; else if (getNamedDouble(env, argv[0], "network_timeout", number)) request.networkTimeout = number; + getNamedString(env, argv[0], "tlsCaFile", request.tlsCaFile); + if (request.tlsCaFile.empty()) getNamedString(env, argv[0], "tls_ca_file", request.tlsCaFile); int64_t start = -1; if (getNamedInt64(env, argv[0], "startPositionMs", start)) request.startPositionMs = start; else if (getNamedInt64(env, argv[0], "start_position_ms", start)) request.startPositionMs = start; diff --git a/playback_lab/entry/src/main/cpp/player.cpp b/playback_lab/entry/src/main/cpp/player.cpp index ac951507b1..c52f538242 100644 --- a/playback_lab/entry/src/main/cpp/player.cpp +++ b/playback_lab/entry/src/main/cpp/player.cpp @@ -173,6 +173,7 @@ bool PlaybackEngine::load(const PlaybackRequestModel& request) coreRequest.subtitle_track_id = request.subtitleTrackId; coreRequest.playback_speed = request.playbackSpeed; coreRequest.network_timeout = request.networkTimeout; + coreRequest.tls_ca_file = request.tlsCaFile.c_str(); { std::lock_guard lock(mutex_); diff --git a/playback_lab/entry/src/main/cpp/player.h b/playback_lab/entry/src/main/cpp/player.h index ea7a09e0fe..d024e6f5ab 100644 --- a/playback_lab/entry/src/main/cpp/player.h +++ b/playback_lab/entry/src/main/cpp/player.h @@ -40,6 +40,7 @@ struct PlaybackRequestModel { int subtitleTrackId = -1; double playbackSpeed = 0.0; double networkTimeout = 0.0; + std::string tlsCaFile; }; struct PlaybackTrack { diff --git a/player/harmony_playback.c b/player/harmony_playback.c index 4b869097bd..97783b4ef0 100644 --- a/player/harmony_playback.c +++ b/player/harmony_playback.c @@ -32,6 +32,7 @@ struct mp_harmony_player { int64_t perf_origin_ns; int64_t buffering_start_ns; int seek_active; + char tls_ca_file[MP_HARMONY_URL]; mp_harmony_playback_snapshot snapshot; mp_harmony_track tracks[MP_HARMONY_MAX_TRACKS]; size_t track_count; @@ -646,7 +647,14 @@ static void *event_loop(void *arg) mp_harmony_error_code error = classify_error(end->error, s); if (p->snapshot.network.http_status >= 400 && p->snapshot.network.http_status <= 599) error = MP_HARMONY_ERROR_HTTP; - set_error_locked(p, error, s); + /* Keep a more specific FFmpeg/TLS/HTTP diagnostic emitted before + * the generic end-file reason instead of replacing it with + * "loading failed". */ + if (p->snapshot.error_code == MP_HARMONY_ERROR_NONE || + p->snapshot.network.http_status >= 400) + set_error_locked(p, error, s); + else + p->snapshot.state = MP_HARMONY_ERROR; } else if (end && end->reason == MPV_END_FILE_REASON_EOF) p->snapshot.state = MP_HARMONY_ENDED; else p->snapshot.state = MP_HARMONY_STOPPED; break; @@ -702,6 +710,11 @@ int mp_harmony_playback_initialize(mp_harmony_player *p) pthread_mutex_unlock(&p->lock); return -1; } } + if (p->tls_ca_file[0] && mpv_set_option_string(p->handle, "tls-ca-file", p->tls_ca_file) < 0) { + set_error_locked(p, MP_HARMONY_ERROR_TLS, "tls-ca-file option failed"); + mpv_terminate_destroy(p->handle); p->handle = NULL; + pthread_mutex_unlock(&p->lock); return -1; + } int64_t initialize_begin = clock_now_ns(); int initialize_result = mpv_initialize(p->handle); if (initialize_begin > 0 && initialize_result >= 0) @@ -785,12 +798,14 @@ static int set_headers_locked(mp_harmony_player *p, const mp_harmony_playback_re int mp_harmony_playback_load(mp_harmony_player *p, const mp_harmony_playback_request *r) { if (!p || !r || !r->url || !r->url[0]) return -1; + if (r->tls_ca_file && strlen(r->tls_ca_file) >= sizeof(p->tls_ca_file)) return -1; if (r->http_header_count > MP_HARMONY_MAX_HEADERS || r->subtitle_file_count > MP_HARMONY_MAX_SUBTITLES || (r->playback_speed != 0.0 && (!isfinite(r->playback_speed) || r->playback_speed <= 0)) || (r->network_timeout != 0.0 && (!isfinite(r->network_timeout) || r->network_timeout < 0))) return -1; + copy_text(p->tls_ca_file, sizeof(p->tls_ca_file), r->tls_ca_file); if (mp_harmony_playback_initialize(p) < 0) return -1; pthread_mutex_lock(&p->lock); if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; } @@ -842,7 +857,8 @@ int mp_harmony_playback_load(mp_harmony_player *p, const mp_harmony_playback_req set_property_string(p, "referrer", r->referer ? r->referer : "") < 0 || set_property_string(p, "force-media-title", r->title ? r->title : "") < 0 || set_property_string(p, "alang", r->preferred_audio_language ? r->preferred_audio_language : "") < 0 || - set_property_string(p, "slang", r->preferred_subtitle_language ? r->preferred_subtitle_language : "") < 0) { + set_property_string(p, "slang", r->preferred_subtitle_language ? r->preferred_subtitle_language : "") < 0 || + set_property_string(p, "tls-ca-file", r->tls_ca_file ? r->tls_ca_file : "") < 0) { pthread_mutex_unlock(&p->lock); return -1; } { char timeout[32]; snprintf(timeout, sizeof(timeout), "%.3f", r->network_timeout > 0 ? r->network_timeout : 0.0); if (set_property_string(p, "network-timeout", timeout) < 0) { pthread_mutex_unlock(&p->lock); return -1; } } diff --git a/sdk/native/product_binding.cpp b/sdk/native/product_binding.cpp index 86ed1a6027..70d8ff1deb 100644 --- a/sdk/native/product_binding.cpp +++ b/sdk/native/product_binding.cpp @@ -126,6 +126,7 @@ bool requestFromArg(napi_env env, napi_value argument, PlaybackRequestModel& req double decimal = 0.0; if (namedDouble(env, argument, "playbackSpeed", decimal)) request.playbackSpeed = decimal; if (namedDouble(env, argument, "networkTimeout", decimal)) request.networkTimeout = decimal; + namedString(env, argument, "tlsCaFile", request.tlsCaFile); bool enabled = true; if (namedBool(env, argument, "subtitleEnabled", enabled)) request.subtitleEnabled = enabled ? 1 : 0; @@ -527,7 +528,6 @@ void onTouch(OH_NativeXComponent*, void*) {} napi_value Init(napi_env env, napi_value exports) { - gPlayer.initialize(); napi_value xcomponent = nullptr; if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &xcomponent) == napi_ok) { OH_NativeXComponent* component = nullptr;