fix: pass emby tls ca bundle to core

This commit is contained in:
BoatingCloud committed 2026-08-31 23:34:32 +08:00
1 parent 67ed15e5d4
commit 1b24aebe77
9 files changed
+34 -7

No files matched your search

+1
View File
@@ -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 {
+6
View File
@@ -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
+1 -1
View File
@@ -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/真机/正式签名仍待完成。
+3 -3
View File
@@ -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`。
@@ -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;
@@ -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<std::mutex> lock(mutex_);
+1
View File
@@ -40,6 +40,7 @@ struct PlaybackRequestModel {
int subtitleTrackId = -1;
double playbackSpeed = 0.0;
double networkTimeout = 0.0;
std::string tlsCaFile;
};
struct PlaybackTrack {
+18 -2
View File
@@ -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; } }
+1 -1
View File
@@ -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;