feat: unify phase5 playback lab with stable core api
This commit is contained in:
1 parent
0db086a1e9
commit
f9d1481fb8
16 files changed
+590
-846
No files matched your search
@@ -6,4 +6,6 @@
|
||||
|
||||
网络诊断只报告实际可取得的 mpv 属性:协议、host、networkState、cache duration/bytes、demuxer、container、当前 bitrate,以及从实际日志识别到的 HTTP status/redirect count。没有对应属性或日志时返回 0/空值,不做推断。
|
||||
|
||||
HLS Master/Variant 的当前媒体分辨率来自 video-params/width/height,码率来自 demux-bitrate;当前 MPV 没有独立稳定的“variant 名称”属性,因此不新增伪造字段。
|
||||
|
||||
DNS、TCP connect、connection reset、timeout 和 FFmpeg open failure 统一归入 NETWORK,并保留脱敏原始消息;TLS handshake/certificate 归入 TLS,HTTP status 归入 HTTP,demux/decoder 错误不伪装成网络错误。
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## 脱敏规则
|
||||
|
||||
Authorization、Proxy-Authorization、Cookie、X-Api-Key 以及 URL 中的 token、auth、key、signature、sig、expires 参数在错误文本和日志中替换为 [REDACTED]。原始 mpv 日志不得直接写入 OH 日志。
|
||||
Authorization、Proxy-Authorization、Cookie、X-Api-Key 以及 URL 中的 token、access_token、auth、key、api_key、apikey、signature、x-amz-signature、sig、expires 参数在错误文本和日志中替换为 [REDACTED]。原始 mpv 日志不得直接写入 OH 日志。
|
||||
|
||||
## TLS、重定向与凭证
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
- 请求字段、会话 generation、线程安全控制、稳定状态/错误、轨道、媒体信息和网络缓存诊断已实现。
|
||||
- Playback Lab 已接入请求对象、脱敏 header 编辑器、速度/音量/静音、外部字幕、轨道/媒体/网络面板。
|
||||
- 文档、测试矩阵和构建矩阵已补齐。
|
||||
- default/software/FFmpeg-Harmony 三套 AArch64 libmpv 构建、稳定 API 符号校验与 Playback Lab Native/ArkTS 编译已通过;PackageHap 因环境缺少 Java Runtime 保持待补。
|
||||
- default/software/FFmpeg-Harmony 三套 AArch64 libmpv 构建、稳定 API 符号校验与 Playback Lab Native/ArkTS/PackageHap 编译已通过;使用 DevEco 自带 JBR 的 `--no-daemon` 流程生成 unsigned HAP,并由 devecocli 安装模拟器。
|
||||
- 模拟器已启动 Playback Lab,完成本地 H.264/AAC MP4 首帧、软件解码、播放结束状态和渲染指标 smoke;HTTPS/HLS/网络字幕/鉴权资源仍需真实网络源。
|
||||
- 当前构建日志显示 iconv feature disabled,因此字幕适配器只承诺 UTF-8;GBK/Big5/Shift-JIS 会返回稳定 SUBTITLE 错误。
|
||||
|
||||
尚待真实环境确认:HTTPS/HLS、重定向 header 继承、鉴权资源、网络字幕和物理设备音频/视频输出。不可从本地编译结果推断这些运行时结论。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
| 场景 | 方式 | 结果 |
|
||||
|---|---|---|
|
||||
| 本地 MP4 H264/AAC | Playback Lab | Native/ArkTS 编译通过;设备运行待确认 |
|
||||
| 本地 MP4 H264/AAC | Playback Lab | 模拟器安装启动并完成首帧/播放/结束 smoke;网络与实体机待确认 |
|
||||
| 本地 MKV H264/AAC/SRT | Playback Lab | API/UI 编译覆盖;设备运行待确认 |
|
||||
| 本地 MKV HEVC/AAC/ASS(software) | software mpv | AArch64 构建通过;设备运行待确认 |
|
||||
| HTTP MP4 | 受控测试源 | 网络环境待确认 |
|
||||
@@ -26,6 +26,6 @@
|
||||
| software MPV(OHCodec disabled) | scripts/build-mpv-ohos-software.sh | AArch64 ELF 校验通过 |
|
||||
| FFmpeg-Harmony | scripts/build-mpv-ohos-ffmpeg-harmony.sh | AArch64 ELF 与符号校验通过 |
|
||||
| audio-enabled MPV | 默认脚本 `harmony-audio=enabled` | 与默认构建一并通过 |
|
||||
| Playback Lab HAP | hvigor assembleHap | CompileArkTS/Native 通过;PackageHap 缺 Java Runtime |
|
||||
| Playback Lab HAP | hvigor assembleHap --no-daemon | CompileArkTS/Native/PackageHap 通过;无正式签名,devecocli 已安装模拟器 |
|
||||
|
||||
产品禁止以 UI 定时器伪造 buffering;只有 mpv 事件/属性可改变对应诊断字段。
|
||||
@@ -5,3 +5,7 @@ mp_harmony_playback.h 是上层唯一稳定契约,提供 PlaybackRequest、Pla
|
||||
公开控制命令:Play、Pause、TogglePause、SeekAbsolute、SeekRelative、SeekPercent、Stop、SetSpeed、SetVolume、Mute、SetAudioEnabled、SelectAudioTrack、SelectSubtitleTrack、DisableSubtitle、AddSubtitle、RemoveSubtitle、SetSubtitleDelay、SetSubtitleScale、SetSubtitlePosition、SetAssOverride、GetTracks、GetMediaInfo、GetNetworkInfo、GetPlaybackState。
|
||||
|
||||
所有控制入口串行化;请求中的字符串在调用返回前复制到 mpv 所需的临时节点,调用者可以立即释放输入内存。session id 随每次 load 增长,旧 playlist event 不得改变新会话状态。
|
||||
|
||||
Playback Lab 的 C++ 外壳只做 NAPI/渲染适配,所有请求、控制和状态快照均委托上述 C API;渲染器使用私有句柄桥接,不进入公共 ABI。
|
||||
|
||||
当前 OHOS 构建未启用 iconv,外挂字幕 encoding 只接受 UTF-8/UTF8;其他编码通过稳定 SUBTITLE 错误拒绝。
|
||||
@@ -1,7 +1,7 @@
|
||||
# 字幕管线
|
||||
|
||||
内部字幕和外部字幕统一由 mpv/libass 管线处理。外部源通过 sub-add 加入当前 session,可选中或移除,不销毁播放 session;sub-remove 仅移除指定轨道。
|
||||
内部字幕和外部字幕统一由 mpv/libass 管线处理。外部源通过 sub-add 加入当前 session,可选中或移除,不销毁播放 session;sub-remove 仅移除指定轨道。language/title 通过 sub-add 元数据传入,forced/default 映射为 mpv 的同名 track flags。
|
||||
|
||||
目标格式:SRT、ASS/SSA、WebVTT;ASS 默认保留原样式与 libass override 语义。上层可开关、选择字幕、调整 delay、scale、position、override(具体控件按实际可用属性开放)。
|
||||
|
||||
编码优先 UTF-8;GBK、Big5、Shift-JIS 只在现有 FFmpeg/mpv/iconv 能识别时启用。当前适配器拒绝未验证编码并返回稳定 SUBTITLE 错误,不静默按错误编码显示。Playback Lab 同时提供网络 URL 与本地 file 路径入口。
|
||||
当前 OHOS 构建未启用 iconv,因此只接受 UTF-8/UTF8;GBK、Big5、Shift-JIS 等输入明确返回稳定 SUBTITLE 错误,不静默走 Latin-1 fallback。启用并验证 iconv 后再扩展编码白名单。Playback Lab 同时提供网络 URL 与本地 file 路径入口。
|
||||
@@ -15,3 +15,15 @@
|
||||
文件变更: player/harmony_playback.c, playback_lab/entry/src/main/cpp/player.cpp, DOCS/PHASE5_TEST_MATRIX.md, DOCS/PHASE5_STATUS.md, memory/projects.md
|
||||
教训: 网络 HTTP status 采集必须覆盖 cplayer/ffmpeg 日志前缀;HLS/鉴权/字幕运行证据仍需真实网络与可安装 HAP,不能用编译结果替代。
|
||||
标签: #phase5 #build #emulator #hap #java-runtime #network-pending
|
||||
|
||||
[PROJECT:Velix-MPV-Core] Phase 5 最终实现与模拟器 smoke
|
||||
结论: C++ Playback Lab 已统一委托稳定 C 播放核心;default/software/FFmpeg-Harmony 构建、PackageHap(DevEco JBR)和 devecocli 安装启动通过;模拟器完成本地 H.264/AAC 首帧、软件解码、渲染指标与结束状态验证,真实 HTTP/HTTPS/HLS/网络字幕仍待受控源。
|
||||
文件变更: playback_lab/entry/src/main/cpp/{player.h,player.cpp,harmony_playback_internal.h}, player/harmony_playback.c, DOCS/PHASE5_*.md, memory/*
|
||||
教训: hvigor 默认守护进程会复用无 Java 环境;使用 DevEco JBR 并加 `--no-daemon` 才能稳定执行 PackageHap。设备 UI smoke 不能替代真实网络验收。
|
||||
标签: #phase5 #c-api #playback-lab #hap #emulator #software-decode #network-pending
|
||||
|
||||
[PROJECT:Velix-MPV-Core] Phase 5 收尾校验与推送准备
|
||||
结论: 修复普通相对 seek 的无效 default 参数,并保留 EOF 后已获取的媒体快照;三套 AArch64 产物、符号/ELF 校验和 HAP 产物均已复核,准备提交远端。
|
||||
文件变更: player/harmony_playback.c, memory/2026-08-29.md
|
||||
教训: mpv seek 未指定 exact/keyframe 时必须省略 flags 参数;EOF 后属性可能短暂不可读,诊断快照不能无条件清零。
|
||||
标签: #phase5 #seek #eof #validation #push
|
||||
@@ -0,0 +1,11 @@
|
||||
# Velix-MPV-Core 记忆索引
|
||||
|
||||
- 项目:地产运维中台媒体核心的 OpenHarmony MPV 端口。
|
||||
- 当前阶段:Phase 5 CODE COMPLETE;真实 HTTP/HTTPS/HLS、网络字幕、鉴权资源和实体机验收待补。
|
||||
- 公共入口:`include/mpv/harmony_playback.h`,稳定 C API 不暴露 MPV/FFmpeg 内部类型。
|
||||
- Playback Lab:C++ 仅做 NAPI/渲染适配,核心请求、控制、状态统一委托 C API。
|
||||
- 默认策略:软件解码;`harmony-ohcodec-gl` mapper 保持禁用,不声明零拷贝 GPU 能力。
|
||||
- 构建:default/software/FFmpeg-Harmony AArch64 通过;HAP 使用 DevEco JBR + `--no-daemon` 通过。
|
||||
- 设备:模拟器 `127.0.0.1:5555`,Playback Lab 已安装启动并完成本地 MP4 smoke。
|
||||
- 部署约定:服务器只按 `deploy.sh`、`deploy.sh status/logs/clean/clean-all` 执行。
|
||||
- 详细状态:`memory/projects.md`;基础设施:`memory/infra.md`;教训:`memory/lessons.md`。
|
||||
+2
-1
@@ -15,11 +15,12 @@
|
||||
- hdc:`/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc`
|
||||
- 模拟器目标:`127.0.0.1:5555`,HarmonyOS 6.1.0.126 / API 24
|
||||
- Playback Lab 构建:在 `playback_lab/` 执行 `scripts/stage-mpv.sh` 后用 hvigor `--no-daemon assembleHap --mode module -p product=default`
|
||||
- HAP 构建:当前环境无可用 Java Runtime,hvigor 可完成 Native/ArkTS 编译但 PackageHap 阶段失败;安装 JDK 后重试。
|
||||
- HAP 构建:使用 DevEco 自带 `/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home`,并以 `hvigorw.js assembleHap --no-daemon --mode module -p product=default` 完成 PackageHap;默认未配置正式签名。
|
||||
- OHAudio:API 24 headers `ohaudio/native_audiostreambuilder.h` / `native_audiorenderer.h` / `native_audiostream_base.h`,动态库 `libohaudio.so`;Meson 使用 `-Dharmony-audio=enabled`。
|
||||
- Phase 3 HAP:`playback_lab/entry/build/default/outputs/default/entry-default-unsigned.hap`;模拟器 H.264/AAC smoke target `127.0.0.1:5555`。
|
||||
- Phase 4 构建入口:`scripts/build-mpv-ohos.sh`(OHCodec enabled)、`scripts/build-mpv-ohos-software.sh`(OHCodec disabled)、`scripts/build-all-ohos.sh`(全量矩阵);GL API probe 通过单独 Meson `harmony-ohcodec-gl=enabled` 构建验证。
|
||||
- Phase 4 产物:`build/ohos-arm64/prefix/lib/libmpv.so.2.5.0`;软件构建位于 `build/ohos-arm64/build/mpv-software/libmpv.so.2.5.0`;HAP 位于 `playback_lab/entry/build/default/outputs/default/entry-default-unsigned.hap`。
|
||||
- 模拟器验证:`devecocli run --module entry --device 127.0.0.1:5555 --skip-build` 成功安装/启动;Playback Lab 本地 H.264/AAC MP4 首帧与结束状态可见。
|
||||
- FFmpeg-Harmony:`../Velix-FFmpeg-Harmony`,origin `https://git.leee.xin/leeexin/Velix-FFmpeg-Harmony.git`,upstream `https://github.com/FFmpeg/FFmpeg.git`,branch `harmony-ohcodec-export`;prefix `../Velix-FFmpeg-Harmony/build/ohos-arm64/prefix`。
|
||||
- MPV fork 接入:`scripts/build-mpv-ohos-ffmpeg-harmony.sh`;可用 `FFMPEG_PREFIX` 与 `MPV_BUILD_ROOT` 覆盖默认路径,默认 MPV/软件脚本不变。
|
||||
- MPV fork 输出:切换脚本默认安装到 `build/ohos-arm64/prefix-mpv-ffmpeg-harmony`,不会覆盖默认 `prefix`。
|
||||
+3
-3
@@ -4,11 +4,11 @@
|
||||
|
||||
- 状态:Phase 5 CODE COMPLETE / emulator-network validation pending;GPU INTEROP MAPPER 未进入;默认软件解码与网络播放公共 API 已完成,实体机/真实网络验收未完成。
|
||||
- 已完成:远程闭环、固定依赖、隔离工具链、C/C++ 探针、FFmpeg/OpenSSL/字体栈/libplacebo、AArch64 `libmpv.so.2.5.0`、ABI/符号/污染/许可证审计。
|
||||
- 产物:`build/ohos-arm64/prefix/lib/libmpv.so.2.5.0`,当前重建 34,340,576 bytes;切换构建位于 `build/ohos-arm64/prefix-mpv-ffmpeg-harmony/lib/libmpv.so.2.5.0`;DT_NEEDED 含 `libohaudio.so`。
|
||||
- 产物:`build/ohos-arm64/prefix/lib/libmpv.so.2.5.0`,当前重建 34,341,696 bytes;切换构建位于 `build/ohos-arm64/prefix-mpv-ffmpeg-harmony/lib/libmpv.so.2.5.0`;DT_NEEDED 含 `libohaudio.so`。
|
||||
- 已完成:独立 HAP、XComponent/OHNativeWindow、EGL ES3、libmpv OpenGL Render、软件解码本地 MP4 首帧、播放控制与释放重入。
|
||||
- 已完成:`ao_harmony` PCM float32→S16 fallback、采样率/mono-stereo 协商、pause/resume/stop/flush/release、timestamp epoch、starvation/flush metrics;历史 HAP Native/ArkTS 通过,当前 Phase 5 PackageHap 仍受 Java Runtime 环境阻断。
|
||||
- 已完成:`ao_harmony` PCM float32→S16 fallback、采样率/mono-stereo 协商、pause/resume/stop/flush/release、timestamp epoch、starvation/flush metrics;Phase 5 HAP 已用 DevEco JBR 完成打包并安装模拟器。
|
||||
- 已完成:OHCodec H.264/HEVC capability 查询、`hwdec=ohcodec` direct-surface 实验驱动、显式软件回退、Playback Lab requested/actual/metrics 诊断、软件/硬解/GL probe 构建矩阵;FFmpeg 未修改。
|
||||
- 边界:官方 OHCodec 上游没有非 surface 的可导入硬件帧路径,EGL/NativeBuffer 仅保留禁用默认探针;无零拷贝、HDR 或硬件帧产出声明。
|
||||
- Phase 4.5:`../Velix-FFmpeg-Harmony` branch `harmony-ohcodec-export` 已推送 tip `990614364f`;`scripts/build-mpv-ohos-ffmpeg-harmony.sh` 已消费其 prefix 构建 MPV。
|
||||
- 待办:实体机 H.264/HEVC capability、surface 生命周期/stride/crop/fence、真实 GPU 互操作与 A/V sync;报告见两个仓库的 `DOCS/OHCODEC_*.md` 与 `DOCS/PHASE4_5_STATUS.md`。
|
||||
- Phase 5 已完成:稳定 C API、请求/headers 会话隔离、错误/状态、轨道/媒体/网络/字幕控制、Playback Lab 诊断 UI 与文档;默认/软件/FFmpeg-Harmony 构建通过,HAP 打包缺 Java,HTTP/HLS/字幕运行验证待补。
|
||||
- Phase 5 已完成:稳定 C API、请求/headers 会话隔离、错误/状态、轨道/媒体/网络/字幕控制、Playback Lab 诊断 UI 与文档;C++ 外壳已统一委托 C API,默认/软件/FFmpeg-Harmony 构建及 HAP 打包通过;模拟器已完成本地 MP4 首帧/播放结束 smoke,HTTP/HLS/网络字幕/鉴权运行验证待补。
|
||||
@@ -97,7 +97,7 @@ bool getNamedBool(napi_env env, napi_value object, const char* name, bool& value
|
||||
napi_get_value_bool(env, property, &value) == napi_ok;
|
||||
}
|
||||
|
||||
bool getRequest(napi_env env, napi_callback_info info, PlaybackRequest& request)
|
||||
bool getRequest(napi_env env, napi_callback_info info, PlaybackRequestModel& request)
|
||||
{
|
||||
size_t argc = 1;
|
||||
napi_value argv[1] = {nullptr};
|
||||
@@ -217,7 +217,7 @@ napi_value undefined(napi_env env)
|
||||
|
||||
napi_value load(napi_env env, napi_callback_info info)
|
||||
{
|
||||
PlaybackRequest request;
|
||||
PlaybackRequestModel request;
|
||||
if (getRequest(env, info, request)) gPlayer.load(request);
|
||||
return undefined(env);
|
||||
}
|
||||
@@ -469,6 +469,13 @@ napi_value getState(napi_env env, napi_callback_info)
|
||||
napi_set_named_property(env, object, "lastError", value);
|
||||
napi_create_string_utf8(env, state.errorCode.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "errorCode", value);
|
||||
napi_value error = nullptr;
|
||||
napi_create_object(env, &error);
|
||||
napi_create_string_utf8(env, state.errorCode.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, error, "code", value);
|
||||
napi_create_string_utf8(env, state.lastError.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, error, "rawMessage", value);
|
||||
napi_set_named_property(env, object, "error", error);
|
||||
napi_create_double(env, static_cast<double>(state.sessionId), &value);
|
||||
napi_set_named_property(env, object, "sessionId", value);
|
||||
napi_create_double(env, state.speed, &value);
|
||||
@@ -508,6 +515,21 @@ napi_value getState(napi_env env, napi_callback_info)
|
||||
napi_create_int64(env, state.network.currentBitrate, &value);
|
||||
napi_set_named_property(env, network, "currentBitrate", value);
|
||||
napi_set_named_property(env, object, "network", network);
|
||||
napi_value metrics = nullptr;
|
||||
napi_create_object(env, &metrics);
|
||||
napi_create_int64(env, state.dropped, &value);
|
||||
napi_set_named_property(env, metrics, "droppedFrames", value);
|
||||
napi_create_double(env, static_cast<double>(state.renderCount), &value);
|
||||
napi_set_named_property(env, metrics, "renderCount", value);
|
||||
napi_get_boolean(env, state.network.buffering, &value);
|
||||
napi_set_named_property(env, metrics, "buffering", value);
|
||||
napi_create_double(env, state.network.cacheDuration, &value);
|
||||
napi_set_named_property(env, metrics, "cacheDuration", value);
|
||||
napi_create_int64(env, state.network.cacheBytes, &value);
|
||||
napi_set_named_property(env, metrics, "cacheBytes", value);
|
||||
napi_create_int64(env, state.network.currentBitrate, &value);
|
||||
napi_set_named_property(env, metrics, "currentBitrate", value);
|
||||
napi_set_named_property(env, object, "metrics", metrics);
|
||||
napi_value media = nullptr;
|
||||
napi_create_object(env, &media);
|
||||
napi_create_string_utf8(env, state.media.container.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "mpv/harmony_playback.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Private renderer bridge; never install this declaration as public API. */
|
||||
mpv_handle *mp_harmony_playback_internal_handle(mp_harmony_player *player);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include "harmony_playback_internal.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
struct PlaybackHeader {
|
||||
@@ -25,7 +24,7 @@ struct PlaybackSubtitleSource {
|
||||
bool isDefault = false;
|
||||
};
|
||||
|
||||
struct PlaybackRequest {
|
||||
struct PlaybackRequestModel {
|
||||
std::string url;
|
||||
std::string title;
|
||||
int64_t startPositionMs = -1;
|
||||
@@ -138,7 +137,7 @@ public:
|
||||
bool setAudioEnabled(bool enabled);
|
||||
bool setHardwareDecode(bool enabled);
|
||||
void load(const std::string& url);
|
||||
bool load(const PlaybackRequest& request);
|
||||
bool load(const PlaybackRequestModel& request);
|
||||
void play();
|
||||
void pause();
|
||||
void togglePause();
|
||||
@@ -165,32 +164,16 @@ public:
|
||||
void markFrameRendered();
|
||||
|
||||
private:
|
||||
void eventLoop();
|
||||
void observeProperties();
|
||||
void setState(const char* state);
|
||||
void setError(const std::string& error);
|
||||
void updateProperty(uint64_t userdata, const mpv_event_property* property);
|
||||
void syncHwdecStatsLocked();
|
||||
void handleLogMessage(const mpv_event_log_message* message);
|
||||
void refreshTracksLocked();
|
||||
void refreshMediaLocked();
|
||||
void refreshNetworkLocked();
|
||||
bool setHeadersLocked(const PlaybackRequest& request);
|
||||
static std::string redactSensitive(const std::string& value);
|
||||
static std::string classifyError(const std::string& value);
|
||||
void syncCoreSnapshot();
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
mutable std::mutex apiMutex_;
|
||||
PlaybackSnapshot snapshot_;
|
||||
mp_harmony_player* corePlayer_ = nullptr;
|
||||
mpv_handle* handle_ = nullptr;
|
||||
std::thread eventThread_;
|
||||
std::atomic<bool> stopEvents_{false};
|
||||
std::atomic<bool> initialized_{false};
|
||||
uint64_t sessionId_ = 0;
|
||||
int64_t playlistEntryId_ = 0;
|
||||
bool awaitingStart_ = false;
|
||||
std::chrono::steady_clock::time_point startedAt_;
|
||||
static constexpr uint64_t kHwdecCurrent = 6;
|
||||
static constexpr uint64_t kHwdecInterop = 7;
|
||||
static constexpr uint64_t kDecoder = 8;
|
||||
};
|
||||
@@ -41,6 +41,8 @@ interface PlaybackState {
|
||||
gpuInteropFrameCount: number;
|
||||
sessionId: number;
|
||||
errorCode: string;
|
||||
error: PlaybackErrorState;
|
||||
metrics: PlaybackMetricsState;
|
||||
speed: number;
|
||||
volume: number;
|
||||
muted: boolean;
|
||||
@@ -52,6 +54,20 @@ interface PlaybackState {
|
||||
tracks: TrackState[];
|
||||
}
|
||||
|
||||
interface PlaybackErrorState {
|
||||
code: string;
|
||||
rawMessage: string;
|
||||
}
|
||||
|
||||
interface PlaybackMetricsState {
|
||||
droppedFrames: number;
|
||||
renderCount: number;
|
||||
buffering: boolean;
|
||||
cacheDuration: number;
|
||||
cacheBytes: number;
|
||||
currentBitrate: number;
|
||||
}
|
||||
|
||||
interface NetworkState {
|
||||
protocol: string;
|
||||
host: string;
|
||||
@@ -83,6 +99,7 @@ interface MediaState {
|
||||
bitDepth: number;
|
||||
audioSampleRate: number;
|
||||
audioChannels: number;
|
||||
audioChannelLayout: string;
|
||||
trackCount: number;
|
||||
subtitleCount: number;
|
||||
}
|
||||
@@ -165,12 +182,14 @@ struct Index {
|
||||
hardwareFrameCount: 0, softwareFrameCount: 0, hwFallbackCount: 0,
|
||||
nativeBufferImportCount: 0, nativeBufferImportFailure: 0, surfacePresentCount: 0,
|
||||
gpuInteropFrameCount: 0,
|
||||
sessionId: 0, errorCode: 'NONE', speed: 1, volume: 100, muted: false,
|
||||
sessionId: 0, errorCode: 'NONE', error: { code: 'NONE', rawMessage: '' },
|
||||
metrics: { droppedFrames: 0, renderCount: 0, buffering: false, cacheDuration: 0, cacheBytes: 0, currentBitrate: 0 },
|
||||
speed: 1, volume: 100, muted: false,
|
||||
subtitleDelayMs: 0, subtitleScale: 1, subtitlePosition: 100,
|
||||
network: { protocol: '', host: '', networkState: '', httpStatus: 0, redirectCount: 0,
|
||||
buffering: false, cacheDuration: 0, cacheBytes: 0, demuxer: '', container: '', currentBitrate: 0 },
|
||||
media: { container: '', demuxer: '', duration: 0, videoCodec: '', audioCodec: '', pixelFormat: '', colorPrimaries: '', colorTransfer: '', colorMatrix: '',
|
||||
width: 0, height: 0, fps: 0, bitrate: 0, bitDepth: 0, audioSampleRate: 0, audioChannels: 0,
|
||||
width: 0, height: 0, fps: 0, bitrate: 0, bitDepth: 0, audioSampleRate: 0, audioChannels: 0, audioChannelLayout: '',
|
||||
trackCount: 0, subtitleCount: 0 },
|
||||
tracks: []
|
||||
};
|
||||
|
||||
+116
-35
@@ -14,6 +14,7 @@
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
struct mp_harmony_player {
|
||||
pthread_mutex_t lock;
|
||||
@@ -23,6 +24,7 @@ struct mp_harmony_player {
|
||||
int stop_events;
|
||||
mpv_handle *handle;
|
||||
uint64_t session_id;
|
||||
char current_url[MP_HARMONY_URL];
|
||||
int64_t playlist_entry_id;
|
||||
int awaiting_start;
|
||||
mp_harmony_playback_snapshot snapshot;
|
||||
@@ -38,6 +40,9 @@ enum {
|
||||
|
||||
static int set_headers_locked(mp_harmony_player *p, const mp_harmony_playback_request *request);
|
||||
|
||||
/* Private renderer bridge; intentionally absent from the public header. */
|
||||
MP_HARMONY_EXPORT mpv_handle *mp_harmony_playback_internal_handle(mp_harmony_player *player);
|
||||
|
||||
static void copy_text(char *dst, size_t size, const char *src)
|
||||
{
|
||||
if (!dst || size == 0) return;
|
||||
@@ -60,10 +65,21 @@ static int text_has(const char *text, const char *needle)
|
||||
static int supported_subtitle_encoding(const char *encoding)
|
||||
{
|
||||
if (!encoding || !encoding[0]) return 1;
|
||||
return strcasecmp(encoding, "utf-8") == 0 || strcasecmp(encoding, "utf8") == 0 ||
|
||||
strcasecmp(encoding, "gbk") == 0 || strcasecmp(encoding, "gb18030") == 0 ||
|
||||
strcasecmp(encoding, "big5") == 0 || strcasecmp(encoding, "shift-jis") == 0 ||
|
||||
strcasecmp(encoding, "sjis") == 0;
|
||||
/* The current OHOS matrix has no iconv feature; accept UTF-8 only so
|
||||
* unsupported input cannot silently fall back to mojibake. */
|
||||
return strcasecmp(encoding, "utf-8") == 0 || strcasecmp(encoding, "utf8") == 0;
|
||||
}
|
||||
|
||||
static int supported_ass_override(const char *mode)
|
||||
{
|
||||
return mode && (strcmp(mode, "no") == 0 || strcmp(mode, "yes") == 0 ||
|
||||
strcmp(mode, "force") == 0 || strcmp(mode, "scale") == 0 ||
|
||||
strcmp(mode, "strip") == 0);
|
||||
}
|
||||
|
||||
static int is_cookie_header(const char *name)
|
||||
{
|
||||
return name && strcasecmp(name, "cookie") == 0;
|
||||
}
|
||||
|
||||
static void redact_text(char *dst, size_t size, const char *src)
|
||||
@@ -234,8 +250,8 @@ static int node_int(mpv_node *node)
|
||||
static void refresh_tracks_locked(mp_harmony_player *p)
|
||||
{
|
||||
mpv_node node = {0};
|
||||
p->track_count = 0;
|
||||
if (mpv_get_property(p->handle, "track-list", MPV_FORMAT_NODE, &node) < 0) return;
|
||||
p->track_count = 0;
|
||||
if (node.format == MPV_FORMAT_NODE_ARRAY && node.u.list) {
|
||||
for (int i = 0; i < node.u.list->num && p->track_count < MP_HARMONY_MAX_TRACKS; i++) {
|
||||
mpv_node *item = &node.u.list->values[i];
|
||||
@@ -273,15 +289,15 @@ static void refresh_tracks_locked(mp_harmony_player *p)
|
||||
static void refresh_media_locked(mp_harmony_player *p)
|
||||
{
|
||||
char *v = mpv_get_property_string(p->handle, "file-format");
|
||||
copy_text(p->snapshot.media.container, sizeof(p->snapshot.media.container), v); if (v) mpv_free(v);
|
||||
if (v && v[0]) copy_text(p->snapshot.media.container, sizeof(p->snapshot.media.container), v); if (v) mpv_free(v);
|
||||
v = mpv_get_property_string(p->handle, "current-demuxer");
|
||||
copy_text(p->snapshot.media.demuxer, sizeof(p->snapshot.media.demuxer), v); if (v) mpv_free(v);
|
||||
if (v && v[0]) copy_text(p->snapshot.media.demuxer, sizeof(p->snapshot.media.demuxer), v); if (v) mpv_free(v);
|
||||
v = mpv_get_property_string(p->handle, "video-codec");
|
||||
copy_text(p->snapshot.media.video_codec, sizeof(p->snapshot.media.video_codec), v); if (v) mpv_free(v);
|
||||
if (v && v[0]) copy_text(p->snapshot.media.video_codec, sizeof(p->snapshot.media.video_codec), v); if (v) mpv_free(v);
|
||||
v = mpv_get_property_string(p->handle, "audio-codec");
|
||||
copy_text(p->snapshot.media.audio_codec, sizeof(p->snapshot.media.audio_codec), v); if (v) mpv_free(v);
|
||||
if (v && v[0]) copy_text(p->snapshot.media.audio_codec, sizeof(p->snapshot.media.audio_codec), v); if (v) mpv_free(v);
|
||||
v = mpv_get_property_string(p->handle, "video-format");
|
||||
copy_text(p->snapshot.media.pixel_format, sizeof(p->snapshot.media.pixel_format), v); if (v) mpv_free(v);
|
||||
if (v && v[0]) copy_text(p->snapshot.media.pixel_format, sizeof(p->snapshot.media.pixel_format), v); if (v) mpv_free(v);
|
||||
int64_t i64 = 0;
|
||||
if (mpv_get_property(p->handle, "width", MPV_FORMAT_INT64, &i64) >= 0) p->snapshot.media.width = (int)i64;
|
||||
if (mpv_get_property(p->handle, "height", MPV_FORMAT_INT64, &i64) >= 0) p->snapshot.media.height = (int)i64;
|
||||
@@ -290,8 +306,9 @@ static void refresh_media_locked(mp_harmony_player *p)
|
||||
if (mpv_get_property(p->handle, "demux-bitrate", MPV_FORMAT_INT64, &i64) >= 0) p->snapshot.media.bitrate = i64;
|
||||
if (mpv_get_property(p->handle, "container-fps", MPV_FORMAT_DOUBLE, &p->snapshot.media.fps) < 0)
|
||||
p->snapshot.media.fps = 0;
|
||||
if (mpv_get_property(p->handle, "duration", MPV_FORMAT_DOUBLE, &p->snapshot.duration) < 0)
|
||||
p->snapshot.duration = 0;
|
||||
double duration = 0;
|
||||
if (mpv_get_property(p->handle, "duration", MPV_FORMAT_DOUBLE, &duration) >= 0)
|
||||
p->snapshot.duration = duration;
|
||||
p->snapshot.media.duration = p->snapshot.duration;
|
||||
mpv_node node = {0};
|
||||
if (mpv_get_property(p->handle, "video-params", MPV_FORMAT_NODE, &node) >= 0) {
|
||||
@@ -392,8 +409,15 @@ static void *event_loop(void *arg)
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_FILE_LOADED:
|
||||
{
|
||||
char *loaded_url = mpv_get_property_string(p->handle, "stream-open-filename");
|
||||
int matches = !loaded_url || !loaded_url[0] || !p->current_url[0] ||
|
||||
strcmp(loaded_url, p->current_url) == 0;
|
||||
if (loaded_url) mpv_free(loaded_url);
|
||||
if (!matches) break;
|
||||
p->awaiting_start = 0; refresh_media_locked(p); p->snapshot.state = MP_HARMONY_READY;
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_PROPERTY_CHANGE:
|
||||
update_property_locked(p, event->reply_userdata, event->data);
|
||||
break;
|
||||
@@ -432,7 +456,7 @@ mp_harmony_player *mp_harmony_playback_create(void)
|
||||
if (!p) return NULL;
|
||||
if (pthread_mutex_init(&p->lock, NULL) != 0) { free(p); return NULL; }
|
||||
p->lock_ready = 1; p->snapshot.state = MP_HARMONY_IDLE;
|
||||
p->snapshot.error_code = MP_HARMONY_ERROR_NONE; p->snapshot.speed = 1.0;
|
||||
p->snapshot.error_code = MP_HARMONY_ERROR_NONE; p->snapshot.speed = 1.0; p->snapshot.volume = 100;
|
||||
p->snapshot.subtitle_scale = 1.0; p->snapshot.subtitle_position = 100.0;
|
||||
return p;
|
||||
}
|
||||
@@ -466,7 +490,7 @@ int mp_harmony_playback_initialize(mp_harmony_player *p)
|
||||
mpv_observe_property(p->handle, PROP_DURATION, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(p->handle, PROP_PAUSE, "pause", MPV_FORMAT_FLAG);
|
||||
mpv_observe_property(p->handle, PROP_FPS, "estimated-vf-fps", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(p->handle, PROP_DROPPED, "vo-drop-frame-count", MPV_FORMAT_INT64);
|
||||
mpv_observe_property(p->handle, PROP_DROPPED, "frame-drop-count", MPV_FORMAT_INT64);
|
||||
mpv_observe_property(p->handle, PROP_TRACKS, "track-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(p->handle, PROP_CACHE_DURATION, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(p->handle, PROP_CACHE_STATE, "demuxer-cache-state", MPV_FORMAT_NODE);
|
||||
@@ -486,8 +510,14 @@ int mp_harmony_playback_initialize(mp_harmony_player *p)
|
||||
|
||||
static int set_headers_locked(mp_harmony_player *p, const mp_harmony_playback_request *r)
|
||||
{
|
||||
size_t count = r->http_header_count + (r->cookies && r->cookies[0] ? 1 : 0);
|
||||
if (count > MP_HARMONY_MAX_HEADERS) count = MP_HARMONY_MAX_HEADERS;
|
||||
if (!r || (r->http_header_count && !r->http_headers)) return -1;
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < r->http_header_count; i++) {
|
||||
const char *name = r->http_headers[i].name;
|
||||
if (name && name[0] && !(r->cookies && r->cookies[0] && is_cookie_header(name))) count++;
|
||||
}
|
||||
if (r->cookies && r->cookies[0]) count++;
|
||||
if (count > MP_HARMONY_MAX_HEADERS) return -1;
|
||||
mpv_node *values = calloc(count ? count : 1, sizeof(*values));
|
||||
char **owned = calloc(count ? count : 1, sizeof(*owned));
|
||||
if (!values || !owned) { free(values); free(owned); return -1; }
|
||||
@@ -495,15 +525,23 @@ static int set_headers_locked(mp_harmony_player *p, const mp_harmony_playback_re
|
||||
for (size_t i = 0; r->http_headers && i < r->http_header_count && index < count; i++) {
|
||||
const char *name = r->http_headers[i].name ? r->http_headers[i].name : "";
|
||||
const char *value = r->http_headers[i].value ? r->http_headers[i].value : "";
|
||||
if (!name[0] || (r->cookies && r->cookies[0] && is_cookie_header(name))) continue;
|
||||
size_t n = strlen(name) + strlen(value) + 3;
|
||||
owned[index] = malloc(n);
|
||||
if (!owned[index]) continue;
|
||||
if (!owned[index]) {
|
||||
for (size_t j = 0; j < index; j++) free(owned[j]);
|
||||
free(owned); free(values); return -1;
|
||||
}
|
||||
snprintf(owned[index], n, "%s: %s", name, value);
|
||||
values[index].format = MPV_FORMAT_STRING; values[index].u.string = owned[index]; index++;
|
||||
}
|
||||
if (r->cookies && r->cookies[0] && index < count) {
|
||||
size_t n = strlen(r->cookies) + 9; owned[index] = malloc(n);
|
||||
if (owned[index]) { snprintf(owned[index], n, "Cookie: %s", r->cookies); values[index].format = MPV_FORMAT_STRING; values[index].u.string = owned[index]; index++; }
|
||||
if (!owned[index]) {
|
||||
for (size_t j = 0; j < index; j++) free(owned[j]);
|
||||
free(owned); free(values); return -1;
|
||||
}
|
||||
snprintf(owned[index], n, "Cookie: %s", r->cookies); values[index].format = MPV_FORMAT_STRING; values[index].u.string = owned[index]; index++;
|
||||
}
|
||||
mpv_node_list list = {(int)index, values, NULL};
|
||||
mpv_node node = {.format = MPV_FORMAT_NODE_ARRAY, .u.list = &list};
|
||||
@@ -517,6 +555,12 @@ 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->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;
|
||||
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; }
|
||||
@@ -527,6 +571,11 @@ int mp_harmony_playback_load(mp_harmony_player *p, const mp_harmony_playback_req
|
||||
p->snapshot.subtitle_delay_ms = 0;
|
||||
p->snapshot.subtitle_scale = 1.0;
|
||||
p->snapshot.subtitle_position = 100.0;
|
||||
p->snapshot.media = (mp_harmony_media_info){0};
|
||||
p->snapshot.network = (mp_harmony_network_info){0};
|
||||
p->snapshot.metrics = (mp_harmony_playback_metrics){0};
|
||||
p->snapshot.track_count = 0; p->track_count = 0; p->awaiting_start = 1;
|
||||
copy_text(p->current_url, sizeof(p->current_url), r->url);
|
||||
p->snapshot.network.http_status = 0; p->snapshot.network.redirect_count = 0;
|
||||
if (r->subtitle_file_count && !r->subtitle_files) {
|
||||
set_error_locked(p, MP_HARMONY_ERROR_SUBTITLE, "subtitle list is null");
|
||||
@@ -551,9 +600,9 @@ int mp_harmony_playback_load(mp_harmony_player *p, const mp_harmony_playback_req
|
||||
}
|
||||
{ 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; } }
|
||||
if (r->playback_speed > 0) { char speed[32]; snprintf(speed, sizeof(speed), "%.4f", r->playback_speed); if (set_property_string(p, "speed", speed) < 0) { pthread_mutex_unlock(&p->lock); return -1; } p->snapshot.speed = r->playback_speed; }
|
||||
{ char id[32]; snprintf(id, sizeof(id), "%d", r->audio_track_id); set_property_string(p, "aid", r->audio_track_id >= 0 ? id : "auto"); }
|
||||
if (r->subtitle_track_id >= 0) { char id[32]; snprintf(id, sizeof(id), "%d", r->subtitle_track_id); set_property_string(p, "sid", id); }
|
||||
else set_property_string(p, "sid", r->subtitle_enabled == 0 ? "no" : "auto");
|
||||
{ char id[32]; snprintf(id, sizeof(id), "%d", r->audio_track_id); if (set_property_string(p, "aid", r->audio_track_id >= 0 ? id : "auto") < 0) { pthread_mutex_unlock(&p->lock); return -1; } }
|
||||
if (r->subtitle_track_id >= 0) { char id[32]; snprintf(id, sizeof(id), "%d", r->subtitle_track_id); if (set_property_string(p, "sid", id) < 0) { pthread_mutex_unlock(&p->lock); return -1; } }
|
||||
else if (set_property_string(p, "sid", r->subtitle_enabled == 0 ? "no" : "auto") < 0) { pthread_mutex_unlock(&p->lock); return -1; }
|
||||
const char *args[] = {"loadfile", r->url, "replace", NULL};
|
||||
int rc = mpv_command(p->handle, args);
|
||||
if (rc < 0) set_error_locked(p, classify_error(rc, mpv_error_string(rc)), mpv_error_string(rc));
|
||||
@@ -565,9 +614,12 @@ int mp_harmony_playback_load(mp_harmony_player *p, const mp_harmony_playback_req
|
||||
set_error_locked(p, MP_HARMONY_ERROR_SEEK, "initial seek failed");
|
||||
}
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
if (rc >= 0)
|
||||
for (size_t i = 0; i < r->subtitle_file_count; i++) mp_harmony_playback_add_subtitle(p, &r->subtitle_files[i]);
|
||||
return rc;
|
||||
if (rc < 0) return rc;
|
||||
for (size_t i = 0; i < r->subtitle_file_count; i++) {
|
||||
if (r->subtitle_files[i].url && r->subtitle_files[i].url[0] &&
|
||||
mp_harmony_playback_add_subtitle(p, &r->subtitle_files[i]) < 0) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_harmony_playback_play(mp_harmony_player *p) { if (!p) return -1; pthread_mutex_lock(&p->lock); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "pause", "no"); if (r >= 0) p->snapshot.state = MP_HARMONY_PLAYING; pthread_mutex_unlock(&p->lock); return r; }
|
||||
@@ -577,12 +629,12 @@ int mp_harmony_playback_stop(mp_harmony_player *p) { if (!p) return -1; pthread_
|
||||
|
||||
int mp_harmony_playback_seek(mp_harmony_player *p, mp_harmony_seek_mode mode, double value, int exact, int keyframe)
|
||||
{
|
||||
if (!p || value != value) return -1;
|
||||
if (!p || !isfinite(value) || mode < MP_HARMONY_SEEK_RELATIVE || mode > MP_HARMONY_SEEK_PERCENT) return -1;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; }
|
||||
char amount[64]; snprintf(amount, sizeof(amount), "%.6f", value);
|
||||
const char *kind = mode == MP_HARMONY_SEEK_ABSOLUTE ? "absolute" : (mode == MP_HARMONY_SEEK_PERCENT ? "absolute-percent" : "relative");
|
||||
const char *flags = exact ? "exact" : (keyframe ? "keyframes" : "default");
|
||||
const char *flags = exact ? "exact" : (keyframe ? "keyframes" : NULL);
|
||||
const char *args[] = {"seek", amount, kind, flags, NULL};
|
||||
int r = mpv_command(p->handle, args);
|
||||
if (r >= 0) p->snapshot.state = MP_HARMONY_SEEKING;
|
||||
@@ -590,19 +642,39 @@ int mp_harmony_playback_seek(mp_harmony_player *p, mp_harmony_seek_mode mode, do
|
||||
pthread_mutex_unlock(&p->lock); return r;
|
||||
}
|
||||
|
||||
int mp_harmony_playback_set_speed(mp_harmony_player *p, double speed) { if (!p || speed <= 0) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%.4f", speed); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "speed", v); if (r >= 0) p->snapshot.speed = speed; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_volume(mp_harmony_player *p, double volume) { if (!p || volume < 0) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%.3f", volume); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "volume", v); if (r >= 0) p->snapshot.volume = (int)volume; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_speed(mp_harmony_player *p, double speed) { if (!p || !isfinite(speed) || speed <= 0) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%.4f", speed); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "speed", v); if (r >= 0) p->snapshot.speed = speed; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_volume(mp_harmony_player *p, double volume) { if (!p || !isfinite(volume) || volume < 0) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%.3f", volume); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "volume", v); if (r >= 0) p->snapshot.volume = (int)volume; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_mute(mp_harmony_player *p, int muted) { if (!p) return -1; pthread_mutex_lock(&p->lock); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "mute", muted ? "yes" : "no"); if (r >= 0) p->snapshot.muted = muted != 0; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_audio_enabled(mp_harmony_player *p, int enabled) { if (!p) return -1; pthread_mutex_lock(&p->lock); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "aid", enabled ? "auto" : "no"); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_select_audio(mp_harmony_player *p, int id) { if (!p) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%d", id); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "aid", v); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_select_subtitle(mp_harmony_player *p, int id) { if (!p) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%d", id); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sid", v); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_select_audio(mp_harmony_player *p, int id) { if (!p || id < 0) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%d", id); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "aid", v); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_select_subtitle(mp_harmony_player *p, int id) { if (!p || id < 0) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%d", id); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sid", v); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_disable_subtitle(mp_harmony_player *p) { if (!p) return -1; pthread_mutex_lock(&p->lock); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sid", "no"); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_add_subtitle(mp_harmony_player *p, const mp_harmony_subtitle_source *s) { if (!p || !s || !s->url || !s->url[0]) return -1; pthread_mutex_lock(&p->lock); if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; } if (!supported_subtitle_encoding(s->encoding)) { set_error_locked(p, MP_HARMONY_ERROR_SUBTITLE, "unsupported subtitle encoding"); pthread_mutex_unlock(&p->lock); return -1; } if (s->encoding && s->encoding[0]) set_property_string(p, "sub-codepage", s->encoding); const char *flags = (s->select || s->is_default) ? "select" : "auto"; const char *args[] = {"sub-add", s->url, flags, s->title ? s->title : "", s->language ? s->language : "", NULL}; int r = mpv_command(p->handle, args); if (r < 0) set_error_locked(p, MP_HARMONY_ERROR_SUBTITLE, mpv_error_string(r)); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_add_subtitle(mp_harmony_player *p, const mp_harmony_subtitle_source *s)
|
||||
{
|
||||
if (!p || !s || !s->url || !s->url[0]) return -1;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; }
|
||||
if (!supported_subtitle_encoding(s->encoding)) {
|
||||
set_error_locked(p, MP_HARMONY_ERROR_SUBTITLE, "unsupported subtitle encoding");
|
||||
pthread_mutex_unlock(&p->lock); return -1;
|
||||
}
|
||||
if (s->encoding && s->encoding[0] && set_property_string(p, "sub-codepage", s->encoding) < 0) {
|
||||
pthread_mutex_unlock(&p->lock); return -1;
|
||||
}
|
||||
char flags[64];
|
||||
snprintf(flags, sizeof(flags), "%s%s%s", s->select ? "select" : "auto",
|
||||
s->forced ? "+forced" : "", s->is_default ? "+default" : "");
|
||||
const char *args[] = {"sub-add", s->url, flags, s->title ? s->title : "",
|
||||
s->language ? s->language : "", NULL};
|
||||
int r = mpv_command(p->handle, args);
|
||||
if (r < 0) set_error_locked(p, MP_HARMONY_ERROR_SUBTITLE, mpv_error_string(r));
|
||||
pthread_mutex_unlock(&p->lock); return r;
|
||||
}
|
||||
int mp_harmony_playback_remove_subtitle(mp_harmony_player *p, int id) { if (!p) return -1; pthread_mutex_lock(&p->lock); char v[32]; snprintf(v, sizeof(v), "%d", id); int r = -1; if (ensure_ready_locked(p) == 0) { const char *args[] = {"sub-remove", v, NULL}; r = mpv_command(p->handle, args); } if (r < 0) set_error_locked(p, MP_HARMONY_ERROR_SUBTITLE, mpv_error_string(r)); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_subtitle_delay(mp_harmony_player *p, double seconds) { if (!p || seconds != seconds) return -1; pthread_mutex_lock(&p->lock); char value[32]; snprintf(value, sizeof(value), "%.4f", seconds); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-delay", value); if (r >= 0) p->snapshot.subtitle_delay_ms = (int64_t)(seconds * 1000.0); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_subtitle_scale(mp_harmony_player *p, double scale) { if (!p || scale <= 0) return -1; pthread_mutex_lock(&p->lock); char value[32]; snprintf(value, sizeof(value), "%.4f", scale); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-scale", value); if (r >= 0) p->snapshot.subtitle_scale = scale; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_subtitle_position(mp_harmony_player *p, double position) { if (!p || position < 0 || position > 100) return -1; pthread_mutex_lock(&p->lock); char value[32]; snprintf(value, sizeof(value), "%.4f", position); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-pos", value); if (r >= 0) p->snapshot.subtitle_position = position; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_ass_override(mp_harmony_player *p, const char *mode) { if (!p || !mode) return -1; pthread_mutex_lock(&p->lock); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-ass-override", mode); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_subtitle_delay(mp_harmony_player *p, double seconds) { if (!p || !isfinite(seconds)) return -1; pthread_mutex_lock(&p->lock); char value[32]; snprintf(value, sizeof(value), "%.4f", seconds); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-delay", value); if (r >= 0) p->snapshot.subtitle_delay_ms = (int64_t)(seconds * 1000.0); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_subtitle_scale(mp_harmony_player *p, double scale) { if (!p || !isfinite(scale) || scale <= 0) return -1; pthread_mutex_lock(&p->lock); char value[32]; snprintf(value, sizeof(value), "%.4f", scale); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-scale", value); if (r >= 0) p->snapshot.subtitle_scale = scale; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_subtitle_position(mp_harmony_player *p, double position) { if (!p || !isfinite(position) || position < 0 || position > 100) return -1; pthread_mutex_lock(&p->lock); char value[32]; snprintf(value, sizeof(value), "%.4f", position); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-pos", value); if (r >= 0) p->snapshot.subtitle_position = position; pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_set_ass_override(mp_harmony_player *p, const char *mode) { if (!p || !supported_ass_override(mode)) return -1; pthread_mutex_lock(&p->lock); int r = ensure_ready_locked(p) < 0 ? -1 : set_property_string(p, "sub-ass-override", mode); pthread_mutex_unlock(&p->lock); return r; }
|
||||
int mp_harmony_playback_get_tracks(mp_harmony_player *p, mp_harmony_track *tracks, size_t capacity, size_t *count) { if (!p || !count) return -1; pthread_mutex_lock(&p->lock); if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; } refresh_tracks_locked(p); size_t n = p->track_count < capacity ? p->track_count : capacity; if (tracks && n) memcpy(tracks, p->tracks, n * sizeof(*tracks)); *count = p->track_count; pthread_mutex_unlock(&p->lock); return 0; }
|
||||
int mp_harmony_playback_get_media_info(mp_harmony_player *p, mp_harmony_media_info *info) { if (!p || !info) return -1; pthread_mutex_lock(&p->lock); if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; } refresh_media_locked(p); *info = p->snapshot.media; pthread_mutex_unlock(&p->lock); return 0; }
|
||||
int mp_harmony_playback_get_network_info(mp_harmony_player *p, mp_harmony_network_info *info) { if (!p || !info) return -1; pthread_mutex_lock(&p->lock); if (ensure_ready_locked(p) < 0) { pthread_mutex_unlock(&p->lock); return -1; } update_network_locked(p); *info = p->snapshot.network; pthread_mutex_unlock(&p->lock); return 0; }
|
||||
@@ -626,6 +698,15 @@ void mp_harmony_playback_destroy(mp_harmony_player *p)
|
||||
free(p);
|
||||
}
|
||||
|
||||
MP_HARMONY_EXPORT mpv_handle *mp_harmony_playback_internal_handle(mp_harmony_player *p)
|
||||
{
|
||||
if (!p) return NULL;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
mpv_handle *handle = p->handle;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return handle;
|
||||
}
|
||||
|
||||
const char *mp_harmony_playback_state_name(mp_harmony_state state)
|
||||
{
|
||||
static const char *names[] = {"IDLE", "OPENING", "BUFFERING", "READY", "PLAYING", "PAUSED", "SEEKING", "ENDED", "STOPPED", "ERROR", "RELEASED"};
|
||||
|
||||
Reference in new issue
Block a user