feat(playback-lab): add EGL and libmpv playback bridge

This commit is contained in:
BoatingCloud committed 2026-08-29 09:05:47 +08:00
1 parent 2356e2f67b
commit d4c84601fa
6 files changed
+854

No files matched your search

@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.5.0)
project(playback_lab)
set(REPO_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../")
set(MPV_INCLUDE_DIR "${REPO_ROOT}/build/ohos-arm64/prefix/include")
set(MPV_LIBRARY "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libmpv.so.2")
if(NOT EXISTS "${MPV_LIBRARY}")
message(FATAL_ERROR "Missing ${MPV_LIBRARY}; run scripts/stage-mpv.sh first")
endif()
include_directories(${MPV_INCLUDE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_library(mpv SHARED IMPORTED)
set_target_properties(mpv PROPERTIES IMPORTED_LOCATION "${MPV_LIBRARY}")
add_library(entry SHARED
bridge.cpp
player.cpp
render.cpp
)
target_compile_features(entry PRIVATE cxx_std_17)
target_compile_options(entry PRIVATE -fvisibility=hidden -Wall -Wextra -Werror=return-type)
target_link_libraries(entry PUBLIC
mpv
libace_napi.z.so
libace_ndk.z.so
libEGL.so
libGLESv3.so
libnative_window.so
libhilog_ndk.z.so
)
+164
View File
@@ -0,0 +1,164 @@
#include "player.h"
#include "render.h"
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <hilog/log.h>
#include <napi/native_api.h>
#include <string>
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xD001
#define LOG_TAG "VelixBridge"
namespace {
PlaybackEngine gPlayer;
EglRenderer gRenderer(gPlayer);
void onSurfaceCreated(OH_NativeXComponent* component, void* window)
{
gRenderer.onSurfaceCreated(component, window);
}
void onSurfaceChanged(OH_NativeXComponent* component, void* window)
{
gRenderer.onSurfaceChanged(component, window);
}
void onSurfaceDestroyed(OH_NativeXComponent*, void* window)
{
gRenderer.onSurfaceDestroyed(window);
}
void onTouch(OH_NativeXComponent*, void*) {}
bool getStringArg(napi_env env, napi_callback_info info, std::string& value)
{
size_t argc = 1;
napi_value argv[1] = {nullptr};
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok || argc < 1) return false;
size_t size = 0;
if (napi_get_value_string_utf8(env, argv[0], nullptr, 0, &size) != napi_ok) return false;
value.resize(size + 1);
if (napi_get_value_string_utf8(env, argv[0], value.data(), value.size(), &size) != napi_ok) return false;
value.resize(size);
return true;
}
bool getDoubleArg(napi_env env, napi_callback_info info, double& value)
{
size_t argc = 1;
napi_value argv[1] = {nullptr};
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok || argc < 1) return false;
return napi_get_value_double(env, argv[0], &value) == napi_ok;
}
napi_value undefined(napi_env env)
{
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
napi_value load(napi_env env, napi_callback_info info)
{
std::string url;
if (getStringArg(env, info, url)) gPlayer.load(url);
return undefined(env);
}
napi_value play(napi_env env, napi_callback_info)
{
gPlayer.play();
return undefined(env);
}
napi_value pause(napi_env env, napi_callback_info)
{
gPlayer.pause();
return undefined(env);
}
napi_value seek(napi_env env, napi_callback_info info)
{
double delta = 0;
if (getDoubleArg(env, info, delta)) gPlayer.seek(delta);
return undefined(env);
}
napi_value stop(napi_env env, napi_callback_info)
{
gPlayer.stop();
return undefined(env);
}
napi_value release(napi_env env, napi_callback_info)
{
gRenderer.onSurfaceDestroyed(nullptr);
gPlayer.release();
return undefined(env);
}
napi_value getState(napi_env env, napi_callback_info)
{
PlaybackSnapshot state = gPlayer.snapshot();
napi_value object = nullptr;
napi_create_object(env, &object);
napi_value value = nullptr;
napi_create_string_utf8(env, state.state.c_str(), NAPI_AUTO_LENGTH, &value);
napi_set_named_property(env, object, "state", value);
napi_create_double(env, state.position, &value);
napi_set_named_property(env, object, "position", value);
napi_create_double(env, state.duration, &value);
napi_set_named_property(env, object, "duration", value);
napi_create_double(env, state.fps, &value);
napi_set_named_property(env, object, "fps", value);
napi_create_int64(env, state.dropped, &value);
napi_set_named_property(env, object, "dropped", value);
napi_create_int64(env, state.firstFrameMs, &value);
napi_set_named_property(env, object, "firstFrameMs", value);
// Render count is a diagnostic value consumed by ArkTS as a number.
// Returning a BigInt makes the inspector unable to serialize the state.
napi_create_double(env, static_cast<double>(state.renderCount), &value);
napi_set_named_property(env, object, "renderCount", value);
napi_create_string_utf8(env, state.lastError.c_str(), NAPI_AUTO_LENGTH, &value);
napi_set_named_property(env, object, "lastError", value);
napi_create_string_utf8(env, gRenderer.surfaceStatus(), NAPI_AUTO_LENGTH, &value);
napi_set_named_property(env, object, "surface", value);
napi_create_string_utf8(env, gRenderer.eglStatus(), NAPI_AUTO_LENGTH, &value);
napi_set_named_property(env, object, "egl", value);
napi_create_string_utf8(env, gRenderer.mpvStatus(), NAPI_AUTO_LENGTH, &value);
napi_set_named_property(env, object, "mpv", value);
return object;
}
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;
if (napi_unwrap(env, xcomponent, reinterpret_cast<void**>(&component)) == napi_ok && component != nullptr) {
static OH_NativeXComponent_Callback callbacks = {
onSurfaceCreated, onSurfaceChanged, onSurfaceDestroyed, onTouch
};
OH_NativeXComponent_RegisterCallback(component, &callbacks);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "XComponent callback registered");
}
}
napi_property_descriptor properties[] = {
{"load", nullptr, load, nullptr, nullptr, nullptr, napi_default, nullptr},
{"play", nullptr, play, nullptr, nullptr, nullptr, napi_default, nullptr},
{"pause", nullptr, pause, nullptr, nullptr, nullptr, napi_default, nullptr},
{"seek", nullptr, seek, nullptr, nullptr, nullptr, napi_default, nullptr},
{"stop", nullptr, stop, nullptr, nullptr, nullptr, napi_default, nullptr},
{"release", nullptr, release, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getState", nullptr, getState, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties);
return exports;
}
}
NAPI_MODULE(entry, Init)
+298
View File
@@ -0,0 +1,298 @@
#include "player.h"
#include <hilog/log.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xD001
#define LOG_TAG "VelixPlayback"
namespace {
constexpr uint64_t kTimePos = 1;
constexpr uint64_t kDuration = 2;
constexpr uint64_t kPause = 3;
constexpr uint64_t kFps = 4;
constexpr uint64_t kDropped = 5;
void logMpvError(const char* operation, int code)
{
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "%{public}s failed: %{public}s (%{public}d)",
operation, mpv_error_string(code), code);
}
}
PlaybackEngine::PlaybackEngine() = default;
PlaybackEngine::~PlaybackEngine()
{
release();
}
bool PlaybackEngine::initialize()
{
if (initialized_.load()) {
return true;
}
handle_ = mpv_create();
if (handle_ == nullptr) {
setError("mpv_create failed");
return false;
}
const char* options[][2] = {
{"vo", "libmpv"},
{"terminal", "no"},
{"config", "no"},
{"hwdec", "no"},
{"audio", "no"},
{"video-timing-offset", "0"},
{nullptr, nullptr},
};
for (size_t i = 0; options[i][0] != nullptr; ++i) {
int code = mpv_set_option_string(handle_, options[i][0], options[i][1]);
if (code < 0) {
logMpvError(options[i][0], code);
setError(std::string("mpv option failed: ") + options[i][0]);
mpv_terminate_destroy(handle_);
handle_ = nullptr;
return false;
}
}
int code = mpv_initialize(handle_);
if (code < 0) {
logMpvError("mpv_initialize", code);
setError("mpv_initialize failed");
mpv_terminate_destroy(handle_);
handle_ = nullptr;
return false;
}
observeProperties();
initialized_.store(true);
stopEvents_.store(false);
startedAt_ = std::chrono::steady_clock::now();
setState("PLAYER_CREATED");
eventThread_ = std::thread(&PlaybackEngine::eventLoop, this);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "libmpv initialized");
return true;
}
void PlaybackEngine::observeProperties()
{
mpv_observe_property(handle_, kTimePos, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(handle_, kDuration, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(handle_, kPause, "pause", MPV_FORMAT_FLAG);
mpv_observe_property(handle_, kFps, "estimated-vf-fps", MPV_FORMAT_DOUBLE);
mpv_observe_property(handle_, kDropped, "vo-drop-frame-count", MPV_FORMAT_INT64);
}
void PlaybackEngine::load(const std::string& url)
{
if (!initialize() || url.empty()) {
setError(url.empty() ? "empty URL" : "player unavailable");
return;
}
{
std::lock_guard<std::mutex> lock(mutex_);
snapshot_.position = 0.0;
snapshot_.duration = 0.0;
snapshot_.firstFrameMs = -1;
snapshot_.renderCount = 0;
snapshot_.lastError.clear();
startedAt_ = std::chrono::steady_clock::now();
}
const char* command[] = {"loadfile", url.c_str(), "replace", nullptr};
int code = mpv_command(handle_, command);
if (code < 0) {
logMpvError("loadfile", code);
setError(std::string("load failed: ") + mpv_error_string(code));
return;
}
setState("PREPARING");
}
void PlaybackEngine::play()
{
if (handle_ == nullptr) {
setError("player unavailable");
return;
}
int code = mpv_set_property_string(handle_, "pause", "no");
if (code < 0) {
logMpvError("play", code);
setError("play failed");
return;
}
setState("PLAYING");
}
void PlaybackEngine::pause()
{
if (handle_ == nullptr) {
return;
}
int code = mpv_set_property_string(handle_, "pause", "yes");
if (code < 0) {
logMpvError("pause", code);
setError("pause failed");
return;
}
setState("PAUSED");
}
void PlaybackEngine::seek(double delta)
{
if (handle_ == nullptr) {
return;
}
if (!std::isfinite(delta)) {
setError("seek delta is not finite");
return;
}
double target = 0.0;
double duration = 0.0;
{
std::lock_guard<std::mutex> lock(mutex_);
target = snapshot_.position + delta;
duration = snapshot_.duration;
}
if (duration > 0.0) target = std::clamp(target, 0.0, duration);
int code = mpv_set_property(handle_, "time-pos", MPV_FORMAT_DOUBLE, &target);
if (code < 0) {
logMpvError("seek", code);
setError("seek failed");
return;
}
std::lock_guard<std::mutex> lock(mutex_);
snapshot_.lastError.clear();
}
void PlaybackEngine::stop()
{
if (handle_ == nullptr) {
return;
}
int code = mpv_command_string(handle_, "stop");
if (code < 0) {
logMpvError("stop", code);
setError("stop failed");
return;
}
setState("STOPPED");
}
void PlaybackEngine::release()
{
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "release called initialized=%{public}d", initialized_.load());
if (!initialized_.exchange(false)) {
return;
}
stopEvents_.store(true);
if (handle_ != nullptr) {
mpv_wakeup(handle_);
}
if (eventThread_.joinable()) {
eventThread_.join();
}
if (handle_ != nullptr) {
mpv_terminate_destroy(handle_);
handle_ = nullptr;
}
setState("RELEASED");
}
void PlaybackEngine::markFrameRendered()
{
std::lock_guard<std::mutex> lock(mutex_);
++snapshot_.renderCount;
if (snapshot_.firstFrameMs < 0) {
auto elapsed = std::chrono::steady_clock::now() - startedAt_;
snapshot_.firstFrameMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG,
"first frame: %{public}lld ms", static_cast<long long>(snapshot_.firstFrameMs));
}
}
PlaybackSnapshot PlaybackEngine::snapshot() const
{
std::lock_guard<std::mutex> lock(mutex_);
return snapshot_;
}
void PlaybackEngine::setState(const char* state)
{
std::lock_guard<std::mutex> lock(mutex_);
snapshot_.state = state;
}
void PlaybackEngine::setError(const std::string& error)
{
std::lock_guard<std::mutex> lock(mutex_);
snapshot_.state = "ERROR";
snapshot_.lastError = error;
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "%{public}s", error.c_str());
}
void PlaybackEngine::updateProperty(uint64_t userdata, const mpv_event_property* property)
{
if (property == nullptr || property->data == nullptr) {
return;
}
std::lock_guard<std::mutex> lock(mutex_);
switch (userdata) {
case kTimePos:
if (property->format == MPV_FORMAT_DOUBLE) snapshot_.position = *static_cast<double*>(property->data);
break;
case kDuration:
if (property->format == MPV_FORMAT_DOUBLE) snapshot_.duration = *static_cast<double*>(property->data);
break;
case kPause:
if (property->format == MPV_FORMAT_FLAG && *static_cast<int*>(property->data) != 0 &&
snapshot_.state == "PLAYING") snapshot_.state = "PAUSED";
break;
case kFps:
if (property->format == MPV_FORMAT_DOUBLE) snapshot_.fps = *static_cast<double*>(property->data);
break;
case kDropped:
if (property->format == MPV_FORMAT_INT64) snapshot_.dropped = *static_cast<int64_t*>(property->data);
break;
default:
break;
}
}
void PlaybackEngine::eventLoop()
{
while (!stopEvents_.load() && handle_ != nullptr) {
mpv_event* event = mpv_wait_event(handle_, 0.02);
if (event == nullptr) {
continue;
}
switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE:
updateProperty(event->reply_userdata, static_cast<mpv_event_property*>(event->data));
break;
case MPV_EVENT_START_FILE:
setState("PREPARING");
break;
case MPV_EVENT_FILE_LOADED:
setState("READY");
break;
case MPV_EVENT_END_FILE: {
auto* end = static_cast<mpv_event_end_file*>(event->data);
if (end != nullptr && end->reason == MPV_END_FILE_REASON_ERROR) {
setError(std::string("end-file: ") + mpv_error_string(end->error));
} else {
setState("STOPPED");
}
break;
}
case MPV_EVENT_SHUTDOWN:
return;
default:
break;
}
}
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <mpv/client.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
struct PlaybackSnapshot {
std::string state = "IDLE";
double position = 0.0;
double duration = 0.0;
double fps = 0.0;
int64_t dropped = 0;
int64_t firstFrameMs = -1;
uint64_t renderCount = 0;
std::string lastError;
};
class PlaybackEngine {
public:
PlaybackEngine();
~PlaybackEngine();
bool initialize();
void load(const std::string& url);
void play();
void pause();
void seek(double delta);
void stop();
void release();
PlaybackSnapshot snapshot() const;
mpv_handle* handle() const { return handle_; }
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);
mutable std::mutex mutex_;
PlaybackSnapshot snapshot_;
mpv_handle* handle_ = nullptr;
std::thread eventThread_;
std::atomic<bool> stopEvents_{false};
std::atomic<bool> initialized_{false};
std::chrono::steady_clock::time_point startedAt_;
};
+251
View File
@@ -0,0 +1,251 @@
#include "render.h"
#include <GLES3/gl3.h>
#include <EGL/egl.h>
#include <hilog/log.h>
#include <mpv/render_gl.h>
#include <cstring>
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xD001
#define LOG_TAG "VelixRender"
EglRenderer::EglRenderer(PlaybackEngine& player) : player_(player) {}
EglRenderer::~EglRenderer()
{
onSurfaceDestroyed(nullptr);
}
const char* EglRenderer::surfaceStatus() const
{
switch (surfaceStatus_.load()) {
case 1: return "READY";
case 2: return "ERROR";
case 3: return "RELEASED";
default: return "WAITING";
}
}
const char* EglRenderer::eglStatus() const
{
switch (eglStatus_.load()) {
case 1: return "READY";
case 2: return "ERROR";
case 3: return "RELEASED";
default: return "WAITING";
}
}
const char* EglRenderer::mpvStatus() const
{
switch (mpvStatus_.load()) {
case 1: return "READY";
case 2: return "ERROR";
case 3: return "RELEASED";
default: return "WAITING";
}
}
void EglRenderer::onSurfaceCreated(OH_NativeXComponent* component, void* surface)
{
onSurfaceDestroyed(nullptr);
OHNativeWindow* window = static_cast<OHNativeWindow*>(surface);
if (window == nullptr) {
surfaceStatus_.store(2);
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "OHNativeWindow creation failed");
return;
}
if (OH_NativeWindow_NativeObjectReference(window) != 0) {
surfaceStatus_.store(2);
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "OHNativeWindow reference failed");
return;
}
uint64_t width = 1;
uint64_t height = 1;
if (component != nullptr) {
OH_NativeXComponent_GetXComponentSize(component, surface, &width, &height);
}
{
std::lock_guard<std::mutex> lock(mutex_);
window_ = window;
width_ = width > 0 ? static_cast<int>(width) : 1;
height_ = height > 0 ? static_cast<int>(height) : 1;
stop_.store(false);
surfaceReady_.store(true);
redraw_ = true;
}
surfaceStatus_.store(1);
eglStatus_.store(0);
mpvStatus_.store(0);
thread_ = std::thread(&EglRenderer::renderLoop, this);
wake_.notify_one();
}
void EglRenderer::onSurfaceChanged(OH_NativeXComponent* component, void* surface)
{
uint64_t width = 1;
uint64_t height = 1;
if (component != nullptr) {
OH_NativeXComponent_GetXComponentSize(component, surface, &width, &height);
}
{
std::lock_guard<std::mutex> lock(mutex_);
width_ = width > 0 ? static_cast<int>(width) : width_;
height_ = height > 0 ? static_cast<int>(height) : height_;
redraw_ = true;
}
wake_.notify_one();
}
void EglRenderer::onSurfaceDestroyed(void*)
{
surfaceReady_.store(false);
stop_.store(true);
wake_.notify_one();
if (thread_.joinable()) {
thread_.join();
}
std::lock_guard<std::mutex> lock(mutex_);
if (window_ != nullptr) {
OH_NativeWindow_NativeObjectUnreference(window_);
window_ = nullptr;
}
surfaceStatus_.store(3);
eglStatus_.store(3);
mpvStatus_.store(3);
}
void EglRenderer::requestRender()
{
{
std::lock_guard<std::mutex> lock(mutex_);
redraw_ = true;
}
wake_.notify_one();
}
bool EglRenderer::initializeEgl(OHNativeWindow* window)
{
display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display_ == EGL_NO_DISPLAY || eglInitialize(display_, nullptr, nullptr) != EGL_TRUE) {
eglStatus_.store(2);
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "EGL initialize failed: 0x%{public}x", eglGetError());
return false;
}
const EGLint configAttrs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
EGL_NONE
};
EGLConfig config = nullptr;
EGLint count = 0;
if (eglChooseConfig(display_, configAttrs, &config, 1, &count) != EGL_TRUE || count == 0) {
eglStatus_.store(2);
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "EGL config selection failed: 0x%{public}x", eglGetError());
destroyEgl();
return false;
}
const EGLint contextAttrs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttrs);
surface_ = eglCreateWindowSurface(display_, config, reinterpret_cast<EGLNativeWindowType>(window), nullptr);
if (context_ == EGL_NO_CONTEXT || surface_ == EGL_NO_SURFACE ||
eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) {
eglStatus_.store(2);
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "EGL context/surface failed: 0x%{public}x", eglGetError());
destroyEgl();
return false;
}
eglSwapInterval(display_, 1);
glViewport(0, 0, width_, height_);
eglStatus_.store(1);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "EGL vendor=%{public}s version=%{public}s renderer=%{public}s",
eglQueryString(display_, EGL_VENDOR), eglQueryString(display_, EGL_VERSION),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
return true;
}
void EglRenderer::destroyEgl()
{
if (display_ != EGL_NO_DISPLAY) {
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (surface_ != EGL_NO_SURFACE) eglDestroySurface(display_, surface_);
if (context_ != EGL_NO_CONTEXT) eglDestroyContext(display_, context_);
eglTerminate(display_);
}
display_ = EGL_NO_DISPLAY;
context_ = EGL_NO_CONTEXT;
surface_ = EGL_NO_SURFACE;
}
void* EglRenderer::getProcAddress(void*, const char* name)
{
return reinterpret_cast<void*>(eglGetProcAddress(name));
}
void EglRenderer::onMpvUpdate(void* ctx)
{
if (ctx != nullptr) static_cast<EglRenderer*>(ctx)->requestRender();
}
void EglRenderer::renderLoop()
{
OHNativeWindow* window = nullptr;
{
std::lock_guard<std::mutex> lock(mutex_);
window = window_;
}
if (window == nullptr || !initializeEgl(window)) {
mpvStatus_.store(2);
return;
}
const char* api = MPV_RENDER_API_TYPE_OPENGL;
mpv_opengl_init_params glParams = {getProcAddress, this};
mpv_render_param createParams[] = {
{MPV_RENDER_PARAM_API_TYPE, const_cast<char*>(api)},
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &glParams},
{MPV_RENDER_PARAM_INVALID, nullptr},
};
int code = mpv_render_context_create(&mpvRender_, player_.handle(), createParams);
if (code < 0) {
mpvStatus_.store(2);
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, LOG_TAG, "mpv render context failed: %{public}s", mpv_error_string(code));
destroyEgl();
return;
}
mpvStatus_.store(1);
mpv_render_context_set_update_callback(mpvRender_, onMpvUpdate, this);
while (!stop_.load()) {
int width = 1;
int height = 1;
{
std::unique_lock<std::mutex> lock(mutex_);
wake_.wait_for(lock, std::chrono::milliseconds(16), [this] { return redraw_ || stop_.load(); });
redraw_ = false;
width = width_;
height = height_;
}
if (stop_.load()) break;
uint64_t flags = mpv_render_context_update(mpvRender_);
if ((flags & MPV_RENDER_UPDATE_FRAME) == 0) continue;
mpv_opengl_fbo fbo = {0, width, height, GL_RGBA8};
int flip = 1;
mpv_render_param renderParams[] = {
{MPV_RENDER_PARAM_OPENGL_FBO, &fbo},
{MPV_RENDER_PARAM_FLIP_Y, &flip},
{MPV_RENDER_PARAM_INVALID, nullptr},
};
if (mpv_render_context_render(mpvRender_, renderParams) >= 0) {
eglSwapBuffers(display_, surface_);
mpv_render_context_report_swap(mpvRender_);
player_.markFrameRendered();
}
}
mpv_render_context_set_update_callback(mpvRender_, nullptr, nullptr);
mpv_render_context_free(mpvRender_);
mpvRender_ = nullptr;
destroyEgl();
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include "player.h"
#include <mpv/render.h>
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <native_window/external_window.h>
#include <EGL/egl.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
class EglRenderer {
public:
explicit EglRenderer(PlaybackEngine& player);
~EglRenderer();
void onSurfaceCreated(OH_NativeXComponent* component, void* surface);
void onSurfaceChanged(OH_NativeXComponent* component, void* surface);
void onSurfaceDestroyed(void* surface);
void requestRender();
bool hasSurface() const { return surfaceReady_.load(); }
const char* surfaceStatus() const;
const char* eglStatus() const;
const char* mpvStatus() const;
private:
void renderLoop();
bool initializeEgl(OHNativeWindow* window);
void destroyEgl();
static void onMpvUpdate(void* ctx);
static void* getProcAddress(void* ctx, const char* name);
PlaybackEngine& player_;
std::thread thread_;
mutable std::mutex mutex_;
std::condition_variable wake_;
std::atomic<bool> stop_{false};
std::atomic<bool> surfaceReady_{false};
std::atomic<int> surfaceStatus_{0};
std::atomic<int> eglStatus_{0};
std::atomic<int> mpvStatus_{0};
bool redraw_ = false;
OHNativeWindow* window_ = nullptr;
int width_ = 1;
int height_ = 1;
EGLDisplay display_ = EGL_NO_DISPLAY;
EGLContext context_ = EGL_NO_CONTEXT;
EGLSurface surface_ = EGL_NO_SURFACE;
mpv_render_context* mpvRender_ = nullptr;
};