Merge branch 'simplify-quality-language-options' into 'master'

Simplify Quality Selector

See merge request videostreaming/grayjay!127
This commit is contained in:
Kai DeLorenzo 2025-09-09 04:16:34 +00:00
commit 34329035b2
17 changed files with 131 additions and 67 deletions

View file

@ -412,6 +412,8 @@ class VideoUrlSource {
this.bitrate = obj.bitrate ?? 0; this.bitrate = obj.bitrate ?? 0;
this.duration = obj.duration ?? 0; this.duration = obj.duration ?? 0;
this.url = obj.url; this.url = obj.url;
if(obj.frameRate)
this.frameRate = obj.frameRate;
if(obj.requestModifier) if(obj.requestModifier)
this.requestModifier = obj.requestModifier; this.requestModifier = obj.requestModifier;
} }

View file

@ -446,14 +446,18 @@ class Settings : FragmentedStorageFileJson() {
fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality); fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality);
@AdvancedField @AdvancedField
@FormField(R.string.simplify_sources, FieldForm.TOGGLE, R.string.simplify_sources_description, 4) @FormField(R.string.show_advanced_media_source_metadata, FieldForm.TOGGLE, R.string.show_advanced_media_source_metadata_desc, 4)
var showAdvancedMediaSourceMetadata: Boolean = false;
@AdvancedField
@FormField(R.string.simplify_sources, FieldForm.TOGGLE, R.string.simplify_sources_description, 5)
var simplifySources: Boolean = true; var simplifySources: Boolean = true;
@AdvancedField @AdvancedField
@FormField(R.string.always_allow_reverse_landscape_auto_rotate, FieldForm.TOGGLE, R.string.always_allow_reverse_landscape_auto_rotate_description, 5) @FormField(R.string.always_allow_reverse_landscape_auto_rotate, FieldForm.TOGGLE, R.string.always_allow_reverse_landscape_auto_rotate_description, 6)
var alwaysAllowReverseLandscapeAutoRotate: Boolean = true var alwaysAllowReverseLandscapeAutoRotate: Boolean = true
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 6) @FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 7)
@DropdownFieldOptionsId(R.array.player_background_behavior) @DropdownFieldOptionsId(R.array.player_background_behavior)
var backgroundPlay: Int = 2; var backgroundPlay: Int = 2;
@ -461,7 +465,7 @@ class Settings : FragmentedStorageFileJson() {
fun isBackgroundPictureInPicture() = backgroundPlay == 2; fun isBackgroundPictureInPicture() = backgroundPlay == 2;
@AdvancedField @AdvancedField
@FormField(R.string.resume_after_preview, FieldForm.DROPDOWN, R.string.when_watching_a_video_in_preview_mode_resume_at_the_position_when_opening_the_video_code, 7) @FormField(R.string.resume_after_preview, FieldForm.DROPDOWN, R.string.when_watching_a_video_in_preview_mode_resume_at_the_position_when_opening_the_video_code, 8)
@DropdownFieldOptionsId(R.array.resume_after_preview) @DropdownFieldOptionsId(R.array.resume_after_preview)
var resumeAfterPreview: Int = 1; var resumeAfterPreview: Int = 1;
@ -473,7 +477,7 @@ class Settings : FragmentedStorageFileJson() {
return false; return false;
} }
@FormField(R.string.chapter_update_fps_title, FieldForm.DROPDOWN, R.string.chapter_update_fps_description, 8) @FormField(R.string.chapter_update_fps_title, FieldForm.DROPDOWN, R.string.chapter_update_fps_description, 9)
@DropdownFieldOptionsId(R.array.chapter_fps) @DropdownFieldOptionsId(R.array.chapter_fps)
var chapterUpdateFPS: Int = 0; var chapterUpdateFPS: Int = 0;
@ -488,7 +492,7 @@ class Settings : FragmentedStorageFileJson() {
} }
@AdvancedField @AdvancedField
@FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 9) @FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 10)
var useLiveChatWindow: Boolean = true; var useLiveChatWindow: Boolean = true;
@FormField(R.string.restart_after_audio_focus_loss, FieldForm.DROPDOWN, R.string.restart_playback_when_gaining_audio_focus_after_a_loss, 11) @FormField(R.string.restart_after_audio_focus_loss, FieldForm.DROPDOWN, R.string.restart_playback_when_gaining_audio_focus_after_a_loss, 11)

View file

