Files
plezy/android/libmpv/src/main/cpp/main.cpp
T
edde746 98c6a09b41 chore(player): remove temporary native playback diagnostics
Production playback still carried recurring Windows HDR probes, Android subtitle profiling, and verbose mpv logs with debug logging disabled.

Remove the Windows probe, pin the published mpv-build profiler cleanup, and honor Android debug logging preferences for video and music while preserving warnings and errors.
2026-09-06 02:30:08 +02:00

172 lines
4.4 KiB
C++

#include <jni.h>
#include <mpv/client.h>
#include <pthread.h>
#include <atomic>
#include <clocale>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <mutex>
#include <string>
#include <vector>
extern "C" {
#include <libavcodec/jni.h>
}
#include "event.h"
#include "jni_utils.h"
#include "log.h"
#define ARRAYLEN(a) (sizeof(a) / sizeof(a[0]))
void render_cleanup(JNIEnv* env);
extern "C" {
jni_func(void, nativeCreate, jobject appctx);
jni_func(void, nativeInit);
jni_func(void, nativeDestroy);
jni_func(jint, nativeSetLogLevel, jstring level);
jni_func(void, nativeCommand, jobjectArray jarray);
jni_func(void, nativeHookContinue, jlong id);
};
JavaVM* g_vm;
mpv_handle* g_mpv;
std::atomic<bool> g_event_thread_request_exit(false);
static pthread_t event_thread_id;
static bool event_thread_started = false;
static std::mutex g_lifecycle_mutex;
static void prepare_environment(JNIEnv* env, jobject appctx) {
setlocale(LC_NUMERIC, "C");
if (!env->GetJavaVM(&g_vm) && g_vm) av_jni_set_java_vm(g_vm, NULL);
jobject global_appctx = env->NewGlobalRef(appctx);
if (global_appctx) av_jni_set_android_app_ctx(global_appctx, NULL);
init_methods_cache(env);
}
jni_func(void, nativeCreate, jobject appctx) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
prepare_environment(env, appctx);
mpv_handle* leaked_mpv = NULL;
if (g_mpv) {
ALOGE("destroying leaked mpv instance");
leaked_mpv = g_mpv;
if (event_thread_started) {
g_event_thread_request_exit = true;
mpv_wakeup(leaked_mpv);
pthread_join(event_thread_id, NULL);
event_thread_started = false;
}
g_mpv = NULL;
mpv_terminate_destroy(leaked_mpv);
render_cleanup(env);
}
g_mpv = mpv_create();
if (!g_mpv) {
die("context init failed");
return;
}
mpv_request_log_messages(g_mpv, "warn");
}
jni_func(void, nativeInit) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_mpv) {
die("mpv is not created");
return;
}
if (mpv_initialize(g_mpv) < 0) {
die("mpv init failed");
return;
}
// Per-file decode routing (Dolby Vision P5, H.264 High 10) has to land
// before mpv creates the decoder; file-loaded is already too late for the
// MediaCodec path. on_preloaded runs after the demuxer opened the file and
// holds playback until Kotlin continues it (MpvPlayer.onHook).
mpv_hook_add(g_mpv, 0, "on_preloaded", 0);
g_event_thread_request_exit = false;
if (pthread_create(&event_thread_id, NULL, event_thread, NULL) != 0) {
die("thread create failed");
return;
}
event_thread_started = true;
pthread_setname_np(event_thread_id, "event_thread");
}
jni_func(void, nativeDestroy) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_mpv) {
return;
}
mpv_handle* local_mpv = g_mpv;
// Configuration (including an invalid initial log level) can fail before
// nativeInit starts the event thread.
if (event_thread_started) {
g_event_thread_request_exit = true;
mpv_wakeup(local_mpv);
pthread_join(event_thread_id, NULL);
event_thread_started = false;
}
g_mpv = NULL;
// The MediaCodec VO can retain the Surface until final decoder teardown.
// Keep its JNI refs alive for the entire blocking termination.
mpv_terminate_destroy(local_mpv);
render_cleanup(env);
}
jni_func(jint, nativeSetLogLevel, jstring jlevel) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_mpv) return MPV_ERROR_UNINITIALIZED;
const std::string level = java_string_to_utf8(env, jlevel);
if (env->ExceptionCheck()) return MPV_ERROR_NOMEM;
const int result = mpv_request_log_messages(g_mpv, level.c_str());
if (result < 0) ALOGE("mpv_request_log_messages returned error %s", mpv_error_string(result));
return result;
}
jni_func(void, nativeCommand, jobjectArray jarray) {
CHECK_MPV_INIT();
const char* arguments[128] = {0};
int len = env->GetArrayLength(jarray);
if (len >= (int)ARRAYLEN(arguments)) {
die("too many command arguments");
return;
}
std::vector<std::string> storage;
storage.reserve(len);
for (int i = 0; i < len; ++i) {
jstring jarg = (jstring)env->GetObjectArrayElement(jarray, i);
storage.push_back(java_string_to_utf8(env, jarg));
arguments[i] = storage.back().c_str();
env->DeleteLocalRef(jarg);
}
mpv_command(g_mpv, arguments);
}
jni_func(void, nativeHookContinue, jlong id) {
if (!g_mpv) return;
mpv_hook_continue(g_mpv, (uint64_t)id);
}