Add experimental MPV player backend (#161)

Experimental MPV player backend

Related to #14, #22, & #85

You can switch to MPV in advanced settings and toggle using hardware
decoding or not.

This uses code and buildscripts from
https://github.com/mpv-android/mpv-android, plus other third party
libraries to build MPV
This commit is contained in:
damontecres 2025-11-16 18:46:25 -05:00 committed by GitHub
parent d7b333e519
commit 6be2662d4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 3987 additions and 289 deletions

108
app/src/main/jni/main.cpp Normal file
View file

@ -0,0 +1,108 @@
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <locale.h>
#include <atomic>
#include <mpv/client.h>
#include <pthread.h>
extern "C" {
#include <libavcodec/jni.h>
}
#include "log.h"
#include "jni_utils.h"
#include "event.h"
#define ARRAYLEN(a) (sizeof(a)/sizeof(a[0]))
extern "C" {
jni_func(void, create, jobject appctx);
jni_func(void, init);
jni_func(void, destroy);
jni_func(void, command, jobjectArray jarray);
};
JavaVM *g_vm;
mpv_handle *g_mpv;
std::atomic<bool> g_event_thread_request_exit(false);
static pthread_t event_thread_id;
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, create, jobject appctx) {
prepare_environment(env, appctx);
if (g_mpv)
die("mpv is already initialized");
g_mpv = mpv_create();
if (!g_mpv)
die("context init failed");
// use terminal log level but request verbose messages
// this way --msg-level can be used to adjust later
mpv_request_log_messages(g_mpv, "terminal-default");
mpv_set_option_string(g_mpv, "msg-level", "all=v");
}
jni_func(void, init) {
if (!g_mpv)
die("mpv is not created");
if (mpv_initialize(g_mpv) < 0)
die("mpv init failed");
g_event_thread_request_exit = false;
if (pthread_create(&event_thread_id, NULL, event_thread, NULL) != 0)
die("thread create failed");
pthread_setname_np(event_thread_id, "event_thread");
}
jni_func(void, destroy) {
if (!g_mpv) {
ALOGV("mpv destroy called but it's already destroyed");
return;
}
// poke event thread and wait for it to exit
g_event_thread_request_exit = true;
mpv_wakeup(g_mpv);
pthread_join(event_thread_id, NULL);
mpv_terminate_destroy(g_mpv);
g_mpv = NULL;
}
jni_func(void, command, jobjectArray jarray) {
CHECK_MPV_INIT();
const char *arguments[128] = {0};
int len = env->GetArrayLength(jarray);
if (len >= ARRAYLEN(arguments))
die("too many command arguments");
for (int i = 0; i < len; ++i)
arguments[i] = env->GetStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), NULL);
mpv_command(g_mpv, arguments);
for (int i = 0; i < len; ++i)
env->ReleaseStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), arguments[i]);
}