Skip to content

Commit

Permalink
Add a new Compose way to integrate with vlcj
Browse files Browse the repository at this point in the history
  • Loading branch information
succlz123 committed Oct 9, 2022
1 parent 20d0537 commit 023f174
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 29 deletions.
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 @@ -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

0 comments on commit 023f174

Please sign in to comment.