mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-16 23:12:12 +00:00
libmpv init macos
This commit is contained in:
parent
9f7f465a25
commit
5f1370d89b
6 changed files with 347 additions and 25 deletions
|
|
@ -213,6 +213,7 @@ kotlin {
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
implementation(libs.ktor.client.java)
|
implementation(libs.ktor.client.java)
|
||||||
implementation(libs.kotlinx.coroutines.swing)
|
implementation(libs.kotlinx.coroutines.swing)
|
||||||
|
implementation(libs.jna)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,25 @@ interface PlayerEngineController {
|
||||||
fun clearExternalSubtitle()
|
fun clearExternalSubtitle()
|
||||||
fun clearExternalSubtitleAndSelect(trackIndex: Int)
|
fun clearExternalSubtitleAndSelect(trackIndex: Int)
|
||||||
fun applySubtitleStyle(style: SubtitleStyleState) {}
|
fun applySubtitleStyle(style: SubtitleStyleState) {}
|
||||||
|
fun setMetadata(
|
||||||
|
title: String,
|
||||||
|
streamTitle: String,
|
||||||
|
providerName: String,
|
||||||
|
seasonNumber: Int? = null,
|
||||||
|
episodeNumber: Int? = null,
|
||||||
|
episodeTitle: String? = null,
|
||||||
|
) {}
|
||||||
|
fun showSkipButton(type: String, endTimeMs: Long) {}
|
||||||
|
fun hideSkipButton() {}
|
||||||
|
fun showNextEpisode(
|
||||||
|
season: Int,
|
||||||
|
episode: Int,
|
||||||
|
title: String,
|
||||||
|
thumbnail: String? = null,
|
||||||
|
hasAired: Boolean = true,
|
||||||
|
) {}
|
||||||
|
fun hideNextEpisode() {}
|
||||||
|
fun setOnCloseCallback(callback: () -> Unit) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun sanitizePlaybackHeaders(headers: Map<String, String>?): Map<String, String> {
|
internal fun sanitizePlaybackHeaders(headers: Map<String, String>?): Map<String, String> {
|
||||||
|
|
|
||||||
|
|
@ -1180,6 +1180,30 @@ fun PlayerScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(activeSkipInterval, skipIntervalDismissed) {
|
||||||
|
val interval = activeSkipInterval
|
||||||
|
if (interval != null && !skipIntervalDismissed) {
|
||||||
|
playerController?.showSkipButton(interval.type, (interval.endTime * 1000).toLong())
|
||||||
|
} else {
|
||||||
|
playerController?.hideSkipButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(showNextEpisodeCard, nextEpisodeInfo) {
|
||||||
|
val info = nextEpisodeInfo
|
||||||
|
if (showNextEpisodeCard && info != null) {
|
||||||
|
playerController?.showNextEpisode(
|
||||||
|
season = info.season,
|
||||||
|
episode = info.episode,
|
||||||
|
title = info.title ?: "",
|
||||||
|
thumbnail = info.thumbnail,
|
||||||
|
hasAired = info.hasAired,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
playerController?.hideNextEpisode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve next episode info when episodes list or current episode changes
|
// Resolve next episode info when episodes list or current episode changes
|
||||||
LaunchedEffect(allEpisodes, activeSeasonNumber, activeEpisodeNumber) {
|
LaunchedEffect(allEpisodes, activeSeasonNumber, activeEpisodeNumber) {
|
||||||
if (!isSeries || allEpisodes.isEmpty()) {
|
if (!isSeries || allEpisodes.isEmpty()) {
|
||||||
|
|
@ -1407,6 +1431,15 @@ fun PlayerScreen(
|
||||||
onControllerReady = { controller ->
|
onControllerReady = { controller ->
|
||||||
playerController = controller
|
playerController = controller
|
||||||
playerControllerSourceUrl = activeSourceUrl
|
playerControllerSourceUrl = activeSourceUrl
|
||||||
|
controller.setMetadata(
|
||||||
|
title = title,
|
||||||
|
streamTitle = activeStreamTitle,
|
||||||
|
providerName = activeProviderName,
|
||||||
|
seasonNumber = activeSeasonNumber,
|
||||||
|
episodeNumber = activeEpisodeNumber,
|
||||||
|
episodeTitle = activeEpisodeTitle,
|
||||||
|
)
|
||||||
|
controller.setOnCloseCallback { onBackWithProgress() }
|
||||||
},
|
},
|
||||||
onSnapshot = { snapshot ->
|
onSnapshot = { snapshot ->
|
||||||
playbackSnapshot = snapshot
|
playbackSnapshot = snapshot
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.nuvio.app.features.player
|
||||||
|
|
||||||
|
import com.sun.jna.Library
|
||||||
|
import com.sun.jna.Native
|
||||||
|
import com.sun.jna.Pointer
|
||||||
|
|
||||||
|
internal interface DesktopMPVBridgeLib : Library {
|
||||||
|
companion object {
|
||||||
|
val INSTANCE: DesktopMPVBridgeLib by lazy {
|
||||||
|
val libPath = resolveLibraryPath()
|
||||||
|
if (libPath != null) {
|
||||||
|
System.setProperty(
|
||||||
|
"jna.library.path",
|
||||||
|
(System.getProperty("jna.library.path") ?: "") + ":" + libPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Native.load("DesktopMPVBridge", DesktopMPVBridgeLib::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveLibraryPath(): String? {
|
||||||
|
val candidates = listOf(
|
||||||
|
"MPVKit/.build/arm64-apple-macosx/release",
|
||||||
|
"MPVKit/.build/arm64-apple-macosx/debug",
|
||||||
|
"../MPVKit/.build/arm64-apple-macosx/release",
|
||||||
|
"../MPVKit/.build/arm64-apple-macosx/debug",
|
||||||
|
)
|
||||||
|
val userDir = System.getProperty("user.dir") ?: return null
|
||||||
|
for (candidate in candidates) {
|
||||||
|
val dir = java.io.File(userDir, candidate)
|
||||||
|
if (dir.exists() && dir.isDirectory) {
|
||||||
|
val dylib = java.io.File(dir, "libDesktopMPVBridge.dylib")
|
||||||
|
if (dylib.exists()) return dir.absolutePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nuvio_player_create(): Pointer
|
||||||
|
fun nuvio_player_destroy(player: Pointer)
|
||||||
|
fun nuvio_player_show(player: Pointer)
|
||||||
|
|
||||||
|
fun nuvio_player_set_metadata(
|
||||||
|
player: Pointer,
|
||||||
|
title: String,
|
||||||
|
streamTitle: String,
|
||||||
|
providerName: String,
|
||||||
|
season: Int,
|
||||||
|
episode: Int,
|
||||||
|
episodeTitle: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun nuvio_player_load_file(
|
||||||
|
player: Pointer,
|
||||||
|
url: String,
|
||||||
|
audioUrl: String?,
|
||||||
|
headersJson: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun nuvio_player_play(player: Pointer)
|
||||||
|
fun nuvio_player_pause(player: Pointer)
|
||||||
|
fun nuvio_player_seek_to(player: Pointer, positionMs: Long)
|
||||||
|
fun nuvio_player_seek_by(player: Pointer, offsetMs: Long)
|
||||||
|
fun nuvio_player_set_speed(player: Pointer, speed: Float)
|
||||||
|
fun nuvio_player_set_resize_mode(player: Pointer, mode: Int)
|
||||||
|
fun nuvio_player_retry(player: Pointer)
|
||||||
|
|
||||||
|
fun nuvio_player_refresh_state(player: Pointer)
|
||||||
|
fun nuvio_player_is_loading(player: Pointer): Boolean
|
||||||
|
fun nuvio_player_is_playing(player: Pointer): Boolean
|
||||||
|
fun nuvio_player_is_ended(player: Pointer): Boolean
|
||||||
|
fun nuvio_player_get_position_ms(player: Pointer): Long
|
||||||
|
fun nuvio_player_get_duration_ms(player: Pointer): Long
|
||||||
|
fun nuvio_player_get_buffered_ms(player: Pointer): Long
|
||||||
|
fun nuvio_player_get_speed(player: Pointer): Float
|
||||||
|
fun nuvio_player_get_error(player: Pointer): String?
|
||||||
|
|
||||||
|
fun nuvio_player_get_audio_track_count(player: Pointer): Int
|
||||||
|
fun nuvio_player_get_audio_track_id(player: Pointer, index: Int): Int
|
||||||
|
fun nuvio_player_get_audio_track_label(player: Pointer, index: Int): String?
|
||||||
|
fun nuvio_player_get_audio_track_lang(player: Pointer, index: Int): String?
|
||||||
|
fun nuvio_player_is_audio_track_selected(player: Pointer, index: Int): Boolean
|
||||||
|
fun nuvio_player_select_audio_track(player: Pointer, trackId: Int)
|
||||||
|
|
||||||
|
fun nuvio_player_get_subtitle_track_count(player: Pointer): Int
|
||||||
|
fun nuvio_player_get_subtitle_track_id(player: Pointer, index: Int): Int
|
||||||
|
fun nuvio_player_get_subtitle_track_label(player: Pointer, index: Int): String?
|
||||||
|
fun nuvio_player_get_subtitle_track_lang(player: Pointer, index: Int): String?
|
||||||
|
fun nuvio_player_is_subtitle_track_selected(player: Pointer, index: Int): Boolean
|
||||||
|
fun nuvio_player_select_subtitle_track(player: Pointer, trackId: Int)
|
||||||
|
|
||||||
|
fun nuvio_player_set_subtitle_url(player: Pointer, url: String)
|
||||||
|
fun nuvio_player_clear_external_subtitle(player: Pointer)
|
||||||
|
fun nuvio_player_clear_external_subtitle_and_select(player: Pointer, trackId: Int)
|
||||||
|
fun nuvio_player_apply_subtitle_style(
|
||||||
|
player: Pointer,
|
||||||
|
textColor: String,
|
||||||
|
outlineSize: Float,
|
||||||
|
fontSize: Float,
|
||||||
|
subPos: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun nuvio_player_show_skip_button(player: Pointer, type: String, endTimeMs: Long)
|
||||||
|
fun nuvio_player_hide_skip_button(player: Pointer)
|
||||||
|
|
||||||
|
fun nuvio_player_show_next_episode(
|
||||||
|
player: Pointer,
|
||||||
|
season: Int,
|
||||||
|
episode: Int,
|
||||||
|
title: String,
|
||||||
|
thumbnail: String?,
|
||||||
|
hasAired: Boolean,
|
||||||
|
)
|
||||||
|
fun nuvio_player_hide_next_episode(player: Pointer)
|
||||||
|
|
||||||
|
fun nuvio_player_is_closed(player: Pointer): Boolean
|
||||||
|
fun nuvio_player_pop_next_episode_pressed(player: Pointer): Boolean
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,16 @@ package com.nuvio.app.features.player
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import com.nuvio.app.core.storage.ProfileScopedKey
|
import com.nuvio.app.core.storage.ProfileScopedKey
|
||||||
import com.nuvio.app.core.sync.decodeSyncBoolean
|
import com.nuvio.app.core.sync.decodeSyncBoolean
|
||||||
import com.nuvio.app.core.sync.decodeSyncFloat
|
import com.nuvio.app.core.sync.decodeSyncFloat
|
||||||
|
|
@ -22,7 +25,9 @@ import com.nuvio.app.core.sync.encodeSyncInt
|
||||||
import com.nuvio.app.core.sync.encodeSyncString
|
import com.nuvio.app.core.sync.encodeSyncString
|
||||||
import com.nuvio.app.core.sync.encodeSyncStringSet
|
import com.nuvio.app.core.sync.encodeSyncStringSet
|
||||||
import com.nuvio.app.desktop.DesktopPreferences
|
import com.nuvio.app.desktop.DesktopPreferences
|
||||||
|
import com.sun.jna.Pointer
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
|
|
@ -42,53 +47,197 @@ actual fun PlatformPlayerSurface(
|
||||||
onSnapshot: (PlayerPlaybackSnapshot) -> Unit,
|
onSnapshot: (PlayerPlaybackSnapshot) -> Unit,
|
||||||
onError: (String?) -> Unit,
|
onError: (String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
val controller = remember {
|
val bridge = remember { DesktopMPVBridgeLib.INSTANCE }
|
||||||
|
val playerPtr = remember { bridge.nuvio_player_create() }
|
||||||
|
var onCloseCallback by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||||
|
|
||||||
|
DisposableEffect(playerPtr) {
|
||||||
|
bridge.nuvio_player_show(playerPtr)
|
||||||
|
onDispose {
|
||||||
|
bridge.nuvio_player_destroy(playerPtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(sourceUrl, sourceAudioUrl) {
|
||||||
|
val headersJson = if (sourceHeaders.isNotEmpty()) {
|
||||||
|
buildJsonObject {
|
||||||
|
sourceHeaders.forEach { (k, v) -> put(k, v) }
|
||||||
|
}.toString()
|
||||||
|
} else null
|
||||||
|
bridge.nuvio_player_load_file(playerPtr, sourceUrl, sourceAudioUrl, headersJson)
|
||||||
|
if (playWhenReady) {
|
||||||
|
bridge.nuvio_player_play(playerPtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(resizeMode) {
|
||||||
|
val mode = when (resizeMode) {
|
||||||
|
PlayerResizeMode.Fit -> 0
|
||||||
|
PlayerResizeMode.Fill -> 1
|
||||||
|
PlayerResizeMode.Zoom -> 2
|
||||||
|
}
|
||||||
|
bridge.nuvio_player_set_resize_mode(playerPtr, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
val controller = remember(playerPtr) {
|
||||||
object : PlayerEngineController {
|
object : PlayerEngineController {
|
||||||
override fun play() = Unit
|
override fun play() = bridge.nuvio_player_play(playerPtr)
|
||||||
|
override fun pause() = bridge.nuvio_player_pause(playerPtr)
|
||||||
|
override fun seekTo(positionMs: Long) = bridge.nuvio_player_seek_to(playerPtr, positionMs)
|
||||||
|
override fun seekBy(offsetMs: Long) = bridge.nuvio_player_seek_by(playerPtr, offsetMs)
|
||||||
|
override fun retry() = bridge.nuvio_player_retry(playerPtr)
|
||||||
|
override fun setPlaybackSpeed(speed: Float) = bridge.nuvio_player_set_speed(playerPtr, speed)
|
||||||
|
|
||||||
override fun pause() = Unit
|
override fun getAudioTracks(): List<AudioTrack> {
|
||||||
|
val count = bridge.nuvio_player_get_audio_track_count(playerPtr)
|
||||||
|
return (0 until count).map { i ->
|
||||||
|
AudioTrack(
|
||||||
|
index = i,
|
||||||
|
id = bridge.nuvio_player_get_audio_track_id(playerPtr, i).toString(),
|
||||||
|
label = bridge.nuvio_player_get_audio_track_label(playerPtr, i) ?: "",
|
||||||
|
language = bridge.nuvio_player_get_audio_track_lang(playerPtr, i),
|
||||||
|
isSelected = bridge.nuvio_player_is_audio_track_selected(playerPtr, i),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun seekTo(positionMs: Long) = Unit
|
override fun getSubtitleTracks(): List<SubtitleTrack> {
|
||||||
|
val count = bridge.nuvio_player_get_subtitle_track_count(playerPtr)
|
||||||
|
return (0 until count).map { i ->
|
||||||
|
SubtitleTrack(
|
||||||
|
index = i,
|
||||||
|
id = bridge.nuvio_player_get_subtitle_track_id(playerPtr, i).toString(),
|
||||||
|
label = bridge.nuvio_player_get_subtitle_track_label(playerPtr, i) ?: "",
|
||||||
|
language = bridge.nuvio_player_get_subtitle_track_lang(playerPtr, i),
|
||||||
|
isSelected = bridge.nuvio_player_is_subtitle_track_selected(playerPtr, i),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun seekBy(offsetMs: Long) = Unit
|
override fun selectAudioTrack(index: Int) {
|
||||||
|
val count = bridge.nuvio_player_get_audio_track_count(playerPtr)
|
||||||
|
if (index in 0 until count) {
|
||||||
|
val trackId = bridge.nuvio_player_get_audio_track_id(playerPtr, index)
|
||||||
|
bridge.nuvio_player_select_audio_track(playerPtr, trackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun retry() = Unit
|
override fun selectSubtitleTrack(index: Int) {
|
||||||
|
if (index < 0) {
|
||||||
|
bridge.nuvio_player_select_subtitle_track(playerPtr, -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val count = bridge.nuvio_player_get_subtitle_track_count(playerPtr)
|
||||||
|
if (index in 0 until count) {
|
||||||
|
val trackId = bridge.nuvio_player_get_subtitle_track_id(playerPtr, index)
|
||||||
|
bridge.nuvio_player_select_subtitle_track(playerPtr, trackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun setPlaybackSpeed(speed: Float) = Unit
|
override fun setSubtitleUri(url: String) =
|
||||||
|
bridge.nuvio_player_set_subtitle_url(playerPtr, url)
|
||||||
|
|
||||||
override fun getAudioTracks(): List<AudioTrack> = emptyList()
|
override fun clearExternalSubtitle() =
|
||||||
|
bridge.nuvio_player_clear_external_subtitle(playerPtr)
|
||||||
|
|
||||||
override fun getSubtitleTracks(): List<SubtitleTrack> = emptyList()
|
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
|
||||||
|
val trackId = if (trackIndex >= 0) {
|
||||||
|
val count = bridge.nuvio_player_get_subtitle_track_count(playerPtr)
|
||||||
|
if (trackIndex < count) bridge.nuvio_player_get_subtitle_track_id(playerPtr, trackIndex) else -1
|
||||||
|
} else -1
|
||||||
|
bridge.nuvio_player_clear_external_subtitle_and_select(playerPtr, trackId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun selectAudioTrack(index: Int) = Unit
|
override fun applySubtitleStyle(style: SubtitleStyleState) {
|
||||||
|
val colorHex = style.textColor.toMpvColorString()
|
||||||
|
val outline = if (style.outlineEnabled) 2.0f else 0.0f
|
||||||
|
bridge.nuvio_player_apply_subtitle_style(
|
||||||
|
playerPtr, colorHex, outline, style.fontSizeSp.toFloat(), style.bottomOffset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun selectSubtitleTrack(index: Int) = Unit
|
override fun setMetadata(
|
||||||
|
title: String,
|
||||||
|
streamTitle: String,
|
||||||
|
providerName: String,
|
||||||
|
seasonNumber: Int?,
|
||||||
|
episodeNumber: Int?,
|
||||||
|
episodeTitle: String?,
|
||||||
|
) {
|
||||||
|
bridge.nuvio_player_set_metadata(
|
||||||
|
playerPtr, title, streamTitle, providerName,
|
||||||
|
seasonNumber ?: 0, episodeNumber ?: 0, episodeTitle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun setSubtitleUri(url: String) = Unit
|
override fun showSkipButton(type: String, endTimeMs: Long) {
|
||||||
|
bridge.nuvio_player_show_skip_button(playerPtr, type, endTimeMs)
|
||||||
|
}
|
||||||
|
|
||||||
override fun clearExternalSubtitle() = Unit
|
override fun hideSkipButton() {
|
||||||
|
bridge.nuvio_player_hide_skip_button(playerPtr)
|
||||||
|
}
|
||||||
|
|
||||||
override fun clearExternalSubtitleAndSelect(trackIndex: Int) = Unit
|
override fun showNextEpisode(
|
||||||
|
season: Int,
|
||||||
|
episode: Int,
|
||||||
|
title: String,
|
||||||
|
thumbnail: String?,
|
||||||
|
hasAired: Boolean,
|
||||||
|
) {
|
||||||
|
bridge.nuvio_player_show_next_episode(playerPtr, season, episode, title, thumbnail, hasAired)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hideNextEpisode() {
|
||||||
|
bridge.nuvio_player_hide_next_episode(playerPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnCloseCallback(callback: () -> Unit) {
|
||||||
|
onCloseCallback = callback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(controller) {
|
LaunchedEffect(controller) {
|
||||||
onControllerReady(controller)
|
onControllerReady(controller)
|
||||||
onSnapshot(PlayerPlaybackSnapshot(isLoading = false))
|
|
||||||
onError(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
LaunchedEffect(playerPtr) {
|
||||||
modifier = modifier.background(Color.Black),
|
while (true) {
|
||||||
contentAlignment = Alignment.Center,
|
delay(250)
|
||||||
) {
|
if (bridge.nuvio_player_is_closed(playerPtr)) {
|
||||||
Text(
|
onCloseCallback?.invoke()
|
||||||
text = "Desktop playback is not implemented yet.",
|
break
|
||||||
color = Color.White,
|
}
|
||||||
)
|
bridge.nuvio_player_refresh_state(playerPtr)
|
||||||
|
val snapshot = PlayerPlaybackSnapshot(
|
||||||
|
isLoading = bridge.nuvio_player_is_loading(playerPtr),
|
||||||
|
isPlaying = bridge.nuvio_player_is_playing(playerPtr),
|
||||||
|
isEnded = bridge.nuvio_player_is_ended(playerPtr),
|
||||||
|
positionMs = bridge.nuvio_player_get_position_ms(playerPtr),
|
||||||
|
durationMs = bridge.nuvio_player_get_duration_ms(playerPtr),
|
||||||
|
bufferedPositionMs = bridge.nuvio_player_get_buffered_ms(playerPtr),
|
||||||
|
playbackSpeed = bridge.nuvio_player_get_speed(playerPtr),
|
||||||
|
)
|
||||||
|
onSnapshot(snapshot)
|
||||||
|
val error = bridge.nuvio_player_get_error(playerPtr)
|
||||||
|
onError(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(modifier = modifier.background(Color.Black))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun androidx.compose.ui.graphics.Color.toMpvColorString(): String {
|
||||||
|
val r = (red * 255).toInt().coerceIn(0, 255)
|
||||||
|
val g = (green * 255).toInt().coerceIn(0, 255)
|
||||||
|
val b = (blue * 255).toInt().coerceIn(0, 255)
|
||||||
|
val a = (alpha * 255).toInt().coerceIn(0, 255)
|
||||||
|
return "#${r.hex()}${g.hex()}${b.hex()}${a.hex()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.hex(): String = toString(16).padStart(2, '0').uppercase()
|
||||||
|
|
||||||
internal actual object DeviceLanguagePreferences {
|
internal actual object DeviceLanguagePreferences {
|
||||||
actual fun preferredLanguageCodes(): List<String> =
|
actual fun preferredLanguageCodes(): List<String> =
|
||||||
listOfNotNull(Locale.getDefault().toLanguageTag().takeIf { it.isNotBlank() })
|
listOfNotNull(Locale.getDefault().toLanguageTag().takeIf { it.isNotBlank() })
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ supabase = "3.4.1"
|
||||||
quickjsKt = "1.0.1"
|
quickjsKt = "1.0.1"
|
||||||
ksoup = "0.2.6"
|
ksoup = "0.2.6"
|
||||||
reorderable = "3.0.0"
|
reorderable = "3.0.0"
|
||||||
|
jna = "5.14.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
|
|
@ -76,6 +77,7 @@ supabase-functions = { module = "io.github.jan-tennert.supabase:functions-kt", v
|
||||||
quickjs-kt = { module = "io.github.dokar3:quickjs-kt", version.ref = "quickjsKt" }
|
quickjs-kt = { module = "io.github.dokar3:quickjs-kt", version.ref = "quickjsKt" }
|
||||||
ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" }
|
ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" }
|
||||||
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
|
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
|
||||||
|
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue