Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions native/src/cpp/mpv/MPV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRend
const auto object = env->NewGlobalRef(self);

mpv_opengl_init_params gl_params{
.get_proc_address = fnptr<void *(void *, const char *)>([object, jvm](void *ctx, const char *name) -> void * {
.get_proc_address = fnptr<void *(void *, const char *)>([jvm](void *opaque, const char *name) -> void * {
const auto javaRender = static_cast<jobject>(opaque);
JNIEnv *jni_env;
const auto res = jvm->AttachCurrentThread(reinterpret_cast<void **>(&jni_env), nullptr);
if (res != JNI_OK) {
Expand All @@ -590,19 +591,20 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRend
}

// TODO: Fix crash here during renderer disposal
const auto glProcMethod = jni_env->GetMethodID(jni_env->GetObjectClass(object), "getGlProc", "(Ljava/lang/String;)J");
const auto glProcMethod = jni_env->GetMethodID(jni_env->GetObjectClass(javaRender), "getGlProc", "(Ljava/lang/String;)J");
if (glProcMethod == nullptr) {
std::cerr << "Method not found: getGlProc" << std::endl;
return nullptr;
}

const auto nameStr = jni_env->NewStringUTF(name);
const auto ret = jni_env->CallLongMethod(object, glProcMethod, nameStr);
const auto ret = jni_env->CallLongMethod(javaRender, glProcMethod, nameStr);
jni_env->DeleteLocalRef(nameStr);
jvm->DetachCurrentThread();
// const auto ret = glXGetProcAddress(reinterpret_cast<const GLubyte *>(name));
return reinterpret_cast<void *>(ret);
}),
.get_proc_address_ctx = object,
};
params.emplace_back(MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_params);
int advControl = advancedControl ? 1 : 0;
Expand All @@ -614,26 +616,26 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRend
env->DeleteGlobalRef(object);
return mpvResultFailure(env, "mpv_render_context_create", ret);
}
const auto ctx = new RenderContext{handle, object, display};
mpv_render_context_set_update_callback(
handle,
fnptr<void(void *)>([object, jvm](void *) {
fnptr<void(void *)>([jvm](void *opaque) {
const auto render_context = static_cast<RenderContext *>(opaque);
JNIEnv *jni_env;
const auto res = jvm->AttachCurrentThread(reinterpret_cast<void **>(&jni_env), nullptr);
if (res != JNI_OK) {
std::cerr << "Failed to attach current thread" << std::endl;
return;
}
const auto updateMethod = jni_env->GetMethodID(jni_env->GetObjectClass(object), "requestUpdate", "()V");
const auto updateMethod = jni_env->GetMethodID(jni_env->GetObjectClass(render_context->gref), "requestUpdate", "()V");
if (updateMethod == nullptr) {
std::cerr << "Method not found: requestUpdate" << std::endl;
return;
}
std::cerr << "Requesting update: " << object << ", " << updateMethod << std::endl;
jni_env->CallVoidMethod(object, updateMethod);
jni_env->CallVoidMethod(render_context->gref, updateMethod);
jvm->DetachCurrentThread();
}),
nullptr);
const auto ctx = new RenderContext{handle, object, display};
ctx);
return resultSuccess(env, reinterpret_cast<jlong>(ctx));
}

Expand Down
28 changes: 28 additions & 0 deletions src/test/kotlin/dev/silenium/multimedia/simple/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.silenium.multimedia.simple

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.awaitApplication
import java.nio.file.Files
import kotlin.io.path.outputStream

suspend fun main() = awaitApplication {
val file = remember {
val videoFile = Files.createTempFile("video", ".webm")
Thread.currentThread().contextClassLoader.getResourceAsStream("1080p.webm").use {
videoFile.outputStream().use(it::copyTo)
}
videoFile.apply { toFile().deleteOnExit() }
}

Window(onCloseRequest = this::exitApplication) {
val state = rememberPagerState { 1000 }
VerticalPager(state = state, modifier = Modifier.fillMaxSize(), beyondViewportPageCount = 2) {
VideoPlayer(file = file, suspend = state.currentPage != it, modifier = Modifier.fillMaxSize())
}
}
}
62 changes: 62 additions & 0 deletions src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package dev.silenium.multimedia.simple

import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import dev.silenium.compose.gl.surface.GLSurface
import dev.silenium.compose.gl.surface.GLSurfaceView
import dev.silenium.compose.gl.surface.rememberGLSurfaceState
import dev.silenium.multimedia.core.mpv.MPV
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.lwjgl.opengl.GL30.*
import java.nio.file.Path

@Composable
fun VideoPlayer(file: Path, suspend: Boolean = false, modifier: Modifier = Modifier) {
val mpv = remember {
MPV().apply {
setOption("terminal", "yes")
setOption("msg-level", "all=info")
setOption("vo", "libmpv")
setOption("hwdec", "auto")
initialize().getOrThrow()
setProperty("loop", "inf").getOrThrow()
setProperty("keep-open", "yes").getOrThrow()
setProperty("ao-volume", "100").getOrNull()
}
}
var render: MPV.Render? by remember { mutableStateOf(null) }
var ready by remember { mutableStateOf(false) }
LaunchedEffect(ready, file) {
withContext(Dispatchers.Default) {
while (!ready) {
delay(10)
}
println("Loading file")
mpv.commandAsync("loadfile", file.toAbsolutePath().toString()).getOrThrow()
}
}
LaunchedEffect(ready, suspend) {
withContext(Dispatchers.Default) {
mpv.commandAsync("set", "pause", if (suspend) "yes" else "no").getOrThrow()
}
}
val state = rememberGLSurfaceState()
GLSurfaceView(state, modifier = modifier, presentMode = GLSurface.PresentMode.MAILBOX, swapChainSize = 3) {
if (!ready) {
render = mpv.createRender(advancedControl = true, state::requestUpdate)
ready = true
}
glClearColor(0f, 0f, 0f, 0f)
glClear(GL_COLOR_BUFFER_BIT)
render?.render(fbo)?.getOrThrow()
}
DisposableEffect(Unit) {
onDispose {
println("Disposing")
render?.close()
mpv.close()
}
}
}