Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade the vlcj and Add a new Compose way to integrate with vlcj #2306

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
package org.jetbrains.compose.videoplayer.demo

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
import org.jetbrains.compose.videoplayer.VideoPlayer
import org.jetbrains.compose.videoplayer.VideoPlayerCompose
import org.jetbrains.compose.videoplayer.VideoPlayerSwing

fun main() {
singleWindowApplication(
title = "Video Player",
state = WindowState(width = 800.dp, height = 800.dp)
) {
MaterialTheme {
VideoPlayer(
url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
width = 640,
height = 480
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Swing Video Player",
fontSize = 18.sp,
color = Color.Black
)
Spacer(modifier = Modifier.height(8.dp))
VideoPlayerSwing(
url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
width = 480,
height = 360
)
Spacer(modifier = Modifier.height(8.dp))
Box(modifier = Modifier.background(Color.Black), contentAlignment = Alignment.Center) {
VideoPlayerCompose(
url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
width = 480,
height = 360
)
Text(
text = "Compose Video Player",
fontSize = 18.sp,
color = Color.White
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ kotlin {
}
named("desktopMain") {
dependencies {
implementation("uk.co.caprica:vlcj:4.7.0")
implementation("uk.co.caprica:vlcj:4.7.3")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package org.jetbrains.compose.videoplayer
import androidx.compose.runtime.Composable

@Composable
fun VideoPlayer(url: String, width: Int, height: Int) {
VideoPlayerImpl(url, width, height)
fun VideoPlayerSwing(url: String, width: Int, height: Int) {
VideoPlayerSwingImpl(url, width, height)
}

internal expect fun VideoPlayerImpl(url: String, width: Int, height: Int)
@Composable
fun VideoPlayerCompose(url: String, width: Int, height: Int) {
VideoPlayerComposeImpl(url, width, height)
}

internal expect fun VideoPlayerSwingImpl(url: String, width: Int, height: Int)

internal expect fun VideoPlayerComposeImpl(url: String, width: Int, height: Int)
Original file line number Diff line number Diff line change
@@ -1,55 +1,123 @@
package org.jetbrains.compose.videoplayer

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asComposeImageBitmap
import androidx.compose.ui.unit.dp
import org.jetbrains.skia.Bitmap
import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ImageInfo
import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery
import uk.co.caprica.vlcj.player.base.MediaPlayer
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer
import uk.co.caprica.vlcj.player.embedded.videosurface.CallbackVideoSurface
import uk.co.caprica.vlcj.player.embedded.videosurface.VideoSurfaceAdapters
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.BufferFormat
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.BufferFormatCallback
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.RenderCallback
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.format.RV32BufferFormat
import java.nio.ByteBuffer
import java.util.*
import javax.swing.JComponent


@Composable
internal actual fun VideoPlayerImpl(url: String, width: Int, height: Int) {
internal actual fun VideoPlayerSwingImpl(url: String, width: Int, height: Int) {
NativeDiscovery().discover()
val mediaPlayerComponent = remember {
// see https://github.com/caprica/vlcj/issues/887#issuecomment-503288294 for why we're using CallbackMediaPlayerComponent for macOS.
if (isMacOS()) {
CallbackMediaPlayerComponent()
} else {
EmbeddedMediaPlayerComponent()
}
}
SideEffect {
val mediaPlayerComponent = rememberMediaPlayerComponent()
DisposableEffect(Unit) {
mediaPlayerComponent.mediaPlayer().media().play(url)
onDispose {
mediaPlayerComponent.mediaPlayer().release()
}
}
return SwingPanel(background = Color.Transparent, modifier = Modifier.width(width.dp).height(height.dp), factory = {
mediaPlayerComponent
})
}

@Composable
internal actual fun VideoPlayerComposeImpl(url: String, width: Int, height: Int) {
NativeDiscovery().discover()
var imageBitmap by remember { mutableStateOf(ImageBitmap(width, height)) }
Image(modifier = Modifier.width(width.dp).height(height.dp), bitmap = imageBitmap, contentDescription = "Video")
val mediaPlayerComponent = rememberMediaPlayerComponent()
DisposableEffect(Unit) {
var byteArray: ByteArray? = null
var imageInfo: ImageInfo? = null
val mediaPlayer = mediaPlayerComponent.mediaPlayer()
val callbackVideoSurface = CallbackVideoSurface(
object : BufferFormatCallback {

override fun getBufferFormat(sourceWidth: Int, sourceHeight: Int): BufferFormat {
imageInfo = ImageInfo.makeN32(sourceWidth, sourceHeight, ColorAlphaType.OPAQUE)
return RV32BufferFormat(sourceWidth, sourceHeight)
}

override fun allocatedBuffers(buffers: Array<out ByteBuffer>) {
byteArray = ByteArray(buffers[0].limit())
}
},
object : RenderCallback {

override fun display(
mediaPlayer: MediaPlayer,
nativeBuffers: Array<out ByteBuffer>,
bufferFormat: BufferFormat
) {
imageInfo?.let {
val byteBuffer = nativeBuffers[0]
byteBuffer.get(byteArray)
byteBuffer.rewind()
imageBitmap = Bitmap().apply {
allocPixels(it)
installPixels(byteArray)
}.asComposeImageBitmap()
}
}
},
true,
VideoSurfaceAdapters.getVideoSurfaceAdapter(),
)
mediaPlayer.videoSurface().set(callbackVideoSurface)
mediaPlayer.media().play(url)
onDispose {
mediaPlayerComponent.mediaPlayer().release()
mediaPlayer.release()
}
}
}

return SwingPanel(
background = Color.Transparent,
modifier = Modifier.fillMaxSize(),
factory = {
mediaPlayerComponent
@Composable
fun rememberMediaPlayerComponent(): JComponent {
return remember {
// see https://github.com/caprica/vlcj/issues/887#issuecomment-503288294 for why we're using CallbackMediaPlayerComponent for macOS.
if (isMacOS()) {
CallbackMediaPlayerComponent()
} else {
EmbeddedMediaPlayerComponent()
}
)
}
}

/**
* To return mediaPlayer from player components.
* The method names are same, but they don't share the same parent/interface.
* That's why need this method.
*/
private fun Any.mediaPlayer(): MediaPlayer {
private fun Any.mediaPlayer(): EmbeddedMediaPlayer {
return when (this) {
is CallbackMediaPlayerComponent -> mediaPlayer()
is EmbeddedMediaPlayerComponent -> mediaPlayer()
Expand Down