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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ kotlin {
implementation(libs.ktor.client.okhttp)
implementation(libs.appdirs)
implementation(libs.slf4j.simple)
implementation(libs.vlcj)
implementation("net.java.dev.jna:jna:5.15.0")
implementation("net.java.dev.jna:jna-platform:5.15.0")
}
}

Expand Down
49 changes: 27 additions & 22 deletions app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,36 @@ import com.daniebeler.pfpixelix.utils.configureLogger
import java.awt.Desktop
import java.awt.Dimension

fun main() = application {
val appComponent = AppComponent.Companion.create(
object : KmpContext() {},
DesktopFileService(),
DesktopAppIconManager()
)
fun main() {
//https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-desktop-swing-interoperability.html
System.setProperty("compose.swing.render.on.graphics", "true")
System.setProperty("compose.interop.blending", "true")
application {
val appComponent = AppComponent.Companion.create(
object : KmpContext() {},
DesktopFileService(),
DesktopAppIconManager()
)

configureJavaLogger()
configureJavaLogger()

SingletonImageLoader.setSafe {
appComponent.provideImageLoader()
}
SingletonImageLoader.setSafe {
appComponent.provideImageLoader()
}

Desktop.getDesktop().setOpenURIHandler { url ->
appComponent.systemUrlHandler.onRedirect(
url.uri.toString()
)
}
Desktop.getDesktop().setOpenURIHandler { url ->
appComponent.systemUrlHandler.onRedirect(
url.uri.toString()
)
}

Window(
title = "Pixelix",
state = rememberWindowState(width = 600.dp, height = 1000.dp),
onCloseRequest = ::exitApplication,
) {
window.minimumSize = Dimension(400, 600)
App(appComponent) { exitApplication() }
Window(
title = "Pixelix",
state = rememberWindowState(width = 600.dp, height = 1000.dp),
onCloseRequest = ::exitApplication,
) {
window.minimumSize = Dimension(400, 600)
App(appComponent) { exitApplication() }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,92 @@
package com.daniebeler.pfpixelix.utils

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.video_player_not_supported
import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery
import uk.co.caprica.vlcj.factory.discovery.strategy.OsxNativeDiscoveryStrategy
import uk.co.caprica.vlcj.player.base.MediaPlayer
import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent
import uk.co.caprica.vlcj.player.component.InputEvents
import java.awt.Component
import java.util.Locale

actual class VideoPlayer actual constructor(
context: KmpContext,
private val coroutineScope: CoroutineScope
) {
private val mpComponent = initializeMediaPlayerComponent()
private val player = mpComponent.mediaPlayer()

actual var progress: ((current: Long, duration: Long) -> Unit)? = null
actual var hasAudio: ((Boolean) -> Unit)? = null

private val listener = object : MediaPlayerEventAdapter() {
override fun mediaPlayerReady(mediaPlayer: MediaPlayer?) {
hasAudio?.invoke(player.audio().trackCount() > 0)
}

override fun positionChanged(mediaPlayer: MediaPlayer?, newPosition: Float) {
val status = player.status()
progress?.invoke((status.length() * status.position()).toLong(), status.length())
}
}

init {
player.events().addMediaPlayerEventListener(listener)
}

@Composable
actual fun view(modifier: Modifier) {
Box(
modifier.background(MaterialTheme.colors.secondaryVariant),
contentAlignment = Alignment.Center
) {
Text(stringResource(Res.string.video_player_not_supported))
}
SwingPanel(
factory = { mpComponent },
background = Color.Transparent,
modifier = modifier
)
}

actual fun prepare(url: String) {
player.media().prepare(url)
}

actual fun play() {
player.controls().play()
}

actual fun pause() {
player.controls().pause()
}

actual fun release() {
player.events().removeMediaPlayerEventListener(listener)
player.release()
}

actual fun audio(enable: Boolean) {
player.audio().isMute = !enable
}

private fun Component.mediaPlayer() = when (this) {
is CallbackMediaPlayerComponent -> mediaPlayer()
is EmbeddedMediaPlayerComponent -> mediaPlayer()
else -> error("mediaPlayer() can only be called on vlcj player components")
}

actual fun prepare(url: String) {}
private fun initializeMediaPlayerComponent(): Component {
NativeDiscovery(OsxNativeDiscoveryStrategy()).discover()
return if (isMacOS()) {
CallbackMediaPlayerComponent(null, null, InputEvents.NONE, true, null)
} else {
EmbeddedMediaPlayerComponent(null, null, null, InputEvents.NONE, null)
}
}

actual fun play() {}
actual fun pause() {}
actual fun release() {}
actual fun audio(enable: Boolean) {}
private fun isMacOS(): Boolean {
val os = System.getProperty("os.name", "generic").lowercase(Locale.ENGLISH)
return "mac" in os || "darwin" in os
}
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ workRuntimeKtx = "2.10.0"
#desktop
appdirs = "1.1.1"
slf4jSimple = "2.0.17"
vlcj = "4.10.1"

[libraries]

Expand Down Expand Up @@ -105,6 +106,7 @@ filekit-compose = { module = "io.github.vinceglb:filekit-compose", version.ref =
krop = { module = "com.attafitamim.krop:ui", version.ref = "krop" }
appdirs = { module = "ca.gosyer:kotlin-multiplatform-appdirs", version.ref = "appdirs" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4jSimple" }
vlcj = { module = "uk.co.caprica:vlcj", version.ref = "vlcj" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand Down