desktop: video and audio players (#3052)

* desktop: video and audio players

* making player working without preinstalled VLC

* mac support

* don't use vlc lib when not needed

* updated jna version

* changes in script

* video player lazy loading

* mac script changes

* updated build gradle for preserving atrributes of file while copying

* apply the same file stats on libs to make VLC checker happy

* updated script

* changes

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
Stanislav Dmitrenko
2023-09-22 01:03:47 +08:00
committed by GitHub
parent 6de0ed4766
commit 2d7655281f
25 changed files with 787 additions and 187 deletions

View File

@@ -27,7 +27,7 @@ actual class RecorderNative: RecorderInterface {
}
override fun start(onProgressUpdate: (position: Int?, finished: Boolean) -> Unit): String {
VideoPlayer.stopAll()
VideoPlayerHolder.stopAll()
AudioPlayer.stop()
val rec: MediaRecorder
recorder = initRecorder().also { rec = it }
@@ -140,7 +140,7 @@ actual object AudioPlayer: AudioPlayerInterface {
return null
}
VideoPlayer.stopAll()
VideoPlayerHolder.stopAll()
RecorderInterface.stopRecording?.invoke()
val current = currentlyPlaying.value
if (current == null || current.first != fileSource.filePath) {

View File

@@ -1,10 +1,13 @@
package chat.simplex.common.platform
import android.media.MediaMetadataRetriever
import android.media.session.PlaybackState
import android.net.Uri
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import chat.simplex.common.helpers.toUri
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.google.android.exoplayer2.*
@@ -17,49 +20,15 @@ import kotlinx.coroutines.*
import java.io.File
import java.net.URI
actual class VideoPlayer private constructor(
private val uri: URI,
private val gallery: Boolean,
actual class VideoPlayer actual constructor(
override val uri: URI,
override val gallery: Boolean,
private val defaultPreview: ImageBitmap,
defaultDuration: Long,
soundEnabled: Boolean
): VideoPlayerInterface {
actual companion object {
private val players: MutableMap<Pair<URI, Boolean>, VideoPlayer> = mutableMapOf()
private val previewsAndDurations: MutableMap<URI, VideoPlayerInterface.PreviewAndDuration> = mutableMapOf()
actual fun getOrCreate(
uri: URI,
gallery: Boolean,
defaultPreview: ImageBitmap,
defaultDuration: Long,
soundEnabled: Boolean
): VideoPlayer =
players.getOrPut(uri to gallery) { VideoPlayer(uri, gallery, defaultPreview, defaultDuration, soundEnabled) }
actual fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean =
player(fileName, gallery)?.enableSound(enable) == true
private fun player(fileName: String?, gallery: Boolean): VideoPlayer? {
fileName ?: return null
return players.values.firstOrNull { player -> player.uri.path?.endsWith(fileName) == true && player.gallery == gallery }
}
actual fun release(uri: URI, gallery: Boolean, remove: Boolean) =
player(uri.path, gallery)?.release(remove).run { }
actual fun stopAll() {
players.values.forEach { it.stop() }
}
actual fun releaseAll() {
players.values.forEach { it.release(false) }
players.clear()
previewsAndDurations.clear()
}
}
private val currentVolume: Float
override val soundEnabled: MutableState<Boolean> = mutableStateOf(soundEnabled)
override val brokenVideo: MutableState<Boolean> = mutableStateOf(false)
override val videoPlaying: MutableState<Boolean> = mutableStateOf(false)
@@ -114,7 +83,7 @@ actual class VideoPlayer private constructor(
RecorderInterface.stopRecording?.invoke()
}
AudioPlayer.stop()
stopAll()
VideoPlayerHolder.stopAll()
if (listener.value == null) {
runCatching {
val dataSourceFactory = DefaultDataSource.Factory(androidAppContext, DefaultHttpDataSource.Factory())
@@ -224,14 +193,14 @@ actual class VideoPlayer private constructor(
override fun release(remove: Boolean) {
player.release()
if (remove) {
players.remove(uri to gallery)
VideoPlayerHolder.players.remove(uri to gallery)
}
}
private fun setPreviewAndDuration() {
// It freezes main thread, doing it in IO thread
CoroutineScope(Dispatchers.IO).launch {
val previewAndDuration = previewsAndDurations.getOrPut(uri) { getBitmapFromVideo(uri) }
val previewAndDuration = VideoPlayerHolder.previewsAndDurations.getOrPut(uri) { getBitmapFromVideo(uri) }
withContext(Dispatchers.Main) {
preview.value = previewAndDuration.preview ?: defaultPreview
duration.value = (previewAndDuration.duration ?: 0)

View File

@@ -51,7 +51,7 @@ actual fun FullScreenImageView(modifier: Modifier, data: ByteArray, imageBitmap:
}
@Composable
actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) {
actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier, close: () -> Unit) {
AndroidView(
factory = { ctx ->
StyledPlayerView(ctx).apply {