Compare commits

..

1 Commits

Author SHA1 Message Date
Kai 11fd27b774 Fix https://github.com/futo-org/grayjay-android/issues/2055
Use app level screen brightness instead of changing the system brightness

This also fixes an issue where we don't correctly restore the auto brightness state of the device

Changelog: changed
2025-06-13 14:51:48 -05:00
29 changed files with 150 additions and 296 deletions
-1
View File
@@ -14,7 +14,6 @@
<!--<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions"/>
<application
android:allowBackup="true"
-2
View File
@@ -403,8 +403,6 @@ class VideoUrlSource {
this.bitrate = obj.bitrate ?? 0;
this.duration = obj.duration ?? 0;
this.url = obj.url;
if(obj.frameRate)
this.frameRate = obj.frameRate;
if(obj.requestModifier)
this.requestModifier = obj.requestModifier;
}
@@ -442,18 +442,14 @@ class Settings : FragmentedStorageFileJson() {
fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality);
@AdvancedField
@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)
@FormField(R.string.simplify_sources, FieldForm.TOGGLE, R.string.simplify_sources_description, 4)
var simplifySources: Boolean = true;
@AdvancedField
@FormField(R.string.always_allow_reverse_landscape_auto_rotate, FieldForm.TOGGLE, R.string.always_allow_reverse_landscape_auto_rotate_description, 6)
@FormField(R.string.always_allow_reverse_landscape_auto_rotate, FieldForm.TOGGLE, R.string.always_allow_reverse_landscape_auto_rotate_description, 5)
var alwaysAllowReverseLandscapeAutoRotate: Boolean = true
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 7)
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 6)
@DropdownFieldOptionsId(R.array.player_background_behavior)
var backgroundPlay: Int = 2;
@@ -461,7 +457,7 @@ class Settings : FragmentedStorageFileJson() {
fun isBackgroundPictureInPicture() = backgroundPlay == 2;
@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, 8)
@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)
@DropdownFieldOptionsId(R.array.resume_after_preview)
var resumeAfterPreview: Int = 1;
@@ -473,7 +469,7 @@ class Settings : FragmentedStorageFileJson() {
return false;
}
@FormField(R.string.chapter_update_fps_title, FieldForm.DROPDOWN, R.string.chapter_update_fps_description, 9)
@FormField(R.string.chapter_update_fps_title, FieldForm.DROPDOWN, R.string.chapter_update_fps_description, 8)
@DropdownFieldOptionsId(R.array.chapter_fps)
var chapterUpdateFPS: Int = 0;
@@ -488,7 +484,7 @@ class Settings : FragmentedStorageFileJson() {
}
@AdvancedField
@FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 10)
@FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 9)
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)
@@ -1021,13 +1017,10 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.playlist_allow_dups, FieldForm.TOGGLE, R.string.playlist_allow_dups_description, 3)
var playlistAllowDups: Boolean = true;
@FormField(R.string.add_to_beginning_of_watch_later, FieldForm.TOGGLE, R.string.add_to_beginning_description, 4)
var addToBeginning: Boolean = true;
@FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 5)
@FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 4)
var polycentricEnabled: Boolean = true;
@FormField(R.string.polycentric_local_cache, FieldForm.TOGGLE, R.string.polycentric_local_cache_description, 7)
@FormField(R.string.polycentric_local_cache, FieldForm.TOGGLE, R.string.polycentric_local_cache_description, 5)
var polycentricLocalCache: Boolean = true;
}
@@ -1044,19 +1037,16 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.toggle_full_screen, FieldForm.TOGGLE, R.string.toggle_full_screen_descr, 3)
var toggleFullscreen: Boolean = true;
@FormField(R.string.system_brightness, FieldForm.TOGGLE, R.string.system_brightness_descr, 4)
var useSystemBrightness: Boolean = false;
@FormField(R.string.screen_brightness, FieldForm.TOGGLE, R.string.screen_brightness_desc, 4)
var controlScreenBrightness: Boolean = true;
@FormField(R.string.system_volume, FieldForm.TOGGLE, R.string.system_volume_descr, 5)
var useSystemVolume: Boolean = true;
@FormField(R.string.restore_system_brightness, FieldForm.TOGGLE, R.string.restore_system_brightness_descr, 6)
var restoreSystemBrightness: Boolean = true;
@FormField(R.string.zoom_option, FieldForm.TOGGLE, R.string.zoom_option_descr, 7)
@FormField(R.string.zoom_option, FieldForm.TOGGLE, R.string.zoom_option_descr, 6)
var zoom: Boolean = true;
@FormField(R.string.pan_option, FieldForm.TOGGLE, R.string.pan_option_descr, 8)
@FormField(R.string.pan_option, FieldForm.TOGGLE, R.string.pan_option_descr, 7)
var pan: Boolean = true;
}
@@ -74,9 +74,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import androidx.core.net.toUri
import androidx.media3.common.Format
import com.futo.platformplayer.others.Language
import java.util.Locale
class UISlideOverlays {
companion object {
@@ -347,18 +344,14 @@ class UISlideOverlays {
if (source is IHLSManifestAudioSource) {
val variant = HLS.mediaRenditionToVariant(MediaRendition("AUDIO", playlist.baseUri, "Single Playlist", null, null, null, null, null))!!
val language = variant.language
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"
}
val estSize = VideoHelper.estimateSourceSize(variant);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
audioButtons.add(SlideUpMenuItem(
container.context,
R.drawable.ic_music,
if (variant.name != "") variant.name else mainText,
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) variant.codec.trim() else "",
variant.name,
listOf(variant.language, variant.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
(prefix + variant.codec).trim(),
tag = variant,
call = {
selectedAudioVariant = variant
@@ -370,12 +363,14 @@ class UISlideOverlays {
} else {
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(
container.context,
R.drawable.ic_movie,
if (variant.name != "") variant.name else "${variant.width}p${variant.frameRate ?: ""}",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) variant.codec.trim() else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) variant.bitrate?.toHumanBitrate() else "",
variant.name,
"${variant.width}x${variant.height}",
(prefix + variant.codec).trim(),
tag = variant,
call = {
selectedVideoVariant = variant
@@ -390,19 +385,16 @@ class UISlideOverlays {
} else if (playlist is HlsMultivariantPlaylist) {
masterPlaylist = HLS.parseMasterPlaylist(masterPlaylistContent, resolvedPlaylistUrl)
masterPlaylist.getAudioSources().forEach {
val language = it.language
val mainText = when {
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"
}
masterPlaylist.getAudioSources().forEach { it ->
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
audioButtons.add(SlideUpMenuItem(
container.context,
R.drawable.ic_music,
if (it.name != "") it.name else mainText,
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
it.name,
listOf(it.language, it.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
(prefix + it.codec).trim(),
tag = it,
call = {
selectedAudioVariant = it
@@ -422,12 +414,14 @@ class UISlideOverlays {
}*/
masterPlaylist.getVideoSources().forEach {
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
videoButtons.add(SlideUpMenuItem(
container.context,
R.drawable.ic_movie,
if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() else "",
it.name,
"${it.width}x${it.height}",
(prefix + it.codec).trim(),
tag = it,
call = {
selectedVideoVariant = it
@@ -541,9 +535,9 @@ class UISlideOverlays {
SlideUpMenuItem(
container.context,
R.drawable.ic_movie,
if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() ?: "" else "",
it.name,
"${it.width}x${it.height}",
(prefix + it.codec).trim(),
tag = it,
call = {
selectedVideo = it
@@ -579,7 +573,8 @@ class UISlideOverlays {
SlideUpMenuItem(
container.context,
R.drawable.ic_movie,
if (it.name != "") it.name else "HLS",
it.name,
"HLS",
tag = it,
call = {
showHlsPicker(video, it, it.url, container)
@@ -611,18 +606,14 @@ class UISlideOverlays {
.map {
when (it) {
is IAudioUrlSource -> {
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"
}
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
SlideUpMenuItem(
container.context,
R.drawable.ic_music,
if (it.name != "") it.name else mainText,
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) (if (it.original) "Original" else "") else "",
it.name,
"${it.bitrate}",
(prefix + it.codec).trim(),
tag = it,
call = {
selectedAudio = it
@@ -656,7 +647,8 @@ class UISlideOverlays {
SlideUpMenuItem(
container.context,
R.drawable.ic_movie,
if (it.name != "") it.name else "HLS Audio",
it.name,
"HLS Audio",
tag = it,
call = {
showHlsPicker(video, it, it.url, container)
@@ -1159,8 +1151,6 @@ class UISlideOverlays {
call = {
if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true))
UIDialogs.appToast("Added to watch later", false);
else
UIDialogs.toast(container.context.getString(R.string.already_in_watch_later))
}),
)
);
@@ -9,8 +9,6 @@ class DashManifestSource : IVideoSource, IDashManifestSource {
override val bitrate: Int? = null;
override val url : String;
override val duration: Long get() = 0;
// only used for single source DASH
override val frameRate: Int? = null
override var priority: Boolean = false;
@@ -9,8 +9,6 @@ class HLSManifestSource : IVideoSource, IHLSManifestSource {
override val bitrate : Int? = null;
override val url : String;
override val duration: Long = 0;
override val frameRate: Int?
get() = null
override var priority: Boolean = false;
@@ -12,8 +12,7 @@ class HLSVariantVideoUrlSource(
override val bitrate: Int?,
override val duration: Long,
override val priority: Boolean,
val url: String,
override val frameRate: Int? = null
val url: String
) : IVideoUrlSource {
override fun getVideoUrl(): String {
return url
@@ -9,5 +9,4 @@ interface IVideoSource {
val bitrate : Int?;
val duration: Long;
val priority: Boolean;
val frameRate: Int?
}
@@ -13,7 +13,6 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
override val name : String;
override val bitrate : Int;
override val duration : Long;
override val frameRate: Int?
override var priority: Boolean = false;
@@ -23,7 +22,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
//Only for particular videos
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, frameRate : Int? = null) {
constructor(name : String, filePath : String, fileSize: Long, width : Int = 0, height : Int = 0, duration: Long = 0, container : String = "", codec : String = "", bitrate : Int = 0) {
this.name = name;
this.width = width;
this.height = height;
@@ -33,7 +32,6 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
this.filePath = filePath;
this.fileSize = fileSize;
this.bitrate = bitrate;
this.frameRate = frameRate
}
companion object {
@@ -47,8 +45,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource {
source.duration,
overrideContainer ?: source.container,
source.codec,
source.bitrate?:0,
source.frameRate
source.bitrate?:0
);
}
}
@@ -13,7 +13,6 @@ open class VideoUrlSource(
override val container : String = "",
override val codec : String = "",
override val bitrate : Int? = 0,
override val frameRate: Int? = null,
override var priority: Boolean = false
) : IVideoUrlSource, IStreamMetaDataSource {
@@ -39,8 +38,7 @@ open class VideoUrlSource(
source.duration,
source.container,
source.codec,
source.bitrate,
source.frameRate
source.bitrate
);
ret.streamMetaData = streamData;
@@ -4,7 +4,6 @@ import android.net.Uri
import com.futo.platformplayer.SignatureProvider
import com.futo.platformplayer.api.media.Serializer
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.matchesDomain
import com.futo.platformplayer.states.StatePlugins
import kotlinx.serialization.Contextual
@@ -169,17 +168,12 @@ class SourcePluginConfig(
}
fun validate(text: String): Boolean {
try {
if (scriptPublicKey.isNullOrEmpty())
throw IllegalStateException("No public key present");
if (scriptSignature.isNullOrEmpty())
throw IllegalStateException("No signature present");
if(scriptPublicKey.isNullOrEmpty())
throw IllegalStateException("No public key present");
if(scriptSignature.isNullOrEmpty())
throw IllegalStateException("No signature present");
return SignatureProvider.verify(text, scriptSignature, scriptPublicKey);
} catch (e: Throwable) {
Logger.e(TAG, "Failed to verify due to an unhandled exception", e)
return false
}
return SignatureProvider.verify(text, scriptSignature, scriptPublicKey);
}
fun isUrlAllowed(url: String): Boolean {
@@ -210,8 +204,6 @@ class SourcePluginConfig(
obj.sourceUrl = sourceUrl;
return obj;
}
private val TAG = "SourcePluginConfig"
}
@kotlinx.serialization.Serializable
@@ -31,8 +31,6 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
override val bitrate: Int?;
override val duration: Long;
override val priority: Boolean;
// only used for single source DASH
override val frameRate: Int?
var url: String?;
override var manifest: String?;
@@ -54,7 +52,6 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo
codec = _obj.getOrDefault(config, "codec", contextName, "") ?: "";
bitrate = _obj.getOrDefault(config, "bitrate", 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;
canMerge = _obj.getOrDefault(config, "canMerge", contextName, false) ?: false;
hasGenerate = _obj.has("generate");
@@ -18,8 +18,6 @@ class JSDashManifestSource : IVideoUrlSource, IDashManifestSource, JSSource {
override val bitrate: Int? = null;
override val url : String;
override val duration: Long;
// only used for single source DASH
override val frameRate: Int? = null
override var priority: Boolean = false;
@@ -20,7 +20,6 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
override val bitrate: Int? = null
override val url: String
override val duration: Long
override val frameRate: Int? = null
override var priority: Boolean = false
@@ -18,7 +18,6 @@ class JSHLSManifestSource : IHLSManifestSource, JSSource {
override val bitrate : Int? = null;
override val url : String;
override val duration: Long;
override val frameRate: Int? = null
override var priority: Boolean = false;
@@ -16,7 +16,6 @@ open class JSVideoUrlSource : IVideoUrlSource, JSSource {
override val name : String;
override val bitrate : Int;
override val duration: Long;
override val frameRate: Int?
private val url : String;
override var priority: Boolean = false;
@@ -32,7 +31,6 @@ open class JSVideoUrlSource : IVideoUrlSource, JSSource {
name = _obj.getOrThrow(config, "name", contextName);
bitrate = _obj.getOrThrow(config, "bitrate", contextName);
duration = _obj.getOrThrow<Int>(config, "duration", contextName).toLong();
frameRate = _obj.getOrNull(config, "frameRate", contextName);
url = _obj.getOrThrow(config, "url", contextName);
priority = obj.getOrNull(config, "priority", contextName) ?: false;
@@ -19,7 +19,6 @@ class LocalVideoFileSource: IVideoSource {
override val bitrate: Int = 0
override val duration: Long;
override val priority: Boolean = false;
override val frameRate: Int? = null
constructor(file: File) {
name = file.name;
@@ -778,8 +778,6 @@ class ArticleDetailFragment : MainFragment {
view.onAddToWatchLaterClicked.subscribe { a ->
if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true))
UIDialogs.toast("Added to watch later\n[${content.name}]")
else
UIDialogs.toast(context.getString(R.string.already_in_watch_later))
}
}
else if(content is IPlatformPost) {
@@ -226,8 +226,6 @@ class ChannelFragment : MainFragment() {
if (content is IPlatformVideo) {
if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true))
UIDialogs.toast("Added to watch later\n[${content.name}]")
else
UIDialogs.toast(context.getString(R.string.already_in_watch_later))
}
}
adapter.onUrlClicked.subscribe { url ->
@@ -86,8 +86,6 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
if(it is IPlatformVideo) {
if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it), true))
UIDialogs.toast("Added to watch later\n[${it.name}]");
else
UIDialogs.toast(context.getString(R.string.already_in_watch_later))
}
};
adapter.onLongPress.subscribe(this) {
@@ -101,7 +101,7 @@ class VideoDetailFragment() : MainFragment() {
}
private fun isSmallWindow(): Boolean {
return resources.configuration.smallestScreenWidthDp < resources.getInteger(R.integer.smallest_width_dp)
return resources.configuration.smallestScreenWidthDp < resources.getInteger(R.integer.column_width_dp) * 2
}
private fun isAutoRotateEnabled(): Boolean {
@@ -455,10 +455,6 @@ class VideoDetailFragment() : MainFragment() {
activity?.enterPictureInPictureMode(params);
}
}
if (isFullscreen) {
viewDetail?.restoreBrightness()
}
}
fun forcePictureInPicture() {
@@ -495,10 +491,6 @@ class VideoDetailFragment() : MainFragment() {
_isActive = true;
_leavingPiP = false;
if (isFullscreen) {
_viewDetail?.saveBrightness()
}
_viewDetail?.let {
Logger.v(TAG, "onResume preventPictureInPicture=false");
it.preventPictureInPicture = false;
@@ -627,6 +619,11 @@ class VideoDetailFragment() : MainFragment() {
showSystemUI()
}
// temporarily force the device to portrait if auto-rotate is disabled to prevent landscape when exiting full screen on a small device
// @SuppressLint("SourceLockedOrientationActivity")
// if (!isFullscreen && isSmallWindow() && !isAutoRotateEnabled() && !isMinimizingFromFullScreen) {
// activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
// }
updateOrientation();
_view?.allowMotion = !fullscreen;
}
@@ -101,7 +101,6 @@ import com.futo.platformplayer.getNowDiffSeconds
import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.others.Language
import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.selectBestImage
import com.futo.platformplayer.states.AnnouncementType
@@ -1935,8 +1934,8 @@ class VideoDetailView : ConstraintLayout {
}
updateQualityFormatsOverlay(
videoTrackFormats.distinctBy { it.height }.sortedByDescending { it.height },
audioTrackFormats.distinctBy { it.bitrate }.sortedByDescending { it.bitrate });
videoTrackFormats.distinctBy { it.height }.sortedBy { it.height },
audioTrackFormats.distinctBy { it.bitrate }.sortedBy { it.bitrate });
}
}
@@ -2231,9 +2230,8 @@ class VideoDetailView : ConstraintLayout {
.map {
SlideUpMenuItem(this.context,
R.drawable.ic_movie,
if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() ?: "" else "",
it.name,
"${it.width}x${it.height}",
tag = it,
call = { handleSelectVideoTrack(it) });
}.toList().toTypedArray())
@@ -2242,18 +2240,10 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.offline_audio), "audio",
*localAudioSource
.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,
R.drawable.ic_music,
if (it.name != "") it.name else mainText,
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) (if (it.original) "Original" else "") else "",
it.name,
it.bitrate.toHumanBitrate(),
tag = it,
call = { handleSelectAudioTrack(it) });
}.toList().toTypedArray())
@@ -2270,49 +2260,36 @@ class VideoDetailView : ConstraintLayout {
this.context, context.getString(R.string.stream_video), "video", (listOf(
SlideUpMenuItem(this.context, R.drawable.ic_movie, "Auto", tag = "auto", call = { _player.selectVideoTrack(-1) })
) + (liveStreamVideoFormats.map {
val frameRate =
if (it.frameRate.toInt() == Format.NO_VALUE) "" else it.frameRate.toInt()
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) });
SlideUpMenuItem(this.context, R.drawable.ic_movie, it.label
?: it.containerMimeType
?: it.bitrate.toString(), "${it.width}x${it.height}", tag = it, call = { _player.selectVideoTrack(it.height) });
}))
)
else null,
if (liveStreamAudioFormats?.isEmpty() == false)
SlideUpMenuGroup(
this.context, context.getString(R.string.stream_audio), "audio",
if(liveStreamAudioFormats?.isEmpty() == false)
SlideUpMenuGroup(this.context, context.getString(R.string.stream_audio), "audio",
*liveStreamAudioFormats
.map {
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,
SlideUpMenuItem(this.context,
R.drawable.ic_music,
mainText,
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.containerMimeType ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.label else "",
"${it.label ?: it.containerMimeType} ${it.bitrate}",
"",
tag = it,
call = { _player.selectAudioTrack(it.bitrate) });
}.toList().toTypedArray()
)
}.toList().toTypedArray())
else null,
if(bestVideoSources.isNotEmpty())
SlideUpMenuGroup(this.context, context.getString(R.string.video), "video",
*bestVideoSources
.map {
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
SlideUpMenuItem(this.context,
R.drawable.ic_movie,
if (it.name != "") it.name else "${it.height}p${it.frameRate ?: ""}",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.bitrate?.toHumanBitrate() ?: "" else "",
it!!.name,
if (it.width > 0 && it.height > 0) "${it.width}x${it.height}" else "",
(prefix + it.codec.trim()).trim(),
tag = it,
call = { handleSelectVideoTrack(it) });
}.toList().toTypedArray())
@@ -2321,17 +2298,13 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.audio), "audio",
*bestAudioSources
.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"
}
val estSize = VideoHelper.estimateSourceSize(it);
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
SlideUpMenuItem(this.context,
R.drawable.ic_music,
if (it.name != "") it.name else mainText,
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) it.codec.trim() ?: "" else "",
if (Settings.instance.playback.showAdvancedMediaSourceMetadata) (if (it.original) "Original" else "") else "",
it.name,
it.bitrate.toHumanBitrate(),
(prefix + it.codec.trim()).trim(),
tag = it,
call = { handleSelectAudioTrack(it) });
}.toList().toTypedArray())
@@ -2602,15 +2575,6 @@ class VideoDetailView : ConstraintLayout {
}
}
fun saveBrightness() {
if (Settings.instance.gestureControls.useSystemBrightness) {
_player.gestureControl.saveBrightness()
}
}
fun restoreBrightness() {
_player.gestureControl.restoreBrightness()
}
fun setFullscreen(fullscreen : Boolean) {
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
_player.setFullScreen(fullscreen)
@@ -2785,8 +2749,6 @@ class VideoDetailView : ConstraintLayout {
if(it is IPlatformVideo) {
if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it), true))
UIDialogs.toast("Added to watch later\n[${it.name}]");
else
UIDialogs.toast(context.getString(R.string.already_in_watch_later))
}
}
onAddToQueueClicked.subscribe(this) {
@@ -3248,6 +3210,8 @@ class VideoDetailView : ConstraintLayout {
fun applyFragment(frag: VideoDetailFragment) {
fragment = frag;
_player.fragment = frag
}
@@ -178,30 +178,31 @@ class StatePlaylists {
StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER);
}
}
fun addToWatchLater(video: SerializedPlatformVideo, isUserInteraction: Boolean = false): Boolean {
fun addToWatchLater(video: SerializedPlatformVideo, isUserInteraction: Boolean = false, orderPosition: Int = -1): Boolean {
var wasNew = false;
synchronized(_watchlistStore) {
if (_watchlistStore.hasItem { it.url == video.url }) {
return false
if(!_watchlistStore.hasItem { it.url == video.url })
wasNew = true;
_watchlistStore.saveAsync(video);
if(orderPosition == -1)
_watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values).toTypedArray());
else {
val existing = _watchlistOrderStore.getAllValues().toMutableList();
existing.add(orderPosition, video.url);
_watchlistOrderStore.set(*existing.toTypedArray());
}
_watchlistStore.saveAsync(video)
if (Settings.instance.other.addToBeginning) {
_watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values).toTypedArray())
} else {
_watchlistOrderStore.set(*(_watchlistOrderStore.values + listOf(video.url)).toTypedArray())
}
_watchlistOrderStore.save()
_watchlistOrderStore.save();
}
onWatchLaterChanged.emit();
if (isUserInteraction) {
if(isUserInteraction) {
val now = OffsetDateTime.now();
_watchLaterAdds.setAndSave(video.url, now);
broadcastWatchLaterAddition(video, now);
}
StateDownloads.instance.checkForOutdatedPlaylists();
return true;
return wasNew;
}
fun getLastPlayedPlaylist() : Playlist? {
@@ -8,7 +8,6 @@ import android.graphics.Matrix
import android.graphics.drawable.Animatable
import android.media.AudioManager
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.LayoutInflater
import android.view.MotionEvent
@@ -24,7 +23,6 @@ import androidx.core.animation.doOnStart
import androidx.core.view.GestureDetectorCompat
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
@@ -70,8 +68,6 @@ class GestureControlView : LinearLayout {
private val _progressSound: CircularProgressBar;
private var _animatorSound: ObjectAnimator? = null;
private var _brightnessFactor = 1.0f;
private var _originalBrightnessFactor = 1.0f;
private var _originalBrightnessMode: Int = 0;
private var _adjustingBrightness: Boolean = false;
private val _layoutControlsBrightness: FrameLayout;
private val _progressBrightness: CircularProgressBar;
@@ -110,6 +106,7 @@ class GestureControlView : LinearLayout {
val onSeek = Event1<Long>();
val onBrightnessAdjusted = Event1<Float>();
val onBrightnessCleared = Event0();
val onPan = Event2<Float, Float>();
val onZoom = Event1<Float>();
val onSoundAdjusted = Event1<Float>();
@@ -781,60 +778,21 @@ class GestureControlView : LinearLayout {
_animatorBrightness?.start();
}
fun saveBrightness() {
try {
_originalBrightnessMode = android.provider.Settings.System.getInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE)
val brightness = android.provider.Settings.System.getInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS)
_brightnessFactor = brightness / 255.0f;
Log.i(TAG, "Starting brightness brightness: $brightness, _brightnessFactor: $_brightnessFactor, _originalBrightnessMode: $_originalBrightnessMode")
_originalBrightnessFactor = _brightnessFactor
} catch (e: Throwable) {
Settings.instance.gestureControls.useSystemBrightness = false
Settings.instance.save()
UIDialogs.toast(context, "useSystemBrightness disabled due to an error")
}
}
fun restoreBrightness() {
if (Settings.instance.gestureControls.restoreSystemBrightness) {
onBrightnessAdjusted.emit(_originalBrightnessFactor)
if (android.provider.Settings.System.canWrite(context)) {
Log.i(TAG, "Restoring system brightness mode _originalBrightnessMode: $_originalBrightnessMode")
android.provider.Settings.System.putInt(
context.contentResolver,
android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE,
_originalBrightnessMode
)
}
}
}
fun setFullscreen(isFullScreen: Boolean) {
resetZoomPan()
if (isFullScreen) {
if (Settings.instance.gestureControls.useSystemBrightness) {
saveBrightness()
}
onBrightnessCleared.emit()
_brightnessFactor = 1.0f
if (isFullScreen) {
if (Settings.instance.gestureControls.useSystemVolume) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
_soundFactor = currentVolume.toFloat() / maxVolume.toFloat()
}
onBrightnessAdjusted.emit(_brightnessFactor);
onSoundAdjusted.emit(_soundFactor);
} else {
if (Settings.instance.gestureControls.useSystemBrightness) {
restoreBrightness()
} else {
onBrightnessAdjusted.emit(1.0f);
}
//onSoundAdjusted.emit(1.0f);
stopAdjustingBrightness();
stopAdjustingSound();
@@ -13,7 +13,6 @@ import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import androidx.core.view.isVisible
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event0
@@ -43,14 +42,10 @@ class SlideUpMenuOverlay : RelativeLayout {
constructor(context: Context, parent: ViewGroup, titleText: String, okText: String?, animated: Boolean, items: List<View>, hideButtons: Boolean = false): super(context){
init(animated, okText);
_container = parent;
_container!!.removeAllViews();
_container!!.addView(this);
if (_container!!.isVisible) {
isVisible = true
_viewBackground.alpha = 1.0f;
_viewOverlayContainer.translationY = 0.0f;
if(!_container!!.children.contains(this)) {
_container!!.removeAllViews();
_container!!.addView(this);
}
_textTitle.text = titleText;
groupItems = items;
@@ -61,12 +56,6 @@ class SlideUpMenuOverlay : RelativeLayout {
}
setItems(items);
if (!isVisible) {
_viewOverlayContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_viewOverlayContainer.translationY = _viewOverlayContainer.measuredHeight.toFloat()
_viewBackground.alpha = 0f;
}
}
@@ -157,9 +146,16 @@ class SlideUpMenuOverlay : RelativeLayout {
}
isVisible = true;
_container?.visibility = View.VISIBLE;
_container?.post {
_container?.visibility = View.VISIBLE;
_container?.bringToFront();
}
if (_animated) {
_viewOverlayContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
_viewOverlayContainer.translationY = _viewOverlayContainer.measuredHeight.toFloat()
_viewBackground.alpha = 0f;
val animations = arrayListOf<Animator>();
animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(ANIMATION_DURATION_MS));
animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(ANIMATION_DURATION_MS));
@@ -13,6 +13,7 @@ import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.TextView
@@ -40,6 +41,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.formatDuration
import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlayer
@@ -62,6 +64,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
var isFullScreen: Boolean = false
private set;
var fragment: VideoDetailFragment? = null
//Views
private val _root: ConstraintLayout;
private val _videoView: PlayerView;
@@ -283,17 +287,25 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
};
gestureControl.onToggleFullscreen.subscribe { setFullScreen(!isFullScreen) };
gestureControl.onBrightnessAdjusted.subscribe {
if (Settings.instance.gestureControls.useSystemBrightness) {
setSystemBrightness(it)
if (Settings.instance.gestureControls.controlScreenBrightness) {
setScreenBrightness(it)
} else {
setBrightnessOverlay(it)
if (it == 1.0f) {
_overlay_brightness.visibility = View.GONE;
} else {
_overlay_brightness.visibility = View.VISIBLE;
_overlay_brightness.setBackgroundColor(Color.valueOf(0.0f, 0.0f, 0.0f, (1.0f - it)).toArgb());
}
}
};
}
gestureControl.onBrightnessCleared.subscribe {
if (Settings.instance.gestureControls.controlScreenBrightness) {
clearCustomScreenBrightness()
} else {
setBrightnessOverlay(1.0f)
_overlay_brightness.visibility = View.GONE
}
}
gestureControl.onPan.subscribe { x, y ->
_videoView.translationX = x
_videoView.translationY = y
@@ -476,33 +488,26 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
}
}
private fun setBrightnessOverlay(brightness: Float) {
_overlay_brightness.setBackgroundColor(
Color.valueOf(0.0f, 0.0f, 0.0f, (1.0f - brightness)).toArgb()
)
}
private fun updateAutoplayButton() {
_control_autoplay.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
_control_autoplay_fullscreen.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
}
private fun setSystemBrightness(brightness: Float) {
Log.i(TAG, "setSystemBrightness $brightness")
if (android.provider.Settings.System.canWrite(context)) {
Log.i(TAG, "setSystemBrightness canWrite $brightness")
android.provider.Settings.System.putInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
android.provider.Settings.System.putInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS, (brightness * 255.0f).toInt().coerceAtLeast(1).coerceAtMost(255));
} else if (!_promptedForPermissions) {
Log.i(TAG, "setSystemBrightness prompt $brightness")
_promptedForPermissions = true
UIDialogs.showConfirmationDialog(context, "System brightness controls require explicit permission", action = {
openAndroidPermissionsMenu()
})
} else {
Log.i(TAG, "setSystemBrightness no permission?")
//No permissions but already prompted, ignore
}
private fun clearCustomScreenBrightness() {
setScreenBrightness(null)
}
private fun openAndroidPermissionsMenu() {
val intent = Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.setData(Uri.parse("package:" + context.packageName))
context.startActivity(intent)
private fun setScreenBrightness(brightness: Float?) {
val layoutParams: WindowManager.LayoutParams? = fragment?.activity?.window?.attributes
layoutParams?.screenBrightness =
brightness ?: WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
fragment?.activity?.window?.attributes = layoutParams
}
fun updateNextPrevious() {
+2 -3
View File
@@ -8,7 +8,7 @@
android:orientation="vertical"
android:paddingTop="10dp"
android:animateLayoutChanges="true">
<androidx.core.widget.NestedScrollView
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
@@ -152,14 +152,13 @@
android:id="@+id/button_add_sources"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
app:buttonIcon="@drawable/ic_explore"
app:buttonText="Add Sources"
app:buttonSubText="Install new sources to see more content."
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</ScrollView>
</LinearLayout>
-1
View File
@@ -3,5 +3,4 @@
<dimen name="minimized_player_max_width">500dp</dimen>
<dimen name="app_bar_height">200dp</dimen>
<integer name="column_width_dp">400</integer>
<integer name="smallest_width_dp">600</integer>
</resources>
+2 -9
View File
@@ -300,7 +300,6 @@
<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_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="auto_update">Auto Update</string>
<string name="always_allow_reverse_landscape_auto_rotate">Always allow reverse landscape auto-rotate</string>
@@ -349,7 +348,6 @@
<string name="default_audio_quality">Default Audio Quality</string>
<string name="default_playback_speed">Default Playback Speed</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="download_when">Download when</string>
<string name="enable_video_cache">Enable Video Cache</string>
@@ -399,10 +397,8 @@
<string name="brightness_slider_descr">Enable slide gesture to change brightness</string>
<string name="toggle_full_screen">Toggle full screen</string>
<string name="toggle_full_screen_descr">Enable swipe gesture to toggle full screen</string>
<string name="system_brightness">System brightness</string>
<string name="system_brightness_descr">Gesture controls adjust system brightness</string>
<string name="restore_system_brightness">Restore system brightness</string>
<string name="restore_system_brightness_descr">Restore system brightness when exiting full screen</string>
<string name="screen_brightness">Control screen brightness</string>
<string name="screen_brightness_desc">Gesture controls adjust the device screen brightness instead of an overlay filter</string>
<string name="zoom_option">Enable zoom</string>
<string name="zoom_option_descr">Enable two finger pinch zoom gesture</string>
<string name="pan_option">Enable pan</string>
@@ -470,9 +466,6 @@
<string name="playlist_delete_confirmation_description">Show confirmation dialog when deleting media from a playlist</string>
<string name="playlist_allow_dups">Allow duplicate playlist videos</string>
<string name="playlist_allow_dups_description">Allow adding duplicate videos to playlists</string>
<string name="add_to_beginning_of_watch_later">Add new videos to the beginning of Watch Later</string>
<string name="add_to_beginning_description">When adding videos to Watch Later add them to the beginning of the list instead of the end</string>
<string name="already_in_watch_later">Already in watch later</string>
<string name="enable_polycentric">Enable Polycentric</string>
<string name="polycentric_local_cache">Enable Polycentric Local Caching</string>
<string name="polycentric_local_cache_description">Caches polycentric results on-device to reduce load times, changing requires app reboot</string>