diff --git a/native/src/cpp/mpv/MPV.cpp b/native/src/cpp/mpv/MPV.cpp index 90a1c6e..cb0c668 100644 --- a/native/src/cpp/mpv/MPV.cpp +++ b/native/src/cpp/mpv/MPV.cpp @@ -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([object, jvm](void *ctx, const char *name) -> void * { + .get_proc_address = fnptr([jvm](void *opaque, const char *name) -> void * { + const auto javaRender = static_cast(opaque); JNIEnv *jni_env; const auto res = jvm->AttachCurrentThread(reinterpret_cast(&jni_env), nullptr); if (res != JNI_OK) { @@ -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(name)); return reinterpret_cast(ret); }), + .get_proc_address_ctx = object, }; params.emplace_back(MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_params); int advControl = advancedControl ? 1 : 0; @@ -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([object, jvm](void *) { + fnptr([jvm](void *opaque) { + const auto render_context = static_cast(opaque); JNIEnv *jni_env; const auto res = jvm->AttachCurrentThread(reinterpret_cast(&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(ctx)); } diff --git a/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt b/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt new file mode 100644 index 0000000..0b02a94 --- /dev/null +++ b/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt @@ -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()) + } + } +} diff --git a/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt b/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt new file mode 100644 index 0000000..272feb3 --- /dev/null +++ b/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt @@ -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() + } + } +}