mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feat: Added playbackinformation to native player ui
This commit is contained in:
parent
37496f87e3
commit
25304d0a5b
14 changed files with 266 additions and 50 deletions
|
|
@ -80,6 +80,18 @@ class FlutterError (
|
||||||
val details: Any? = null
|
val details: Any? = null
|
||||||
) : Throwable()
|
) : Throwable()
|
||||||
|
|
||||||
|
enum class PlaybackType(val raw: Int) {
|
||||||
|
DIRECT(0),
|
||||||
|
TRANSCODED(1),
|
||||||
|
OFFLINE(2);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ofRaw(raw: Int): PlaybackType? {
|
||||||
|
return values().firstOrNull { it.raw == raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class MediaSegmentType(val raw: Int) {
|
enum class MediaSegmentType(val raw: Int) {
|
||||||
COMMERCIAL(0),
|
COMMERCIAL(0),
|
||||||
PREVIEW(1),
|
PREVIEW(1),
|
||||||
|
|
@ -137,6 +149,37 @@ data class SimpleItemModel (
|
||||||
override fun hashCode(): Int = toList().hashCode()
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class MediaInfo (
|
||||||
|
val playbackType: PlaybackType,
|
||||||
|
val videoInformation: String
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): MediaInfo {
|
||||||
|
val playbackType = pigeonVar_list[0] as PlaybackType
|
||||||
|
val videoInformation = pigeonVar_list[1] as String
|
||||||
|
return MediaInfo(playbackType, videoInformation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
playbackType,
|
||||||
|
videoInformation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is MediaInfo) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return VideoPlayerHelperPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
/** Generated class from Pigeon that represents data sent in messages. */
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
data class PlayableData (
|
data class PlayableData (
|
||||||
val currentItem: SimpleItemModel,
|
val currentItem: SimpleItemModel,
|
||||||
|
|
@ -151,6 +194,7 @@ data class PlayableData (
|
||||||
val segments: List<MediaSegment>,
|
val segments: List<MediaSegment>,
|
||||||
val previousVideo: SimpleItemModel? = null,
|
val previousVideo: SimpleItemModel? = null,
|
||||||
val nextVideo: SimpleItemModel? = null,
|
val nextVideo: SimpleItemModel? = null,
|
||||||
|
val mediaInfo: MediaInfo,
|
||||||
val url: String
|
val url: String
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
@ -168,8 +212,9 @@ data class PlayableData (
|
||||||
val segments = pigeonVar_list[9] as List<MediaSegment>
|
val segments = pigeonVar_list[9] as List<MediaSegment>
|
||||||
val previousVideo = pigeonVar_list[10] as SimpleItemModel?
|
val previousVideo = pigeonVar_list[10] as SimpleItemModel?
|
||||||
val nextVideo = pigeonVar_list[11] as SimpleItemModel?
|
val nextVideo = pigeonVar_list[11] as SimpleItemModel?
|
||||||
val url = pigeonVar_list[12] as String
|
val mediaInfo = pigeonVar_list[12] as MediaInfo
|
||||||
return PlayableData(currentItem, description, startPosition, defaultAudioTrack, audioTracks, defaultSubtrack, subtitleTracks, trickPlayModel, chapters, segments, previousVideo, nextVideo, url)
|
val url = pigeonVar_list[13] as String
|
||||||
|
return PlayableData(currentItem, description, startPosition, defaultAudioTrack, audioTracks, defaultSubtrack, subtitleTracks, trickPlayModel, chapters, segments, previousVideo, nextVideo, mediaInfo, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
|
|
@ -186,6 +231,7 @@ data class PlayableData (
|
||||||
segments,
|
segments,
|
||||||
previousVideo,
|
previousVideo,
|
||||||
nextVideo,
|
nextVideo,
|
||||||
|
mediaInfo,
|
||||||
url,
|
url,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -482,50 +528,60 @@ private open class VideoPlayerHelperPigeonCodec : StandardMessageCodec() {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
129.toByte() -> {
|
129.toByte() -> {
|
||||||
return (readValue(buffer) as Long?)?.let {
|
return (readValue(buffer) as Long?)?.let {
|
||||||
MediaSegmentType.ofRaw(it.toInt())
|
PlaybackType.ofRaw(it.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
130.toByte() -> {
|
130.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as Long?)?.let {
|
||||||
SimpleItemModel.fromList(it)
|
MediaSegmentType.ofRaw(it.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
131.toByte() -> {
|
131.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PlayableData.fromList(it)
|
SimpleItemModel.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
132.toByte() -> {
|
132.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
MediaSegment.fromList(it)
|
MediaInfo.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
133.toByte() -> {
|
133.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
AudioTrack.fromList(it)
|
PlayableData.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
134.toByte() -> {
|
134.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SubtitleTrack.fromList(it)
|
MediaSegment.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
135.toByte() -> {
|
135.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
Chapter.fromList(it)
|
AudioTrack.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
136.toByte() -> {
|
136.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
TrickPlayModel.fromList(it)
|
SubtitleTrack.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
137.toByte() -> {
|
137.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
StartResult.fromList(it)
|
Chapter.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
138.toByte() -> {
|
138.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
TrickPlayModel.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
139.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
StartResult.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
140.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PlaybackState.fromList(it)
|
PlaybackState.fromList(it)
|
||||||
}
|
}
|
||||||
|
|
@ -535,46 +591,54 @@ private open class VideoPlayerHelperPigeonCodec : StandardMessageCodec() {
|
||||||
}
|
}
|
||||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
when (value) {
|
when (value) {
|
||||||
is MediaSegmentType -> {
|
is PlaybackType -> {
|
||||||
stream.write(129)
|
stream.write(129)
|
||||||
writeValue(stream, value.raw)
|
writeValue(stream, value.raw)
|
||||||
}
|
}
|
||||||
is SimpleItemModel -> {
|
is MediaSegmentType -> {
|
||||||
stream.write(130)
|
stream.write(130)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.raw)
|
||||||
}
|
}
|
||||||
is PlayableData -> {
|
is SimpleItemModel -> {
|
||||||
stream.write(131)
|
stream.write(131)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is MediaSegment -> {
|
is MediaInfo -> {
|
||||||
stream.write(132)
|
stream.write(132)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is AudioTrack -> {
|
is PlayableData -> {
|
||||||
stream.write(133)
|
stream.write(133)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SubtitleTrack -> {
|
is MediaSegment -> {
|
||||||
stream.write(134)
|
stream.write(134)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is Chapter -> {
|
is AudioTrack -> {
|
||||||
stream.write(135)
|
stream.write(135)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is TrickPlayModel -> {
|
is SubtitleTrack -> {
|
||||||
stream.write(136)
|
stream.write(136)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is StartResult -> {
|
is Chapter -> {
|
||||||
stream.write(137)
|
stream.write(137)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is PlaybackState -> {
|
is TrickPlayModel -> {
|
||||||
stream.write(138)
|
stream.write(138)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
|
is StartResult -> {
|
||||||
|
stream.write(139)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is PlaybackState -> {
|
||||||
|
stream.write(140)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
else -> super.writeValue(stream, value)
|
else -> super.writeValue(stream, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ 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.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
|
@ -22,6 +23,7 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -65,6 +67,7 @@ import androidx.compose.ui.util.fastCoerceIn
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import nl.jknaapen.fladder.objects.VideoPlayerObject
|
import nl.jknaapen.fladder.objects.VideoPlayerObject
|
||||||
|
import nl.jknaapen.fladder.utility.capitalize
|
||||||
import nl.jknaapen.fladder.utility.formatTime
|
import nl.jknaapen.fladder.utility.formatTime
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
@ -117,7 +120,7 @@ internal fun ProgressBar(
|
||||||
trickPlayModel = playbackData?.trickPlayModel
|
trickPlayModel = playbackData?.trickPlayModel
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
val progressBarTopLabel = listOf(
|
val progressBarTopLabel = listOf(
|
||||||
playableData?.currentItem?.subTitle,
|
playableData?.currentItem?.subTitle,
|
||||||
|
|
@ -134,6 +137,11 @@ internal fun ProgressBar(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Videolabel(playableData?.mediaInfo?.playbackType?.name?.capitalize)
|
||||||
|
Videolabel(playableData?.mediaInfo?.videoInformation)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
|
@ -177,7 +185,30 @@ internal fun ProgressBar(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Videolabel(value: String?) {
|
||||||
|
if (value.isNullOrBlank()) return
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.wrapContentSize()
|
||||||
|
.padding(horizontal = 6.dp, vertical = 4.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -476,11 +476,15 @@ internal fun RowScope.RightButtons(
|
||||||
showAudioDialog: MutableState<Boolean>,
|
showAudioDialog: MutableState<Boolean>,
|
||||||
showSubDialog: MutableState<Boolean>
|
showSubDialog: MutableState<Boolean>
|
||||||
) {
|
) {
|
||||||
|
val hasSubtitles by VideoPlayerObject.hasSubtracks.collectAsState(false)
|
||||||
|
val hasAudioTracks by VideoPlayerObject.hasAudioTracks.collectAsState(false)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp, alignment = Alignment.End)
|
horizontalArrangement = Arrangement.spacedBy(12.dp, alignment = Alignment.End)
|
||||||
) {
|
) {
|
||||||
CustomButton(
|
CustomButton(
|
||||||
|
enabled = hasAudioTracks,
|
||||||
onClick = {
|
onClick = {
|
||||||
showAudioDialog.value = true
|
showAudioDialog.value = true
|
||||||
},
|
},
|
||||||
|
|
@ -491,6 +495,7 @@ internal fun RowScope.RightButtons(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CustomButton(
|
CustomButton(
|
||||||
|
enabled = hasSubtitles,
|
||||||
onClick = {
|
onClick = {
|
||||||
showSubDialog.value = true
|
showSubDialog.value = true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ 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
|
||||||
|
|
||||||
|
if (internalAudioTracks.isEmpty()) return
|
||||||
|
|
||||||
val focusOffTrack = remember { FocusRequester() }
|
val focusOffTrack = remember { FocusRequester() }
|
||||||
val focusRequesters = remember(internalAudioTracks) {
|
val focusRequesters = remember(internalAudioTracks) {
|
||||||
internalAudioTracks.associateWith { FocusRequester() }
|
internalAudioTracks.associateWith { FocusRequester() }
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ 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
|
||||||
|
|
||||||
|
if (internalSubTracks.isEmpty()) return
|
||||||
|
|
||||||
val focusOffTrack = remember { FocusRequester() }
|
val focusOffTrack = remember { FocusRequester() }
|
||||||
|
|
||||||
val focusRequesters = remember(internalSubTracks) {
|
val focusRequesters = remember(internalSubTracks) {
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,10 @@ object VideoPlayerObject {
|
||||||
val subtitleTracks = implementation.playbackData.map { it?.subtitleTracks ?: listOf() }
|
val subtitleTracks = implementation.playbackData.map { it?.subtitleTracks ?: listOf() }
|
||||||
val audioTracks = implementation.playbackData.map { it?.audioTracks ?: listOf() }
|
val audioTracks = implementation.playbackData.map { it?.audioTracks ?: listOf() }
|
||||||
|
|
||||||
|
val hasSubtracks = subtitleTracks.map { it.isNotEmpty() && exoSubTracks.value.isNotEmpty() }
|
||||||
|
val hasAudioTracks = audioTracks.map { it.isNotEmpty() && exoAudioTracks.value.isNotEmpty() }
|
||||||
|
|
||||||
|
|
||||||
fun setPlaybackState(state: PlaybackState) {
|
fun setPlaybackState(state: PlaybackState) {
|
||||||
_currentState.value = state
|
_currentState.value = state
|
||||||
videoPlayerListener?.onPlaybackStateChanged(
|
videoPlayerListener?.onPlaybackStateChanged(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ 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
|
||||||
|
|
@ -115,11 +114,9 @@ fun Modifier.visible(
|
||||||
alpha = alphaAnimated
|
alpha = alphaAnimated
|
||||||
}
|
}
|
||||||
.then(
|
.then(
|
||||||
if (!visible) {
|
if (alphaAnimated == 0f) {
|
||||||
//Collapse composable to disable input blocking
|
|
||||||
Modifier
|
Modifier
|
||||||
.size(0.dp)
|
.size(0.dp)
|
||||||
.clipToBounds()
|
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package nl.jknaapen.fladder.utility
|
||||||
|
|
||||||
import AudioTrack
|
import AudioTrack
|
||||||
import Chapter
|
import Chapter
|
||||||
|
import MediaInfo
|
||||||
import PlayableData
|
import PlayableData
|
||||||
|
import PlaybackType
|
||||||
import SimpleItemModel
|
import SimpleItemModel
|
||||||
import SubtitleTrack
|
import SubtitleTrack
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
@ -99,5 +101,9 @@ val testPlaybackData = PlayableData(
|
||||||
primaryPoster = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.ytimg.com%2Fvi%2Faqz-KE-bpKQ%2Fmaxresdefault.jpg&f=1&nofb=1&ipt=4e375598bf8cc78e681ee62de9111dea32b85972ae756e40a1eddac01aa79f80"
|
primaryPoster = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.ytimg.com%2Fvi%2Faqz-KE-bpKQ%2Fmaxresdefault.jpg&f=1&nofb=1&ipt=4e375598bf8cc78e681ee62de9111dea32b85972ae756e40a1eddac01aa79f80"
|
||||||
),
|
),
|
||||||
segments = listOf(),
|
segments = listOf(),
|
||||||
|
mediaInfo = MediaInfo(
|
||||||
|
videoInformation = "SDR HD",
|
||||||
|
playbackType = PlaybackType.DIRECT,
|
||||||
|
),
|
||||||
url = "https://github.com/ietf-wg-cellar/matroska-test-files/raw/refs/heads/master/test_files/test5.mkv",
|
url = "https://github.com/ietf-wg-cellar/matroska-test-files/raw/refs/heads/master/test_files/test5.mkv",
|
||||||
)
|
)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package nl.jknaapen.fladder.utility
|
||||||
|
|
||||||
|
val String.capitalize: String
|
||||||
|
get() = this.mapIndexed { index, char -> if (index == 0) char.uppercase() else char.lowercase() }
|
||||||
|
.joinToString(separator = "")
|
||||||
|
|
@ -69,6 +69,8 @@ class MediaStreamsModel {
|
||||||
return "${stream.width}x${stream.height}";
|
return "${stream.width}x${stream.height}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get mediaInfoTag => '${displayProfile?.value} ${resolution?.value}';
|
||||||
|
|
||||||
Widget? audioIcon(
|
Widget? audioIcon(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Function()? onTap,
|
Function()? onTap,
|
||||||
|
|
|
||||||
|
|
@ -443,7 +443,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.streamModel?.displayProfile?.value} ${item.streamModel?.resolution?.value}',
|
item.streamModel?.mediaInfoTag ?? "",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,12 @@ bool _deepEquals(Object? a, Object? b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum PlaybackType {
|
||||||
|
direct,
|
||||||
|
transcoded,
|
||||||
|
offline,
|
||||||
|
}
|
||||||
|
|
||||||
enum MediaSegmentType {
|
enum MediaSegmentType {
|
||||||
commercial,
|
commercial,
|
||||||
preview,
|
preview,
|
||||||
|
|
@ -113,6 +119,52 @@ class SimpleItemModel {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MediaInfo {
|
||||||
|
MediaInfo({
|
||||||
|
required this.playbackType,
|
||||||
|
required this.videoInformation,
|
||||||
|
});
|
||||||
|
|
||||||
|
PlaybackType playbackType;
|
||||||
|
|
||||||
|
String videoInformation;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[
|
||||||
|
playbackType,
|
||||||
|
videoInformation,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList(); }
|
||||||
|
|
||||||
|
static MediaInfo decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return MediaInfo(
|
||||||
|
playbackType: result[0]! as PlaybackType,
|
||||||
|
videoInformation: result[1]! as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! MediaInfo || other.runtimeType != runtimeType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (identical(this, other)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _deepEquals(encode(), other.encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
int get hashCode => Object.hashAll(_toList())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
class PlayableData {
|
class PlayableData {
|
||||||
PlayableData({
|
PlayableData({
|
||||||
required this.currentItem,
|
required this.currentItem,
|
||||||
|
|
@ -127,6 +179,7 @@ class PlayableData {
|
||||||
required this.segments,
|
required this.segments,
|
||||||
this.previousVideo,
|
this.previousVideo,
|
||||||
this.nextVideo,
|
this.nextVideo,
|
||||||
|
required this.mediaInfo,
|
||||||
required this.url,
|
required this.url,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -154,6 +207,8 @@ class PlayableData {
|
||||||
|
|
||||||
SimpleItemModel? nextVideo;
|
SimpleItemModel? nextVideo;
|
||||||
|
|
||||||
|
MediaInfo mediaInfo;
|
||||||
|
|
||||||
String url;
|
String url;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
|
|
@ -170,6 +225,7 @@ class PlayableData {
|
||||||
segments,
|
segments,
|
||||||
previousVideo,
|
previousVideo,
|
||||||
nextVideo,
|
nextVideo,
|
||||||
|
mediaInfo,
|
||||||
url,
|
url,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +248,8 @@ class PlayableData {
|
||||||
segments: (result[9] as List<Object?>?)!.cast<MediaSegment>(),
|
segments: (result[9] as List<Object?>?)!.cast<MediaSegment>(),
|
||||||
previousVideo: result[10] as SimpleItemModel?,
|
previousVideo: result[10] as SimpleItemModel?,
|
||||||
nextVideo: result[11] as SimpleItemModel?,
|
nextVideo: result[11] as SimpleItemModel?,
|
||||||
url: result[12]! as String,
|
mediaInfo: result[12]! as MediaInfo,
|
||||||
|
url: result[13]! as String,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,36 +701,42 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is MediaSegmentType) {
|
} else if (value is PlaybackType) {
|
||||||
buffer.putUint8(129);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.index);
|
writeValue(buffer, value.index);
|
||||||
} else if (value is SimpleItemModel) {
|
} else if (value is MediaSegmentType) {
|
||||||
buffer.putUint8(130);
|
buffer.putUint8(130);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.index);
|
||||||
} else if (value is PlayableData) {
|
} else if (value is SimpleItemModel) {
|
||||||
buffer.putUint8(131);
|
buffer.putUint8(131);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is MediaSegment) {
|
} else if (value is MediaInfo) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is AudioTrack) {
|
} else if (value is PlayableData) {
|
||||||
buffer.putUint8(133);
|
buffer.putUint8(133);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SubtitleTrack) {
|
} else if (value is MediaSegment) {
|
||||||
buffer.putUint8(134);
|
buffer.putUint8(134);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is Chapter) {
|
} else if (value is AudioTrack) {
|
||||||
buffer.putUint8(135);
|
buffer.putUint8(135);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is TrickPlayModel) {
|
} else if (value is SubtitleTrack) {
|
||||||
buffer.putUint8(136);
|
buffer.putUint8(136);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is StartResult) {
|
} else if (value is Chapter) {
|
||||||
buffer.putUint8(137);
|
buffer.putUint8(137);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is PlaybackState) {
|
} else if (value is TrickPlayModel) {
|
||||||
buffer.putUint8(138);
|
buffer.putUint8(138);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is StartResult) {
|
||||||
|
buffer.putUint8(139);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is PlaybackState) {
|
||||||
|
buffer.putUint8(140);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(buffer, value);
|
super.writeValue(buffer, value);
|
||||||
}
|
}
|
||||||
|
|
@ -684,24 +747,29 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 129:
|
case 129:
|
||||||
final int? value = readValue(buffer) as int?;
|
final int? value = readValue(buffer) as int?;
|
||||||
return value == null ? null : MediaSegmentType.values[value];
|
return value == null ? null : PlaybackType.values[value];
|
||||||
case 130:
|
case 130:
|
||||||
return SimpleItemModel.decode(readValue(buffer)!);
|
final int? value = readValue(buffer) as int?;
|
||||||
|
return value == null ? null : MediaSegmentType.values[value];
|
||||||
case 131:
|
case 131:
|
||||||
return PlayableData.decode(readValue(buffer)!);
|
return SimpleItemModel.decode(readValue(buffer)!);
|
||||||
case 132:
|
case 132:
|
||||||
return MediaSegment.decode(readValue(buffer)!);
|
return MediaInfo.decode(readValue(buffer)!);
|
||||||
case 133:
|
case 133:
|
||||||
return AudioTrack.decode(readValue(buffer)!);
|
return PlayableData.decode(readValue(buffer)!);
|
||||||
case 134:
|
case 134:
|
||||||
return SubtitleTrack.decode(readValue(buffer)!);
|
return MediaSegment.decode(readValue(buffer)!);
|
||||||
case 135:
|
case 135:
|
||||||
return Chapter.decode(readValue(buffer)!);
|
return AudioTrack.decode(readValue(buffer)!);
|
||||||
case 136:
|
case 136:
|
||||||
return TrickPlayModel.decode(readValue(buffer)!);
|
return SubtitleTrack.decode(readValue(buffer)!);
|
||||||
case 137:
|
case 137:
|
||||||
return StartResult.decode(readValue(buffer)!);
|
return Chapter.decode(readValue(buffer)!);
|
||||||
case 138:
|
case 138:
|
||||||
|
return TrickPlayModel.decode(readValue(buffer)!);
|
||||||
|
case 139:
|
||||||
|
return StartResult.decode(readValue(buffer)!);
|
||||||
|
case 140:
|
||||||
return PlaybackState.decode(readValue(buffer)!);
|
return PlaybackState.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/media_streams_model.dart';
|
import 'package:fladder/models/items/media_streams_model.dart';
|
||||||
|
import 'package:fladder/models/playback/direct_playback_model.dart';
|
||||||
|
import 'package:fladder/models/playback/offline_playback_model.dart';
|
||||||
import 'package:fladder/models/playback/playback_model.dart';
|
import 'package:fladder/models/playback/playback_model.dart';
|
||||||
|
import 'package:fladder/models/playback/transcode_playback_model.dart';
|
||||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||||
import 'package:fladder/src/video_player_helper.g.dart';
|
import 'package:fladder/src/video_player_helper.g.dart';
|
||||||
import 'package:fladder/wrappers/players/base_player.dart';
|
import 'package:fladder/wrappers/players/base_player.dart';
|
||||||
|
|
@ -157,6 +160,15 @@ class NativePlayer extends BasePlayer implements VideoPlayerListenerCallback {
|
||||||
?.map((e) => Chapter(name: e.name, url: e.imageUrl, time: e.startPosition.inMilliseconds))
|
?.map((e) => Chapter(name: e.name, url: e.imageUrl, time: e.startPosition.inMilliseconds))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
|
mediaInfo: MediaInfo(
|
||||||
|
playbackType: switch (model) {
|
||||||
|
DirectPlaybackModel() => PlaybackType.direct,
|
||||||
|
OfflinePlaybackModel() => PlaybackType.offline,
|
||||||
|
TranscodePlaybackModel() => PlaybackType.transcoded,
|
||||||
|
_ => PlaybackType.direct,
|
||||||
|
},
|
||||||
|
videoInformation: model.item.streamModel?.mediaInfoTag ?? " ",
|
||||||
|
),
|
||||||
url: model.media?.url ?? "",
|
url: model.media?.url ?? "",
|
||||||
);
|
);
|
||||||
player.sendPlayableModel(playableData);
|
player.sendPlayableModel(playableData);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,22 @@ class SimpleItemModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PlaybackType {
|
||||||
|
direct,
|
||||||
|
transcoded,
|
||||||
|
offline,
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaInfo {
|
||||||
|
final PlaybackType playbackType;
|
||||||
|
final String videoInformation;
|
||||||
|
|
||||||
|
const MediaInfo({
|
||||||
|
required this.playbackType,
|
||||||
|
required this.videoInformation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class PlayableData {
|
class PlayableData {
|
||||||
final SimpleItemModel currentItem;
|
final SimpleItemModel currentItem;
|
||||||
final String description;
|
final String description;
|
||||||
|
|
@ -40,6 +56,7 @@ class PlayableData {
|
||||||
final List<MediaSegment> segments;
|
final List<MediaSegment> segments;
|
||||||
final SimpleItemModel? previousVideo;
|
final SimpleItemModel? previousVideo;
|
||||||
final SimpleItemModel? nextVideo;
|
final SimpleItemModel? nextVideo;
|
||||||
|
final MediaInfo mediaInfo;
|
||||||
final String url;
|
final String url;
|
||||||
|
|
||||||
PlayableData({
|
PlayableData({
|
||||||
|
|
@ -55,6 +72,7 @@ class PlayableData {
|
||||||
this.segments = const [],
|
this.segments = const [],
|
||||||
this.previousVideo,
|
this.previousVideo,
|
||||||
this.nextVideo,
|
this.nextVideo,
|
||||||
|
required this.mediaInfo,
|
||||||
required this.url,
|
required this.url,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue