Joel Denke
01/22/2024, 10:44 AMElijah Semyonov
01/22/2024, 2:52 PMJoel Denke
01/22/2024, 4:12 PM@Composable
actual fun MyVideoPlayer(
modifier: Modifier,
resource: MyResource,
settings: MyVideoSettings
) {
val resourcePath = resource.absolutePath()
val url = NSURL.fileURLWithPath(resourcePath)
val playerItem = AVPlayerItem.playerItemWithURL(url)
val player = remember { AVPlayer.playerWithPlayerItem(playerItem) }
val playerLayer = remember { AVPlayerLayer() }
val avPlayerViewController = remember { AVPlayerViewController() }
avPlayerViewController.player = player
avPlayerViewController.showsPlaybackControls = settings.showController
avPlayerViewController.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer.player = player
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
UIKitView(
factory = {
val playerContainer = UIView()
playerContainer.addSubview(avPlayerViewController.view)
playerContainer
},
onResize = { view: UIView, rect: CValue<CGRect> ->
CATransaction.begin()
CATransaction.setValue(true, kCATransactionDisableActions)
view.layer.setFrame(rect)
playerLayer.setFrame(rect)
avPlayerViewController.view.layer.frame = rect
CATransaction.commit()
},
update = {
player.play()
avPlayerViewController.player?.play()
},
modifier = modifier
)
}
Joel Denke
01/22/2024, 4:14 PMElijah Semyonov
01/22/2024, 4:14 PMJoel Denke
01/22/2024, 4:14 PMBox(Modifier.fillMaxSize()) {
MyVideoPlayer(
modifier = Modifier.fillMaxSize(),
resource = myResource("raw/video.mp4")
//url = "<https://test.com/video.mp4>
)
Scaffold( ...
}
Joel Denke
01/22/2024, 4:15 PMprivate fun resourcePath() = NSBundle.mainBundle.resourcePath + "/compose-resources/" + path
Joel Denke
01/22/2024, 4:20 PMJoel Denke
01/23/2024, 8:07 AMJoel Denke
01/23/2024, 8:08 AMElijah Semyonov
01/23/2024, 8:12 AMJoel Denke
01/23/2024, 8:13 AMJoel Denke
01/23/2024, 8:14 AMElijah Semyonov
01/23/2024, 8:14 AMElijah Semyonov
01/23/2024, 8:16 AMJoel Denke
01/23/2024, 8:18 AMElijah Semyonov
01/23/2024, 9:58 AMJoel Denke
01/23/2024, 10:07 AMJoel Denke
01/23/2024, 10:07 AMJoel Denke
01/23/2024, 10:08 AMJoel Denke
01/23/2024, 10:09 AMJoel Denke
01/23/2024, 10:13 AMElijah Semyonov
01/23/2024, 10:48 AMJoel Denke
01/23/2024, 10:48 AMElijah Semyonov
01/23/2024, 10:50 AMviewDidDisappear
on Compose ViewController, triggering eager ComposeScene disposal together with interop views, that were the source of modal expansion, it should be fixed now and is coming in betaJoel Denke
01/23/2024, 10:51 AMElijah Semyonov
01/23/2024, 10:51 AMJoel Denke
01/23/2024, 10:53 AMJoel Denke
01/23/2024, 10:54 AMElijah Semyonov
01/23/2024, 10:55 AMJoel Denke
01/23/2024, 10:57 AMJoel Denke
01/23/2024, 10:57 AMElijah Semyonov
01/23/2024, 11:35 AMKonstantin Tskhovrebov
01/23/2024, 11:46 AMJoel Denke
01/23/2024, 12:00 PMKonstantin Tskhovrebov
01/23/2024, 12:59 PMElijah Semyonov
01/23/2024, 1:00 PMJoel Denke
01/23/2024, 1:03 PM@Composable
override fun absolutePath(): String {
return LocalContext.current.packageResourcePath + "/" + path
//val classLoader = Thread.currentThread().contextClassLoader ?: CoodyPlatformResource::class.java.classLoader
//val resourceUrl: URL = requireNotNull(classLoader.getResource(path))
//return resourceUrl.path
}
The commented lines is how you do in Jetbrains versions, at least the stable version of it. Not sure about alpha or dev snapshots.Joel Denke
01/23/2024, 1:03 PMJoel Denke
01/23/2024, 1:04 PMactual class PlatformResource(private val path: String): PlatformResource {
private fun resourcePath() = NSBundle.mainBundle.resourcePath + "/compose-resources/" + path
@Composable
override fun absolutePath(): String {
return resourcePath()
}
fun readBytes(): ByteArray {
val absolutePath = resourcePath()
val contentsAtPath: NSData? = NSFileManager.defaultManager().contentsAtPath(absolutePath)
if (contentsAtPath != null) {
val byteArray = ByteArray(contentsAtPath.length.toInt())
byteArray.usePinned {
memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length)
}
return byteArray
} else {
throw IllegalStateException(path)
}
}
}
Would be nice if you had something similar in Jetbrains resources library πJoel Denke
01/23/2024, 1:06 PMKonstantin Tskhovrebov
01/23/2024, 1:08 PMJoel Denke
01/23/2024, 1:09 PMKonstantin Tskhovrebov
01/23/2024, 1:09 PMJoel Denke
01/23/2024, 1:09 PMJoel Denke
01/23/2024, 1:10 PMJoel Denke
01/23/2024, 1:11 PMcompose-resources
my code will break πKonstantin Tskhovrebov
01/23/2024, 1:12 PMKonstantin Tskhovrebov
01/23/2024, 1:12 PMJoel Denke
01/23/2024, 1:14 PMJoel Denke
01/23/2024, 1:14 PMKonstantin Tskhovrebov
01/23/2024, 3:00 PMJoel Denke
01/23/2024, 3:02 PMJoel Denke
01/23/2024, 3:04 PMJoel Denke
01/23/2024, 3:06 PMsealed interface MyImageSource {
data class Painter(val relativePath: String) : MyImageSource {
val painter: Painter
@Composable
get() {
if (LocalInspectionMode.current) {
return rememberVectorPainter(Icons.Default.Add)
}
return painterResource(relativePath)
}
}
data class Vector(val vector: ImageVector) : MyImageSource
data class Bitmap(val bitmap: ImageBitmap) : MyImageSource
data class Coil(
val coilRequest: ImageRequest,
val transform: (AsyncImagePainter.State) -> AsyncImagePainter.State = DefaultTransform,
val onState: ((AsyncImagePainter.State) -> Unit)? = null,
val clipToBounds: Boolean = true,
val filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
) : MyImageSource
}
Joel Denke
01/23/2024, 3:11 PMJoel Denke
01/23/2024, 3:14 PM@Composable
actual fun MyVideoPlayer(modifier: Modifier, resource: MyResource, settings: MyVideoSettings) {
val context = LocalContext.current
val path = File(resource.absolutePath())
val resourcePackageName = context.packageName
val resourceName = path.nameWithoutExtension
val resourceId = context.resources.getIdentifier(resourceName, "raw", context.packageName)
val uri = Uri.parse("android.resource://$resourcePackageName/$resourceId")
val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
val defaultDataSourceFactory = DefaultDataSource.Factory(context)
val dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(
context,
defaultDataSourceFactory
)
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri))
setMediaSource(source)
prepare()
}
}
exoPlayer.playWhenReady = true
exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
exoPlayer.repeatMode = Player.REPEAT_MODE_ONE
DisposableEffect(
AndroidView(factory = {
PlayerView(context).apply {
if (!settings.showController) {
hideController()
useController = false
}
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
player = exoPlayer
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
})
) {
onDispose { exoPlayer.release() }
}
}
For now I enforce all videos have to be in raw folder, which I copy to android res folder. But can easy change qualifiers/resource paths however I want.
At moment its fine for me πKonstantin Tskhovrebov
01/23/2024, 4:06 PMJoel Denke
01/23/2024, 4:33 PM