fix: Lots of small adjustments and changes to native player

This commit is contained in:
PartyDonut 2025-10-16 15:11:52 +02:00
parent 360b87dacb
commit b4e68c9e15
14 changed files with 197 additions and 156 deletions

View file

@ -62,6 +62,7 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop"
android:exported="true" /> android:exported="true" />
<service <service

View file

@ -61,7 +61,11 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
override fun launchActivity(callback: (Result<StartResult>) -> Unit) { override fun launchActivity(callback: (Result<StartResult>) -> Unit) {
try { try {
videoPlayerCallback = callback videoPlayerCallback = callback
val intent = Intent(this, VideoPlayerActivity::class.java)
val intent = Intent(this, VideoPlayerActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}
videoPlayerLauncher.launch(intent) videoPlayerLauncher.launch(intent)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()

View file

@ -38,6 +38,11 @@ class VideoPlayerActivity : ComponentActivity() {
} }
} }
} }
override fun onPause() {
super.onPause()
VideoPlayerObject.implementation.pause()
}
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)

View file

@ -79,7 +79,6 @@ import nl.jknaapen.fladder.utility.ImmersiveSystemBars
import nl.jknaapen.fladder.utility.defaultSelected import nl.jknaapen.fladder.utility.defaultSelected
import nl.jknaapen.fladder.utility.leanBackEnabled import nl.jknaapen.fladder.utility.leanBackEnabled
import nl.jknaapen.fladder.utility.visible import nl.jknaapen.fladder.utility.visible
import kotlin.math.absoluteValue
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -143,7 +142,8 @@ fun CustomVideoControls(
// Restart the multiplier // Restart the multiplier
LaunchedEffect(lastSeekInteraction.longValue) { LaunchedEffect(lastSeekInteraction.longValue) {
delay(2.seconds) delay(1.seconds)
if (currentSkipTime == 0L) return@LaunchedEffect
player?.seekTo(position + currentSkipTime) player?.seekTo(position + currentSkipTime)
currentSkipTime = 0L currentSkipTime = 0L
} }
@ -167,18 +167,12 @@ fun CustomVideoControls(
if (!showControls) { if (!showControls) {
when (keyEvent.key) { when (keyEvent.key) {
DirectionLeft -> { DirectionLeft -> {
if (currentSkipTime == 0L) {
player?.seekTo(position - backwardSpeed.inWholeMilliseconds)
}
currentSkipTime -= backwardSpeed.inWholeMilliseconds currentSkipTime -= backwardSpeed.inWholeMilliseconds
updateSeekInteraction() updateSeekInteraction()
return@onKeyEvent true return@onKeyEvent true
} }
DirectionRight -> { DirectionRight -> {
if (currentSkipTime.absoluteValue == 0L) {
player?.seekTo(position + forwardSpeed.inWholeMilliseconds)
}
currentSkipTime += forwardSpeed.inWholeMilliseconds currentSkipTime += forwardSpeed.inWholeMilliseconds
updateSeekInteraction() updateSeekInteraction()
return@onKeyEvent true return@onKeyEvent true

View file

@ -19,7 +19,6 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.clearAudioTrack import nl.jknaapen.fladder.utility.clearAudioTrack
import nl.jknaapen.fladder.utility.conditional
import nl.jknaapen.fladder.utility.setInternalAudioTrack import nl.jknaapen.fladder.utility.setInternalAudioTrack
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@ -32,15 +31,31 @@ fun AudioPicker(
val audioTracks by VideoPlayerObject.audioTracks.collectAsState(listOf()) val audioTracks by VideoPlayerObject.audioTracks.collectAsState(listOf())
val internalAudioTracks by VideoPlayerObject.exoAudioTracks val internalAudioTracks by VideoPlayerObject.exoAudioTracks
val focusRequester = remember { FocusRequester() } val focusOffTrack = remember { FocusRequester() }
val focusRequesters = remember(internalAudioTracks) {
internalAudioTracks.associateWith { FocusRequester() }
}
val listState = rememberLazyListState() val listState = rememberLazyListState()
LaunchedEffect(selectedIndex) { LaunchedEffect(selectedIndex, audioTracks, internalAudioTracks) {
if (selectedIndex == -1) return@LaunchedEffect if (selectedIndex == -1) {
listState.scrollToItem( focusOffTrack.requestFocus()
audioTracks.indexOfFirst { it.index == selectedIndex.toLong() } return@LaunchedEffect
) }
val serverTrackIndex = audioTracks.indexOfFirst { it.index == selectedIndex.toLong() }
if (serverTrackIndex <= 0) {
focusOffTrack.requestFocus()
return@LaunchedEffect
}
val internalIndex = serverTrackIndex - 1
val lazyColumnIndex = internalIndex + 1
listState.scrollToItem(lazyColumnIndex)
focusRequesters[internalAudioTracks[internalIndex]]?.requestFocus()
} }
CustomModalBottomSheet( CustomModalBottomSheet(
@ -54,45 +69,37 @@ fun AudioPicker(
.padding(horizontal = 8.dp, vertical = 16.dp), .padding(horizontal = 8.dp, vertical = 16.dp),
) { ) {
item { item {
val selectedOff = -1 == selectedIndex val selectedOff = selectedIndex == -1
TrackButton( TrackButton(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.conditional(selectedOff) { .focusRequester(focusOffTrack),
focusRequester(focusRequester)
},
onClick = { onClick = {
VideoPlayerObject.setAudioTrackIndex(-1) VideoPlayerObject.setAudioTrackIndex(-1)
player.clearAudioTrack() player.clearAudioTrack()
}, },
selected = selectedOff selected = selectedOff
) { ) {
Text( Text("Off")
text = "Off",
)
} }
} }
internalAudioTracks.forEachIndexed { index, track -> internalAudioTracks.forEachIndexed { index, track ->
val serverTrack = audioTracks.elementAtOrNull(index + 1) val serverTrack = audioTracks.elementAtOrNull(index + 1)
val selected = serverTrack?.index == selectedIndex.toLong() val selected = serverTrack?.index?.toInt() == selectedIndex
item { item {
TrackButton( TrackButton(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.conditional(selected) { .focusRequester(focusRequesters[track]!!),
focusRequester(focusRequester)
},
onClick = { onClick = {
serverTrack?.index?.let { serverTrack?.index?.let { VideoPlayerObject.setAudioTrackIndex(it.toInt()) }
VideoPlayerObject.setAudioTrackIndex(it.toInt())
}
player.setInternalAudioTrack(track) player.setInternalAudioTrack(track)
}, },
selected = selected selected = selected
) { ) {
Text( Text(serverTrack?.name ?: "")
text = serverTrack?.name ?: "",
)
} }
} }
} }

View file

@ -2,18 +2,20 @@ package nl.jknaapen.fladder.composables.dialogs
import Chapter import Chapter
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -22,20 +24,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue 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.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.conditional import nl.jknaapen.fladder.utility.highlightOnFocus
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -47,27 +47,23 @@ internal fun ChapterSelectionSheet(
val chapters = playbackData?.chapters ?: listOf() val chapters = playbackData?.chapters ?: listOf()
val currentPosition by VideoPlayerObject.position.collectAsState(0L) val currentPosition by VideoPlayerObject.position.collectAsState(0L)
val focusRequester = remember { FocusRequester() } val focusRequesters = remember(chapters) {
chapters.associateWith { FocusRequester() }
}
if (chapters.isEmpty()) return if (chapters.isEmpty()) return
var currentChapter: Chapter? by remember {
mutableStateOf(
chapters[chapters.indexOfCurrent(
currentPosition
)]
)
}
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
LaunchedEffect(chapters, currentPosition) { val currentChapterIndex = remember(currentPosition) {
val chapter = chapters.indexOfCurrent(currentPosition) chapters.indexOfCurrent(currentPosition)
lazyListState.animateScrollToItem( }
chapter
) val currentChapter = chapters.getOrNull(currentChapterIndex)
currentChapter = chapters[chapter]
focusRequester.requestFocus() LaunchedEffect(currentChapter) {
lazyListState.animateScrollToItem(chapters.indexOf(currentChapter))
focusRequesters[currentChapter]?.requestFocus()
} }
CustomModalBottomSheet( CustomModalBottomSheet(
@ -76,6 +72,8 @@ internal fun ChapterSelectionSheet(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(0.55f)
.wrapContentHeight()
.padding(horizontal = 16.dp, vertical = 16.dp) .padding(horizontal = 16.dp, vertical = 16.dp)
.wrapContentHeight(), .wrapContentHeight(),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@ -90,36 +88,11 @@ internal fun ChapterSelectionSheet(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
chapters.forEachIndexed { index, chapter -> chapters.forEachIndexed { index, chapter ->
val selectedChapter = currentChapter == chapter
val isCurrentChapter = chapters.indexOfCurrent(currentPosition) == index val isCurrentChapter = chapters.indexOfCurrent(currentPosition) == index
item { item {
Column( Column(
modifier = Modifier modifier = Modifier
.background(
color = if (selectedChapter) Color.White.copy(alpha = 0.25f) else Color.Black.copy(
alpha = 0.75f
),
shape = RoundedCornerShape(8.dp)
)
.aspectRatio(1.67f)
.border(
width = 2.dp,
color = Color.White.copy(alpha = if (selectedChapter) 0.45f else 0f),
shape = RoundedCornerShape(8.dp)
)
.conditional(selectedChapter) {
focusRequester(focusRequester)
}
.onFocusChanged {
if (it.isFocused) {
currentChapter = chapter
}
}
.clickable(
onClick = {
onSelected(chapter)
}
)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy( verticalArrangement = Arrangement.spacedBy(
@ -127,33 +100,48 @@ internal fun ChapterSelectionSheet(
alignment = Alignment.CenterVertically alignment = Alignment.CenterVertically
), ),
) { ) {
AsyncImage(
model = chapter.url,
modifier = Modifier
.focusRequester(focusRequesters[chapter]!!)
.highlightOnFocus(
color = MaterialTheme.colorScheme.primary,
width = 3.dp,
shape = RoundedCornerShape(24.dp)
)
.clickable(
onClick = {
onSelected(chapter)
}
)
.aspectRatio(1.67f)
.clip(shape = RoundedCornerShape(24.dp))
.weight(1f),
contentDescription = "",
contentScale = ContentScale.FillBounds
)
Row( Row(
horizontalArrangement = Arrangement.spacedBy( horizontalArrangement = Arrangement.spacedBy(
8.dp, 8.dp,
alignment = Alignment.CenterHorizontally alignment = Alignment.Start
), ),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
if (isCurrentChapter)
Box(
modifier = Modifier
.size(16.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = CircleShape
)
)
Text( Text(
chapter.name, chapter.name,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
color = Color.White color = Color.White
) )
} }
AsyncImage(
model = chapter.url,
modifier = Modifier
.clip(
shape = RoundedCornerShape(24.dp)
)
.heightIn(min = 125.dp, max = 150.dp)
.border(
width = 2.dp,
color = Color.White.copy(alpha = if (isCurrentChapter) 1f else 0f),
shape = RoundedCornerShape(24.dp)
),
contentDescription = ""
)
} }
} }
} }
@ -163,9 +151,13 @@ internal fun ChapterSelectionSheet(
} }
private fun List<Chapter>.indexOfCurrent(currentPosition: Long): Int { private fun List<Chapter>.indexOfCurrent(currentPosition: Long): Int {
return this.indexOfFirst { chapter -> if (isEmpty()) return 0
val nextChapterTime =
this.getOrNull(this.indexOf(chapter) + 1)?.time ?: Long.MAX_VALUE for (i in indices) {
currentPosition >= chapter.time && currentPosition < nextChapterTime val chapter = this[i]
val nextTime = getOrNull(i + 1)?.time ?: Long.MAX_VALUE
if (currentPosition in chapter.time until nextTime) return i
} }
return if (currentPosition < first().time) 0 else lastIndex
} }

View file

@ -6,10 +6,10 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -29,7 +29,11 @@ internal fun CustomModalBottomSheet(
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
val modalBottomSheetState = rememberModalBottomSheetState( val modalBottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true skipPartiallyExpanded = true,
confirmValueChange = { newValue ->
newValue == SheetValue.Expanded ||
newValue == SheetValue.Hidden
}
) )
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -47,7 +51,6 @@ internal fun CustomModalBottomSheet(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight()
.displayCutoutPadding() .displayCutoutPadding()
.background( .background(
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),

View file

@ -20,7 +20,6 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.clearSubtitleTrack import nl.jknaapen.fladder.utility.clearSubtitleTrack
import nl.jknaapen.fladder.utility.conditional
import nl.jknaapen.fladder.utility.setInternalSubtitleTrack import nl.jknaapen.fladder.utility.setInternalSubtitleTrack
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@ -33,16 +32,27 @@ fun SubtitlePicker(
val subTitles by VideoPlayerObject.subtitleTracks.collectAsState(listOf()) val subTitles by VideoPlayerObject.subtitleTracks.collectAsState(listOf())
val internalSubTracks by VideoPlayerObject.exoSubTracks val internalSubTracks by VideoPlayerObject.exoSubTracks
val focusRequester = remember { FocusRequester() } val focusOffTrack = remember { FocusRequester() }
val focusRequesters = remember(internalSubTracks) {
internalSubTracks.associateWith { FocusRequester() }
}
val listState = rememberLazyListState() val listState = rememberLazyListState()
LaunchedEffect(selectedIndex, subTitles) { LaunchedEffect(selectedIndex, subTitles, internalSubTracks) {
if (selectedIndex == -1) return@LaunchedEffect val serverSubIndex = subTitles.indexOfFirst { it.index == selectedIndex.toLong() }
listState.scrollToItem(
subTitles.indexOfFirst { it.index == selectedIndex.toLong() } if (serverSubIndex <= 0) {
) focusOffTrack.requestFocus()
focusRequester.requestFocus() return@LaunchedEffect
}
val internalIndex = serverSubIndex - 1
val lazyColumnIndex = internalIndex + 1
listState.scrollToItem(lazyColumnIndex)
focusRequesters[internalSubTracks[internalIndex]]?.requestFocus()
} }
CustomModalBottomSheet( CustomModalBottomSheet(
@ -60,9 +70,7 @@ fun SubtitlePicker(
TrackButton( TrackButton(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.conditional(selectedOff) { .focusRequester(focusOffTrack),
focusRequester(focusRequester)
},
onClick = { onClick = {
VideoPlayerObject.setSubtitleTrackIndex(-1) VideoPlayerObject.setSubtitleTrackIndex(-1)
player.clearSubtitleTrack() player.clearSubtitleTrack()
@ -81,9 +89,7 @@ fun SubtitlePicker(
TrackButton( TrackButton(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.conditional(selected) { .focusRequester(focusRequesters[subtitle]!!),
focusRequester(focusRequester)
},
onClick = { onClick = {
serverSub?.index?.let { serverSub?.index?.let {
VideoPlayerObject.setSubtitleTrackIndex(it.toInt()) VideoPlayerObject.setSubtitleTrackIndex(it.toInt())

View file

@ -17,7 +17,6 @@ import androidx.compose.ui.unit.dp
import io.github.rabehx.iconsax.Iconsax import io.github.rabehx.iconsax.Iconsax
import io.github.rabehx.iconsax.filled.TickSquare import io.github.rabehx.iconsax.filled.TickSquare
import nl.jknaapen.fladder.composables.controls.CustomButton import nl.jknaapen.fladder.composables.controls.CustomButton
import nl.jknaapen.fladder.utility.defaultSelected
@Composable @Composable
internal fun TrackButton( internal fun TrackButton(
@ -33,8 +32,7 @@ internal fun TrackButton(
backgroundColor = Color.White.copy(alpha = 0.25f), backgroundColor = Color.White.copy(alpha = 0.25f),
modifier = modifier modifier = modifier
.padding(vertical = 6.dp, horizontal = 12.dp) .padding(vertical = 6.dp, horizontal = 12.dp)
.defaultMinSize(minHeight = 40.dp) .defaultMinSize(minHeight = 40.dp),
.defaultSelected(selected),
onClick = onClick, onClick = onClick,
) { ) {
Row( Row(

View file

@ -164,10 +164,16 @@ internal fun NextUpOverlay(
) )
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
Text( Text(
"Next-up in $timeUntilNextVideo seconds", "Next-up in $timeUntilNextVideo seconds",
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )
Box( Box(
@ -181,6 +187,7 @@ internal fun NextUpOverlay(
) )
) )
MediaInfo() MediaInfo()
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,

View file

@ -61,7 +61,6 @@ class VideoPlayerImplementation(
) )
.build() .build()
player?.stop() player?.stop()
player?.clearMediaItems() player?.clearMediaItems()
player?.setMediaItem(mediaItem) player?.setMediaItem(mediaItem)

View file

@ -58,8 +58,6 @@ internal fun ExoPlayer(
val videoHost = VideoPlayerObject val videoHost = VideoPlayerObject
val context = LocalContext.current val context = LocalContext.current
var initialized = false
val extractorsFactory = DefaultExtractorsFactory().apply { val extractorsFactory = DefaultExtractorsFactory().apply {
val isLowRamDevice = context.getSystemService<ActivityManager>()?.isLowRamDevice == true val isLowRamDevice = context.getSystemService<ActivityManager>()?.isLowRamDevice == true
setTsExtractorTimestampSearchBytes( setTsExtractorTimestampSearchBytes(
@ -167,13 +165,17 @@ internal fun ExoPlayer(
override fun onTracksChanged(tracks: Tracks) { override fun onTracksChanged(tracks: Tracks) {
super.onTracksChanged(tracks) super.onTracksChanged(tracks)
if (!initialized) { val subTracks = exoPlayer.getSubtitleTracks()
initialized = true val audioTracks = exoPlayer.getAudioTracks()
if (subTracks.isEmpty() && audioTracks.isEmpty()) return
if (subTracks != VideoPlayerObject.exoSubTracks.value || audioTracks != VideoPlayerObject.exoAudioTracks.value) {
VideoPlayerObject.implementation.playbackData.value?.let { VideoPlayerObject.implementation.playbackData.value?.let {
exoPlayer.properlySetSubAndAudioTracks(it) exoPlayer.properlySetSubAndAudioTracks(it)
} }
VideoPlayerObject.exoSubTracks.value = exoPlayer.getSubtitleTracks() VideoPlayerObject.exoSubTracks.value = subTracks
VideoPlayerObject.exoAudioTracks.value = exoPlayer.getAudioTracks() VideoPlayerObject.exoAudioTracks.value = audioTracks
} }
} }
} }

View file

@ -3,6 +3,7 @@ package nl.jknaapen.fladder.utility
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -13,13 +14,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -42,7 +43,7 @@ fun Modifier.highlightOnFocus(
) )
.border( .border(
width = width, width = width,
color = color.copy(alpha = 0.5f), color = color.copy(alpha = 0.8f),
) )
} else } else
if (width != 0.dp) { if (width != 0.dp) {
@ -54,7 +55,7 @@ fun Modifier.highlightOnFocus(
) )
.border( .border(
width = width, width = width,
color = color.copy(alpha = 0.5f), color = color.copy(alpha = 0.8f),
shape = shape shape = shape
) )
} else { } else {
@ -104,12 +105,23 @@ fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier):
fun Modifier.visible( fun Modifier.visible(
visible: Boolean, visible: Boolean,
): Modifier { ): Modifier {
val alphaAnimated by animateFloatAsState(if (visible) 1f else 0f) val alphaAnimated by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
label = "AlphaAnimation"
)
return this return this
.graphicsLayer { .graphicsLayer {
alpha = alphaAnimated alpha = alphaAnimated
} }
.then( .then(
if (!visible) Modifier.pointerInput(Unit) {} else Modifier if (!visible) {
//Collapse composable to disable input blocking
Modifier
.size(0.dp)
.clipToBounds()
} else {
Modifier
}
) )
} }

View file

@ -56,6 +56,7 @@ fun ExoPlayer.setInternalAudioTrack(audioTrack: InternalTrack) {
selector.setParameters( selector.setParameters(
selector.buildUponParameters() selector.buildUponParameters()
.setRendererDisabled(audioTrack.rendererIndex, false) .setRendererDisabled(audioTrack.rendererIndex, false)
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, false)
.build() .build()
) )
@ -74,8 +75,12 @@ fun ExoPlayer.clearAudioTrack(disable: Boolean = true) {
selector.setParameters( selector.setParameters(
selector.buildUponParameters() selector.buildUponParameters()
.setRendererDisabled(C.TRACK_TYPE_AUDIO, disable) .setRendererDisabled(C.TRACK_TYPE_AUDIO, disable)
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, disable)
.build() .build()
) )
this.trackSelectionParameters = selector.parameters.buildUpon()
.build()
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@ -110,21 +115,27 @@ fun ExoPlayer.getSubtitleTracks(): List<InternalTrack> {
fun ExoPlayer.clearSubtitleTrack() { fun ExoPlayer.clearSubtitleTrack() {
val selector = trackSelector as? DefaultTrackSelector ?: return val selector = trackSelector as? DefaultTrackSelector ?: return
val newParams = selector.buildUponParameters() val newParams = selector.buildUponParameters()
.setRendererDisabled(C.TRACK_TYPE_TEXT, false) // keep text renderer active .setRendererDisabled(C.TRACK_TYPE_TEXT, false)
.setPreferredTextLanguage(null) // don't auto-pick a language .setPreferredTextLanguage(null)
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true) // < disables selection of *any* text track .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
.build() .build()
selector.setParameters(newParams) selector.setParameters(newParams)
this.trackSelectionParameters = selector.parameters.buildUpon()
.build()
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun ExoPlayer.enableSubtitles(language: String? = null) { fun ExoPlayer.enableSubtitles(language: String? = null) {
val selector = trackSelector as? DefaultTrackSelector ?: return val selector = trackSelector as? DefaultTrackSelector ?: return
val newParams = selector.buildUponParameters() val newParams = selector.buildUponParameters()
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false) // allow text again .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
.setPreferredTextLanguage(language) // optional: auto-pick by language .setPreferredTextLanguage(language)
.build() .build()
selector.setParameters(newParams) selector.setParameters(newParams)
this.trackSelectionParameters = selector.parameters.buildUpon()
.build()
} }