@ -74,6 +74,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.media3.common.Format
import com.futo.platformplayer.others.Language
import java.util.Locale
class UISlideOverlays { class UISlideOverlays {
companion object { companion object {
@ -413,14 +416,18 @@ class UISlideOverlays {
if (source is IHLSManifestAudioSource) { if (source is IHLSManifestAudioSource) {
val variant = HLS.mediaRenditionToVariant(MediaRendition("AUDIO", playlist.baseUri, "Single Playlist", null, null, null, null, null))!! val variant = HLS.mediaRenditionToVariant(MediaRendition("AUDIO", playlist.baseUri, "Single Playlist", null, null, null, null, null))!!
val estSize = VideoHelper.estimateSourceSize(variant); val language = variant.language
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else ""; val mainText = when {
language != Language.UNKNOWN && variant.bitrate == Format.NO_VALUE -> Locale.forLanguageTag(language).displayLanguage
language == Language.UNKNOWN && variant.bitrate != Format.NO_VALUE -> variant.bitrate.toHumanBitrate()
language != Language.UNKNOWN && variant.bitrate != Format.NO_VALUE -> "${Locale.forLanguageTag(language).displayLanguage} ${variant.bitrate.toHumanBitrate()}"
else -> "Default"
}
audioButtons.add(SlideUpMenuItem( audioButtons.add(SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_music, R.drawable.ic_music,
variant.name, if (variant.name != "") variant.name else mainText,
listOf(variant.language, variant.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) variant.codec.trim() else "",
(prefix + variant.codec).trim(),
tag = variant, tag = variant,
call = { call = {
selectedAudioVariant = variant selectedAudioVariant = variant
@ -432,14 +439,12 @@ class UISlideOverlays {
} else { } else {
val variant = HLS.variantReferenceToVariant(VariantPlaylistReference(playlist.baseUri, StreamInfo(null, null, null, null, null, null, null, null, null))) val variant = HLS.variantReferenceToVariant(VariantPlaylistReference(playlist.baseUri, StreamInfo(null, null, null, null, null, null, null, null, null)))
val estSize = VideoHelper.estimateSourceSize(variant);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
videoButtons.add(SlideUpMenuItem( videoButtons.add(SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_movie, R.drawable.ic_movie,
variant.name, if (variant.name != "") variant.name else "${variant.width}p${variant.frameRate ?: ""}",
"${variant.width}x${variant.height}", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) variant.codec.trim() else "",
(prefix + variant.codec).trim(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) variant.bitrate?.toHumanBitrate() else "",
tag = variant, tag = variant,
call = { call = {
selectedVideoVariant = variant selectedVideoVariant = variant
@ -454,16 +459,19 @@ class UISlideOverlays {
} else if (playlist is HlsMultivariantPlaylist) { } else if (playlist is HlsMultivariantPlaylist) {
masterPlaylist = HLS.parseMasterPlaylist(masterPlaylistContent, resolvedPlaylistUrl) masterPlaylist = HLS.parseMasterPlaylist(masterPlaylistContent, resolvedPlaylistUrl)
masterPlaylist.getAudioSources().forEach { it -> masterPlaylist.getAudioSources().forEach {
val language = it.language
val estSize = VideoHelper.estimateSourceSize(it); val mainText = when {
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else ""; language != Language.UNKNOWN && it.bitrate == Format.NO_VALUE -> Locale.forLanguageTag(language).displayLanguage
language == Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> it.bitrate.toHumanBitrate()
language != Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> "${Locale.forLanguageTag(language).displayLanguage} ${it.bitrate.toHumanBitrate()}"
else -> "Default"
}
audioButtons.add(SlideUpMenuItem( audioButtons.add(SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_music, R.drawable.ic_music,
it.name, if (it.name != "") it.name else mainText,
listOf(it.language, it.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
(prefix + it.codec).trim(),
tag = it, tag = it,
call = { call = {
selectedAudioVariant = it selectedAudioVariant = it
@ -483,14 +491,12 @@ class UISlideOverlays {
}*/ }*/
masterPlaylist.getVideoSources().forEach { masterPlaylist.getVideoSources().forEach {
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
videoButtons.add(SlideUpMenuItem( videoButtons.add(SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_movie, R.drawable.ic_movie,
it.name, if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
"${it.width}x${it.height}", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
(prefix + it.codec).trim(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() else "",
tag = it, tag = it,
call = { call = {
selectedVideoVariant = it selectedVideoVariant = it
@ -604,9 +610,9 @@ class UISlideOverlays {
SlideUpMenuItem( SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_movie, R.drawable.ic_movie,
it.name, if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
"${it.width}x${it.height}", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
(prefix + it.codec).trim(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() ?: "" else "",
tag = it, tag = it,
call = { call = {
selectedVideo = it selectedVideo = it
@ -642,8 +648,7 @@ class UISlideOverlays {
SlideUpMenuItem( SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_movie, R.drawable.ic_movie,
it.name, if (it.name != "") it.name else "HLS",
"HLS",
tag = it, tag = it,
call = { call = {
showHlsPicker(video, it, it.url, container) showHlsPicker(video, it, it.url, container)
@ -675,14 +680,18 @@ class UISlideOverlays {
.map { .map {
when (it) { when (it) {
is IAudioUrlSource -> { is IAudioUrlSource -> {
val estSize = VideoHelper.estimateSourceSize(it); val mainText = when {
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else ""; it.language != Language.UNKNOWN && it.bitrate == Format.NO_VALUE -> Locale.forLanguageTag(it.language).displayLanguage
it.language == Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> it.bitrate.toHumanBitrate()
it.language != Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> "${Locale.forLanguageTag(it.language).displayLanguage} ${it.bitrate.toHumanBitrate()}"
else -> "Default"
}
SlideUpMenuItem( SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_music, R.drawable.ic_music,
it.name, if (it.name != "") it.name else mainText,
"${it.bitrate}", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() ?: "" else "",
(prefix + it.codec).trim(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) (if (it.original) "Original" else "") else "",
tag = it, tag = it,
call = { call = {
selectedAudio = it selectedAudio = it
@ -716,8 +725,7 @@ class UISlideOverlays {
SlideUpMenuItem( SlideUpMenuItem(
container.context, container.context,
R.drawable.ic_movie, R.drawable.ic_movie,
it.name, if (it.name != "") it.name else "HLS Audio",
"HLS Audio",
tag = it, tag = it,
call = { call = {
showHlsPicker(video, it, it.url, container) showHlsPicker(video, it, it.url, container)

View file

@ -9,6 +9,8 @@ class DashManifestSource : IVideoSource, IDashManifestSource {
override val bitrate: Int? = null; override val bitrate: Int? = null;
override val url : String; override val url : String;
override val duration: Long get() = 0; override val duration: Long get() = 0;
// only used for single source DASH
override val frameRate: Int? = null
override var priority: Boolean = false; override var priority: Boolean = false;

View file

@ -9,6 +9,8 @@ class HLSManifestSource : IVideoSource, IHLSManifestSource {
override val bitrate : Int? = null; override val bitrate : Int? = null;
override val url : String; override val url : String;
override val duration: Long = 0; override val duration: Long = 0;
override val frameRate: Int?
get() = null
override var priority: Boolean = false; override var priority: Boolean = false;

View file

@ -12,7 +12,8 @@ class HLSVariantVideoUrlSource(
override val bitrate: Int?, override val bitrate: Int?,
override val duration: Long, override val duration: Long,
override val priority: Boolean, override val priority: Boolean,
val url: String val url: String,
override val frameRate: Int? = null
) : IVideoUrlSource { ) : IVideoUrlSource {
override fun getVideoUrl(): String { override fun getVideoUrl(): String {
return url return url

View file

@ -9,4 +9,5 @@ interface IVideoSource {
val bitrate : Int?; val bitrate : Int?;
val duration: Long; val duration: Long;
val priority: Boolean; val priority: Boolean;
val frameRate: Int?
} }

View file

@ -13,6 +13,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
override val name : String; override val name : String;
override val bitrate : Int; override val bitrate : Int;
override val duration : Long; override val duration : Long;
override val frameRate: Int?
override var priority: Boolean = false; override var priority: Boolean = false;
@ -22,7 +23,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
//Only for particular videos //Only for particular videos
override var streamMetaData: StreamMetaData? = null; override var streamMetaData: StreamMetaData? = null;
constructor(name : String, filePath : String, fileSize: Long, width : Int = 0, height : Int = 0, duration: Long = 0, container : String = "", codec : String = "", bitrate : Int = 0) { constructor(name : String, filePath : String, fileSize: Long, width : Int = 0, height : Int = 0, duration: Long = 0, container : String = "", codec : String = "", bitrate : Int = 0, frameRate : Int? = null) {
this.name = name; this.name = name;
this.width = width; this.width = width;
this.height = height; this.height = height;
@ -32,6 +33,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
this.filePath = filePath; this.filePath = filePath;
this.fileSize = fileSize; this.fileSize = fileSize;
this.bitrate = bitrate; this.bitrate = bitrate;
this.frameRate = frameRate
} }
companion object { companion object {
@ -45,7 +47,8 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
source.duration, source.duration,
overrideContainer ?: source.container, overrideContainer ?: source.container,
source.codec, source.codec,
source.bitrate?:0 source.bitrate?:0,
source.frameRate
); );
} }
} }

View file

@ -13,6 +13,7 @@ open class VideoUrlSource(
override val container : String = "", override val container : String = "",
override val codec : String = "", override val codec : String = "",
override val bitrate : Int? = 0, override val bitrate : Int? = 0,
override val frameRate: Int? = null,
override var priority: Boolean = false override var priority: Boolean = false
) : IVideoUrlSource, IStreamMetaDataSource { ) : IVideoUrlSource, IStreamMetaDataSource {
@ -38,7 +39,8 @@ open class VideoUrlSource(
source.duration, source.duration,
source.container, source.container,
source.codec, source.codec,
source.bitrate source.bitrate,
source.frameRate
); );
ret.streamMetaData = streamData; ret.streamMetaData = streamData;

View file

@ -40,6 +40,8 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
override val bitrate: Int?; override val bitrate: Int?;
override val duration: Long; override val duration: Long;
override val priority: Boolean; override val priority: Boolean;
// only used for single source DASH
override val frameRate: Int?
val url: String?; val url: String?;
override var manifest: String?; override var manifest: String?;
@ -61,6 +63,7 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
codec = _obj.getOrDefault(config, "codec", contextName, "") ?: ""; codec = _obj.getOrDefault(config, "codec", contextName, "") ?: "";
bitrate = _obj.getOrDefault(config, "bitrate", contextName, 0) ?: 0; bitrate = _obj.getOrDefault(config, "bitrate", contextName, 0) ?: 0;
duration = _obj.getOrDefault(config, "duration", contextName, 0) ?: 0; duration = _obj.getOrDefault(config, "duration", contextName, 0) ?: 0;
frameRate = _obj.getOrNull(config, "frameRate", contextName);
priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false; priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false;
canMerge = _obj.getOrDefault(config, "canMerge", contextName, false) ?: false; canMerge = _obj.getOrDefault(config, "canMerge", contextName, false) ?: false;
hasGenerate = _obj.has("generate"); hasGenerate = _obj.has("generate");

View file

@ -18,6 +18,8 @@ class JSDashManifestSource : IVideoUrlSource, IDashManifestSource, JSSource {
override val bitrate: Int? = null; override val bitrate: Int? = null;
override val url : String; override val url : String;
override val duration: Long; override val duration: Long;
// only used for single source DASH
override val frameRate: Int? = null
override var priority: Boolean = false; override var priority: Boolean = false;

View file

@ -22,6 +22,7 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
override val bitrate: Int? = null override val bitrate: Int? = null
override val url: String override val url: String
override val duration: Long override val duration: Long
override val frameRate: Int? = null
override var priority: Boolean = false override var priority: Boolean = false

View file

@ -18,6 +18,7 @@ class JSHLSManifestSource : IHLSManifestSource, JSSource {
override val bitrate : Int? = null; override val bitrate : Int? = null;
override val url : String; override val url : String;
override val duration: Long; override val duration: Long;
override val frameRate: Int? = null
override var priority: Boolean = false; override var priority: Boolean = false;

View file

@ -16,6 +16,7 @@ open class JSVideoUrlSource : IVideoUrlSource, JSSource {
override val name : String; override val name : String;
override val bitrate : Int; override val bitrate : Int;
override val duration: Long; override val duration: Long;
override val frameRate: Int?
private val url : String; private val url : String;
override var priority: Boolean = false; override var priority: Boolean = false;
@ -31,6 +32,7 @@ open class JSVideoUrlSource : IVideoUrlSource, JSSource {
name = _obj.getOrThrow(config, "name", contextName); name = _obj.getOrThrow(config, "name", contextName);
bitrate = _obj.getOrThrow(config, "bitrate", contextName); bitrate = _obj.getOrThrow(config, "bitrate", contextName);
duration = _obj.getOrThrow<Int>(config, "duration", contextName).toLong(); duration = _obj.getOrThrow<Int>(config, "duration", contextName).toLong();
frameRate = _obj.getOrNull(config, "frameRate", contextName);
url = _obj.getOrThrow(config, "url", contextName); url = _obj.getOrThrow(config, "url", contextName);
priority = obj.getOrNull(config, "priority", contextName) ?: false; priority = obj.getOrNull(config, "priority", contextName) ?: false;

View file

@ -19,6 +19,7 @@ class LocalVideoFileSource: IVideoSource {
override val bitrate: Int = 0 override val bitrate: Int = 0
override val duration: Long; override val duration: Long;
override val priority: Boolean = false; override val priority: Boolean = false;
override val frameRate: Int? = null
constructor(file: File) { constructor(file: File) {
name = file.name; name = file.name;

View file

@ -104,6 +104,7 @@ import com.futo.platformplayer.getNowDiffSeconds
import com.futo.platformplayer.helpers.VideoHelper import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.others.Language
import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.selectBestImage
import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.AnnouncementType
@ -2414,8 +2415,9 @@ class VideoDetailView : ConstraintLayout {
.map { .map {
SlideUpMenuItem(this.context, SlideUpMenuItem(this.context,
R.drawable.ic_movie, R.drawable.ic_movie,
it.name, if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
"${it.width}x${it.height}", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() ?: "" else "",
tag = it, tag = it,
call = { handleSelectVideoTrack(it) }); call = { handleSelectVideoTrack(it) });
}.toList().toTypedArray()) }.toList().toTypedArray())
@ -2424,10 +2426,18 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.offline_audio), "audio", SlideUpMenuGroup(this.context, context.getString(R.string.offline_audio), "audio",
*localAudioSource *localAudioSource
.map { .map {
val mainText = when {
it.language != Language.UNKNOWN && it.bitrate == Format.NO_VALUE -> Locale.forLanguageTag(it.language).displayLanguage
it.language == Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> it.bitrate.toHumanBitrate()
it.language != Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> "${Locale.forLanguageTag(it.language).displayLanguage} ${it.bitrate.toHumanBitrate()}"
else -> "Default"
}
SlideUpMenuItem(this.context, SlideUpMenuItem(this.context,
R.drawable.ic_music, R.drawable.ic_music,
it.name, if (it.name != "") it.name else mainText,
it.bitrate.toHumanBitrate(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) (if (it.original) "Original" else "") else "",
tag = it, tag = it,
call = { handleSelectAudioTrack(it) }); call = { handleSelectAudioTrack(it) });
}.toList().toTypedArray()) }.toList().toTypedArray())
@ -2444,36 +2454,49 @@ class VideoDetailView : ConstraintLayout {
this.context, context.getString(R.string.stream_video), "video", (listOf( this.context, context.getString(R.string.stream_video), "video", (listOf(
SlideUpMenuItem(this.context, R.drawable.ic_movie, "Auto", tag = "auto", call = { _player.selectVideoTrack(-1) }) SlideUpMenuItem(this.context, R.drawable.ic_movie, "Auto", tag = "auto", call = { _player.selectVideoTrack(-1) })
) + (liveStreamVideoFormats.map { ) + (liveStreamVideoFormats.map {
SlideUpMenuItem(this.context, R.drawable.ic_movie, it.label val frameRate =
?: it.containerMimeType if (it.frameRate.toInt() == Format.NO_VALUE) "" else it.frameRate.toInt()
?: it.bitrate.toString(), "${it.width}x${it.height}", tag = it, call = { _player.selectVideoTrack(it.height) }); SlideUpMenuItem(
this.context, R.drawable.ic_movie, "${it.height}p${frameRate}",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.containerMimeType ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.label ?: it.bitrate.toHumanBitrate() else "",
tag = it, call = { _player.selectVideoTrack(it.height) });
})) }))
) )
else null, else null,
if(liveStreamAudioFormats?.isEmpty() == false) if (liveStreamAudioFormats?.isEmpty() == false)
SlideUpMenuGroup(this.context, context.getString(R.string.stream_audio), "audio", SlideUpMenuGroup(
this.context, context.getString(R.string.stream_audio), "audio",
*liveStreamAudioFormats *liveStreamAudioFormats
.map { .map {
SlideUpMenuItem(this.context, val language = it.language
val mainText = when {
language != null && it.bitrate == Format.NO_VALUE -> Locale.forLanguageTag(language).displayLanguage
language == null && it.bitrate != Format.NO_VALUE -> it.bitrate.toHumanBitrate()
language != null && it.bitrate != Format.NO_VALUE -> "${Locale.forLanguageTag(language).displayLanguage} ${it.bitrate.toHumanBitrate()}"
else -> "Default"
}
SlideUpMenuItem(
this.context,
R.drawable.ic_music, R.drawable.ic_music,
"${it.label ?: it.containerMimeType} ${it.bitrate}", mainText,
"", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.containerMimeType ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.label else "",
tag = it, tag = it,
call = { _player.selectAudioTrack(it.bitrate) }); call = { _player.selectAudioTrack(it.bitrate) });
}.toList().toTypedArray()) }.toList().toTypedArray()
)
else null, else null,
if(bestVideoSources.isNotEmpty()) if(bestVideoSources.isNotEmpty())
SlideUpMenuGroup(this.context, context.getString(R.string.video), "video", SlideUpMenuGroup(this.context, context.getString(R.string.video), "video",
*bestVideoSources *bestVideoSources
.map { .map {
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
SlideUpMenuItem(this.context, SlideUpMenuItem(this.context,
R.drawable.ic_movie, R.drawable.ic_movie,
it!!.name, if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
if (it.width > 0 && it.height > 0) "${it.width}x${it.height}" else "", if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
(prefix + it.codec.trim()).trim(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() ?: "" else "",
tag = it, tag = it,
call = { handleSelectVideoTrack(it) }); call = { handleSelectVideoTrack(it) });
}.toList().toTypedArray()) }.toList().toTypedArray())
@ -2482,13 +2505,17 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.audio), "audio", SlideUpMenuGroup(this.context, context.getString(R.string.audio), "audio",
*bestAudioSources *bestAudioSources
.map { .map {
val estSize = VideoHelper.estimateSourceSize(it); val mainText = when {
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else ""; it.language != Language.UNKNOWN && it.bitrate == Format.NO_VALUE -> Locale.forLanguageTag(it.language).displayLanguage
it.language == Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> it.bitrate.toHumanBitrate()
it.language != Language.UNKNOWN && it.bitrate != Format.NO_VALUE -> "${Locale.forLanguageTag(it.language).displayLanguage} ${it.bitrate.toHumanBitrate()}"
else -> "Default"
}
SlideUpMenuItem(this.context, SlideUpMenuItem(this.context,
R.drawable.ic_music, R.drawable.ic_music,
it.name, if (it.name != "") it.name else mainText,
it.bitrate.toHumanBitrate(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() ?: "" else "",
(prefix + it.codec.trim()).trim(), if (Settings.instance.playback.showAdvancedMediaSourceMetadata) (if (it.original) "Original" else "") else "",
tag = it, tag = it,
call = { handleSelectAudioTrack(it) }); call = { handleSelectAudioTrack(it) });
}.toList().toTypedArray()) }.toList().toTypedArray())

View file

@ -308,6 +308,7 @@
<string name="check_disabled_plugin_updates_description">Check disabled plugins for updates</string> <string name="check_disabled_plugin_updates_description">Check disabled plugins for updates</string>
<string name="planned_content_notifications">Planned Content Notifications</string> <string name="planned_content_notifications">Planned Content Notifications</string>
<string name="planned_content_notifications_description">Schedules discovered planned content as notifications, resulting in more accurate notifications for this content.</string> <string name="planned_content_notifications_description">Schedules discovered planned content as notifications, resulting in more accurate notifications for this content.</string>
<string name="show_advanced_media_source_metadata_desc">When displaying media sources show advanced metadata like container and codec</string>
<string name="attempt_to_utilize_byte_ranges">Attempt to utilize byte ranges</string> <string name="attempt_to_utilize_byte_ranges">Attempt to utilize byte ranges</string>
<string name="auto_update">Auto Update</string> <string name="auto_update">Auto Update</string>
<string name="always_allow_reverse_landscape_auto_rotate">Always allow reverse landscape auto-rotate</string> <string name="always_allow_reverse_landscape_auto_rotate">Always allow reverse landscape auto-rotate</string>
@ -358,6 +359,7 @@
<string name="default_audio_quality">Default Audio Quality</string> <string name="default_audio_quality">Default Audio Quality</string>
<string name="default_playback_speed">Default Playback Speed</string> <string name="default_playback_speed">Default Playback Speed</string>
<string name="default_video_quality">Default Video Quality</string> <string name="default_video_quality">Default Video Quality</string>
<string name="show_advanced_media_source_metadata">Show Advanced Media Source Metadata</string>
<string name="deletes_license_keys_from_app">Deletes license keys from app</string> <string name="deletes_license_keys_from_app">Deletes license keys from app</string>
<string name="download_when">Download when</string> <string name="download_when">Download when</string>
<string name="enable_video_cache">Enable Video Cache</string> <string name="enable_video_cache">Enable Video Cache</string>