feat: Add fillScreen and videoFit to native player options

This commit is contained in:
PartyDonut 2025-10-17 14:07:38 +02:00
parent 63203f39df
commit 318f32c7e6
8 changed files with 148 additions and 23 deletions

View file

@ -55,6 +55,8 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
}
callback?.invoke(Result.success(startResult))
VideoPlayerObject.implementation.player?.stop()
VideoPlayerObject.implementation.player?.release()
}
}
@ -72,6 +74,8 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
}
override fun disposeActivity() {
VideoPlayerObject.implementation.player?.stop()
VideoPlayerObject.implementation.player?.release()
VideoPlayerObject.currentActivity?.finish()
}

View file

@ -65,6 +65,22 @@ private object PlayerSettingsHelperPigeonUtils {
}
enum class VideoPlayerFit(val raw: Int) {
FILL(0),
CONTAIN(1),
COVER(2),
FIT_WIDTH(3),
FIT_HEIGHT(4),
NONE(5),
SCALE_DOWN(6);
companion object {
fun ofRaw(raw: Int): VideoPlayerFit? {
return values().firstOrNull { it.raw == raw }
}
}
}
enum class PlayerOrientations(val raw: Int) {
PORTRAIT_UP(0),
PORTRAIT_DOWN(1),
@ -124,7 +140,9 @@ data class PlayerSettings (
val skipForward: Long,
val skipBackward: Long,
val autoNextType: AutoNextType,
val acceptedOrientations: List<PlayerOrientations>
val acceptedOrientations: List<PlayerOrientations>,
val fillScreen: Boolean,
val videoFit: VideoPlayerFit
)
{
companion object {
@ -136,7 +154,9 @@ data class PlayerSettings (
val skipBackward = pigeonVar_list[4] as Long
val autoNextType = pigeonVar_list[5] as AutoNextType
val acceptedOrientations = pigeonVar_list[6] as List<PlayerOrientations>
return PlayerSettings(enableTunneling, skipTypes, themeColor, skipForward, skipBackward, autoNextType, acceptedOrientations)
val fillScreen = pigeonVar_list[7] as Boolean
val videoFit = pigeonVar_list[8] as VideoPlayerFit
return PlayerSettings(enableTunneling, skipTypes, themeColor, skipForward, skipBackward, autoNextType, acceptedOrientations, fillScreen, videoFit)
}
}
fun toList(): List<Any?> {
@ -148,6 +168,8 @@ data class PlayerSettings (
skipBackward,
autoNextType,
acceptedOrientations,
fillScreen,
videoFit,
)
}
override fun equals(other: Any?): Boolean {
@ -166,25 +188,30 @@ private open class PlayerSettingsHelperPigeonCodec : StandardMessageCodec() {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
PlayerOrientations.ofRaw(it.toInt())
VideoPlayerFit.ofRaw(it.toInt())
}
}
130.toByte() -> {
return (readValue(buffer) as Long?)?.let {
AutoNextType.ofRaw(it.toInt())
PlayerOrientations.ofRaw(it.toInt())
}
}
131.toByte() -> {
return (readValue(buffer) as Long?)?.let {
SegmentType.ofRaw(it.toInt())
AutoNextType.ofRaw(it.toInt())
}
}
132.toByte() -> {
return (readValue(buffer) as Long?)?.let {
SegmentSkip.ofRaw(it.toInt())
SegmentType.ofRaw(it.toInt())
}
}
133.toByte() -> {
return (readValue(buffer) as Long?)?.let {
SegmentSkip.ofRaw(it.toInt())
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PlayerSettings.fromList(it)
}
@ -194,24 +221,28 @@ private open class PlayerSettingsHelperPigeonCodec : StandardMessageCodec() {
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is PlayerOrientations -> {
is VideoPlayerFit -> {
stream.write(129)
writeValue(stream, value.raw)
}
is AutoNextType -> {
is PlayerOrientations -> {
stream.write(130)
writeValue(stream, value.raw)
}
is SegmentType -> {
is AutoNextType -> {
stream.write(131)
writeValue(stream, value.raw)
}
is SegmentSkip -> {
is SegmentType -> {
stream.write(132)
writeValue(stream, value.raw)
}
is PlayerSettings -> {
is SegmentSkip -> {
stream.write(133)
writeValue(stream, value.raw)
}
is PlayerSettings -> {
stream.write(134)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)

View file

@ -6,6 +6,7 @@ import PlayerSettingsPigeon
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import nl.jknaapen.fladder.utility.toExoPlayerFit
import kotlin.time.DurationUnit
import kotlin.time.toDuration
@ -33,6 +34,14 @@ object PlayerSettingsObject : PlayerSettingsPigeon {
settings?.autoNextType ?: AutoNextType.OFF
}
val acceptedOrientations = settings.map { settings ->
settings?.acceptedOrientations ?: emptyList()
}
val fillScreen = settings.map { settings -> settings?.fillScreen ?: false }
val videoFit = settings.map { settings -> settings?.videoFit.toExoPlayerFit }
override fun sendPlayerSettings(playerSettings: PlayerSettings) {
settings.value = playerSettings
}

View file

@ -7,12 +7,15 @@ import android.view.WindowManager
import androidx.activity.compose.LocalActivity
import androidx.annotation.OptIn
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -33,6 +36,7 @@ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.extractor.DefaultExtractorsFactory
import androidx.media3.extractor.ts.TsExtractor
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.CaptionStyleCompat
import androidx.media3.ui.PlayerView
import io.github.peerless2012.ass.media.kt.buildWithAssSupport
@ -43,6 +47,7 @@ import nl.jknaapen.fladder.messengers.properlySetSubAndAudioTracks
import nl.jknaapen.fladder.objects.PlayerSettingsObject
import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.AllowedOrientations
import nl.jknaapen.fladder.utility.conditional
import nl.jknaapen.fladder.utility.getAudioTracks
import nl.jknaapen.fladder.utility.getSubtitleTracks
import kotlin.time.Duration.Companion.seconds
@ -139,7 +144,6 @@ internal fun ExoPlayer(
val listener = object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
activity?.window?.let {
println("Changing playback state")
if (isPlaying) {
it.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
@ -200,8 +204,12 @@ internal fun ExoPlayer(
}
}
val acceptedOrientations by PlayerSettingsObject.acceptedOrientations.collectAsState(emptyList())
val fillScreen by PlayerSettingsObject.fillScreen.collectAsState(false)
val videoFit by PlayerSettingsObject.videoFit.collectAsState(AspectRatioFrameLayout.RESIZE_MODE_FIT)
AllowedOrientations(
PlayerSettingsObject.settings.value?.acceptedOrientations ?: emptyList()
acceptedOrientations
) {
NextUpOverlay(
modifier = Modifier
@ -210,11 +218,15 @@ internal fun ExoPlayer(
AndroidView(
modifier = Modifier
.fillMaxSize()
.background(color = Color.Black),
.background(color = Color.Black)
.conditional(!fillScreen) {
displayCutoutPadding()
},
factory = {
PlayerView(it).apply {
player = exoPlayer
useController = false
resizeMode = videoFit
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,

View file

@ -0,0 +1,18 @@
package nl.jknaapen.fladder.utility
import VideoPlayerFit
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.AspectRatioFrameLayout
val VideoPlayerFit?.toExoPlayerFit: Int
@UnstableApi
get() = when (this) {
VideoPlayerFit.FILL -> AspectRatioFrameLayout.RESIZE_MODE_FILL
VideoPlayerFit.CONTAIN -> AspectRatioFrameLayout.RESIZE_MODE_FIT
VideoPlayerFit.COVER -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
VideoPlayerFit.FIT_WIDTH -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
VideoPlayerFit.FIT_HEIGHT -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT
VideoPlayerFit.NONE -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
VideoPlayerFit.SCALE_DOWN -> AspectRatioFrameLayout.RESIZE_MODE_FIT
null -> AspectRatioFrameLayout.RESIZE_MODE_FIT
}

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -51,6 +52,16 @@ final pigeonPlayerSettingsSyncProvider = Provider<void>((ref) {
},
skipBackward: (userData?.userSettings?.skipBackDuration ?? const Duration(seconds: 15)).inMilliseconds,
skipForward: (userData?.userSettings?.skipForwardDuration ?? const Duration(seconds: 30)).inMilliseconds,
fillScreen: value.fillScreen,
videoFit: switch (value.videoFit) {
BoxFit.fill => pigeon.VideoPlayerFit.fill,
BoxFit.contain => pigeon.VideoPlayerFit.contain,
BoxFit.cover => pigeon.VideoPlayerFit.cover,
BoxFit.fitWidth => pigeon.VideoPlayerFit.fitWidth,
BoxFit.fitHeight => pigeon.VideoPlayerFit.fitHeight,
BoxFit.none => pigeon.VideoPlayerFit.none,
BoxFit.scaleDown => pigeon.VideoPlayerFit.scaleDown,
},
acceptedOrientations: (value.allowedOrientations?.toList() ?? DeviceOrientation.values)
.map(
(e) => switch (e) {

View file

@ -29,6 +29,16 @@ bool _deepEquals(Object? a, Object? b) {
}
enum VideoPlayerFit {
fill,
contain,
cover,
fitWidth,
fitHeight,
none,
scaleDown,
}
enum PlayerOrientations {
portraitUp,
portraitDown,
@ -65,6 +75,8 @@ class PlayerSettings {
required this.skipBackward,
required this.autoNextType,
required this.acceptedOrientations,
required this.fillScreen,
required this.videoFit,
});
bool enableTunneling;
@ -81,6 +93,10 @@ class PlayerSettings {
List<PlayerOrientations> acceptedOrientations;
bool fillScreen;
VideoPlayerFit videoFit;
List<Object?> _toList() {
return <Object?>[
enableTunneling,
@ -90,6 +106,8 @@ class PlayerSettings {
skipBackward,
autoNextType,
acceptedOrientations,
fillScreen,
videoFit,
];
}
@ -106,6 +124,8 @@ class PlayerSettings {
skipBackward: result[4]! as int,
autoNextType: result[5]! as AutoNextType,
acceptedOrientations: (result[6] as List<Object?>?)!.cast<PlayerOrientations>(),
fillScreen: result[7]! as bool,
videoFit: result[8]! as VideoPlayerFit,
);
}
@ -135,20 +155,23 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is PlayerOrientations) {
} else if (value is VideoPlayerFit) {
buffer.putUint8(129);
writeValue(buffer, value.index);
} else if (value is AutoNextType) {
} else if (value is PlayerOrientations) {
buffer.putUint8(130);
writeValue(buffer, value.index);
} else if (value is SegmentType) {
} else if (value is AutoNextType) {
buffer.putUint8(131);
writeValue(buffer, value.index);
} else if (value is SegmentSkip) {
} else if (value is SegmentType) {
buffer.putUint8(132);
writeValue(buffer, value.index);
} else if (value is PlayerSettings) {
} else if (value is SegmentSkip) {
buffer.putUint8(133);
writeValue(buffer, value.index);
} else if (value is PlayerSettings) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
@ -160,17 +183,20 @@ class _PigeonCodec extends StandardMessageCodec {
switch (type) {
case 129:
final int? value = readValue(buffer) as int?;
return value == null ? null : PlayerOrientations.values[value];
return value == null ? null : VideoPlayerFit.values[value];
case 130:
final int? value = readValue(buffer) as int?;
return value == null ? null : AutoNextType.values[value];
return value == null ? null : PlayerOrientations.values[value];
case 131:
final int? value = readValue(buffer) as int?;
return value == null ? null : SegmentType.values[value];
return value == null ? null : AutoNextType.values[value];
case 132:
final int? value = readValue(buffer) as int?;
return value == null ? null : SegmentSkip.values[value];
return value == null ? null : SegmentType.values[value];
case 133:
final int? value = readValue(buffer) as int?;
return value == null ? null : SegmentSkip.values[value];
case 134:
return PlayerSettings.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);

View file

@ -20,6 +20,8 @@ class PlayerSettings {
final int skipBackward;
final AutoNextType autoNextType;
final List<PlayerOrientations> acceptedOrientations;
final bool fillScreen;
final VideoPlayerFit videoFit;
const PlayerSettings({
required this.enableTunneling,
@ -29,9 +31,21 @@ class PlayerSettings {
required this.skipBackward,
required this.autoNextType,
required this.acceptedOrientations,
required this.fillScreen,
required this.videoFit,
});
}
enum VideoPlayerFit {
fill,
contain,
cover,
fitWidth,
fitHeight,
none,
scaleDown,
}
enum PlayerOrientations {
portraitUp,
portraitDown,