Skip to content

Commit 5e6cb41

Browse files
authored
Merge pull request #36 from silenium-dev/fix/native
fix crashes in native code
2 parents 3d69c88 + 671f9fb commit 5e6cb41

File tree

3 files changed

+101
-9
lines changed

3 files changed

+101
-9
lines changed

native/src/cpp/mpv/MPV.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRend
581581
const auto object = env->NewGlobalRef(self);
582582

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

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

599600
const auto nameStr = jni_env->NewStringUTF(name);
600-
const auto ret = jni_env->CallLongMethod(object, glProcMethod, nameStr);
601+
const auto ret = jni_env->CallLongMethod(javaRender, glProcMethod, nameStr);
601602
jni_env->DeleteLocalRef(nameStr);
602603
jvm->DetachCurrentThread();
603604
// const auto ret = glXGetProcAddress(reinterpret_cast<const GLubyte *>(name));
604605
return reinterpret_cast<void *>(ret);
605606
}),
607+
.get_proc_address_ctx = object,
606608
};
607609
params.emplace_back(MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_params);
608610
int advControl = advancedControl ? 1 : 0;
@@ -614,26 +616,26 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRend
614616
env->DeleteGlobalRef(object);
615617
return mpvResultFailure(env, "mpv_render_context_create", ret);
616618
}
619+
const auto ctx = new RenderContext{handle, object, display};
617620
mpv_render_context_set_update_callback(
618621
handle,
619-
fnptr<void(void *)>([object, jvm](void *) {
622+
fnptr<void(void *)>([jvm](void *opaque) {
623+
const auto render_context = static_cast<RenderContext *>(opaque);
620624
JNIEnv *jni_env;
621625
const auto res = jvm->AttachCurrentThread(reinterpret_cast<void **>(&jni_env), nullptr);
622626
if (res != JNI_OK) {
623627
std::cerr << "Failed to attach current thread" << std::endl;
624628
return;
625629
}
626-
const auto updateMethod = jni_env->GetMethodID(jni_env->GetObjectClass(object), "requestUpdate", "()V");
630+
const auto updateMethod = jni_env->GetMethodID(jni_env->GetObjectClass(render_context->gref), "requestUpdate", "()V");
627631
if (updateMethod == nullptr) {
628632
std::cerr << "Method not found: requestUpdate" << std::endl;
629633
return;
630634
}
631-
std::cerr << "Requesting update: " << object << ", " << updateMethod << std::endl;
632-
jni_env->CallVoidMethod(object, updateMethod);
635+
jni_env->CallVoidMethod(render_context->gref, updateMethod);
633636
jvm->DetachCurrentThread();
634637
}),
635-
nullptr);
636-
const auto ctx = new RenderContext{handle, object, display};
638+
ctx);
637639
return resultSuccess(env, reinterpret_cast<jlong>(ctx));
638640
}
639641

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.silenium.multimedia.simple
2+
3+
import androidx.compose.foundation.layout.fillMaxSize
4+
import androidx.compose.foundation.pager.VerticalPager
5+
import androidx.compose.foundation.pager.rememberPagerState
6+
import androidx.compose.runtime.remember
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.window.Window
9+
import androidx.compose.ui.window.awaitApplication
10+
import java.nio.file.Files
11+
import kotlin.io.path.outputStream
12+
13+
suspend fun main() = awaitApplication {
14+
val file = remember {
15+
val videoFile = Files.createTempFile("video", ".webm")
16+
Thread.currentThread().contextClassLoader.getResourceAsStream("1080p.webm").use {
17+
videoFile.outputStream().use(it::copyTo)
18+
}
19+
videoFile.apply { toFile().deleteOnExit() }
20+
}
21+
22+
Window(onCloseRequest = this::exitApplication) {
23+
val state = rememberPagerState { 1000 }
24+
VerticalPager(state = state, modifier = Modifier.fillMaxSize(), beyondViewportPageCount = 2) {
25+
VideoPlayer(file = file, suspend = state.currentPage != it, modifier = Modifier.fillMaxSize())
26+
}
27+
}
28+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package dev.silenium.multimedia.simple
2+
3+
import androidx.compose.runtime.*
4+
import androidx.compose.ui.Modifier
5+
import dev.silenium.compose.gl.surface.GLSurface
6+
import dev.silenium.compose.gl.surface.GLSurfaceView
7+
import dev.silenium.compose.gl.surface.rememberGLSurfaceState
8+
import dev.silenium.multimedia.core.mpv.MPV
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.delay
11+
import kotlinx.coroutines.withContext
12+
import org.lwjgl.opengl.GL30.*
13+
import java.nio.file.Path
14+
15+
@Composable
16+
fun VideoPlayer(file: Path, suspend: Boolean = false, modifier: Modifier = Modifier) {
17+
val mpv = remember {
18+
MPV().apply {
19+
setOption("terminal", "yes")
20+
setOption("msg-level", "all=info")
21+
setOption("vo", "libmpv")
22+
setOption("hwdec", "auto")
23+
initialize().getOrThrow()
24+
setProperty("loop", "inf").getOrThrow()
25+
setProperty("keep-open", "yes").getOrThrow()
26+
setProperty("ao-volume", "100").getOrNull()
27+
}
28+
}
29+
var render: MPV.Render? by remember { mutableStateOf(null) }
30+
var ready by remember { mutableStateOf(false) }
31+
LaunchedEffect(ready, file) {
32+
withContext(Dispatchers.Default) {
33+
while (!ready) {
34+
delay(10)
35+
}
36+
println("Loading file")
37+
mpv.commandAsync("loadfile", file.toAbsolutePath().toString()).getOrThrow()
38+
}
39+
}
40+
LaunchedEffect(ready, suspend) {
41+
withContext(Dispatchers.Default) {
42+
mpv.commandAsync("set", "pause", if (suspend) "yes" else "no").getOrThrow()
43+
}
44+
}
45+
val state = rememberGLSurfaceState()
46+
GLSurfaceView(state, modifier = modifier, presentMode = GLSurface.PresentMode.MAILBOX, swapChainSize = 3) {
47+
if (!ready) {
48+
render = mpv.createRender(advancedControl = true, state::requestUpdate)
49+
ready = true
50+
}
51+
glClearColor(0f, 0f, 0f, 0f)
52+
glClear(GL_COLOR_BUFFER_BIT)
53+
render?.render(fbo)?.getOrThrow()
54+
}
55+
DisposableEffect(Unit) {
56+
onDispose {
57+
println("Disposing")
58+
render?.close()
59+
mpv.close()
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)