mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-13 13:50:30 +05:30
Merge pull request #5934 from Bnyro/master
feat: preference to force maximum audio quality
This commit is contained in:
commit
faecb1dceb
@ -1,14 +1,15 @@
|
||||
package com.github.libretube.helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.accessibility.CaptioningManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.RemoteActionCompat
|
||||
@ -19,7 +20,9 @@ import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.cronet.CronetDataSource
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
@ -45,14 +48,14 @@ import com.github.libretube.extensions.updateParameters
|
||||
import com.github.libretube.obj.VideoStats
|
||||
import com.github.libretube.util.PlayingQueue
|
||||
import com.github.libretube.util.TextUtils
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object PlayerHelper {
|
||||
private const val ACTION_MEDIA_CONTROL = "media_control"
|
||||
@ -99,7 +102,7 @@ object PlayerHelper {
|
||||
/**
|
||||
* Get the system's default captions style
|
||||
*/
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun getCaptionStyle(context: Context): CaptionStyleCompat {
|
||||
val captioningManager = context.getSystemService<CaptioningManager>()!!
|
||||
return if (!captioningManager.isEnabled) {
|
||||
@ -339,15 +342,15 @@ object PlayerHelper {
|
||||
|
||||
val playAutomatically: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PLAY_AUTOMATICALLY,
|
||||
true
|
||||
)
|
||||
PreferenceKeys.PLAY_AUTOMATICALLY,
|
||||
true
|
||||
)
|
||||
|
||||
val disablePipedProxy: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY,
|
||||
false
|
||||
)
|
||||
PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY,
|
||||
false
|
||||
)
|
||||
|
||||
fun shouldPlayNextVideo(isPlaylist: Boolean = false): Boolean {
|
||||
// if there is no next video, it obviously should not be played
|
||||
@ -356,11 +359,11 @@ object PlayerHelper {
|
||||
}
|
||||
|
||||
return autoPlayEnabled || (
|
||||
isPlaylist && PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTOPLAY_PLAYLISTS,
|
||||
false
|
||||
)
|
||||
)
|
||||
isPlaylist && PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTOPLAY_PLAYLISTS,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val handleAudioFocus
|
||||
@ -382,19 +385,44 @@ object PlayerHelper {
|
||||
.toIntOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the preferred audio quality: auto or worst
|
||||
*/
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun applyPreferredAudioQuality(context: Context, trackSelector: DefaultTrackSelector) {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun setPreferredAudioQuality(
|
||||
context: Context,
|
||||
player: Player,
|
||||
trackSelector: DefaultTrackSelector
|
||||
) {
|
||||
val prefKey = if (NetworkHelper.isNetworkMetered(context)) {
|
||||
PreferenceKeys.PLAYER_AUDIO_QUALITY_MOBILE
|
||||
} else {
|
||||
PreferenceKeys.PLAYER_AUDIO_QUALITY
|
||||
}
|
||||
when (PreferenceHelper.getString(prefKey, "auto")) {
|
||||
"worst" -> trackSelector.updateParameters {
|
||||
setMaxAudioBitrate(1)
|
||||
|
||||
val qualityPref = PreferenceHelper.getString(prefKey, "auto")
|
||||
if (qualityPref == "auto") return
|
||||
|
||||
// multiple groups due to different possible audio languages
|
||||
val audioTrackGroups = player.currentTracks.groups
|
||||
.filter { it.type == C.TRACK_TYPE_AUDIO }
|
||||
|
||||
for (audioTrackGroup in audioTrackGroups) {
|
||||
// find the best audio bitrate
|
||||
val streams = (0 until audioTrackGroup.length).map { index ->
|
||||
index to audioTrackGroup.getTrackFormat(index).bitrate
|
||||
}
|
||||
|
||||
// if no bitrate info is available, fallback to the
|
||||
// - first stream for lowest quality
|
||||
// - last stream for highest quality
|
||||
val streamIndex = if (qualityPref == "best") {
|
||||
streams.maxByOrNull { it.second }?.takeIf { it.second != -1 }?.first
|
||||
?: (streams.size - 1)
|
||||
} else {
|
||||
streams.minByOrNull { it.second }?.takeIf { it.second != -1 }?.first ?: 0
|
||||
}
|
||||
|
||||
trackSelector.updateParameters {
|
||||
val override = TrackSelectionOverride(audioTrackGroup.mediaTrackGroup, streamIndex)
|
||||
setOverrideForType(override)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -412,7 +440,8 @@ object PlayerHelper {
|
||||
val intent = Intent(getIntentActionName(activity))
|
||||
.setPackage(activity.packageName)
|
||||
.putExtra(CONTROL_TYPE, event)
|
||||
val pendingIntent = PendingIntentCompat.getBroadcast(activity, event.ordinal, intent, 0, false)!!
|
||||
val pendingIntent =
|
||||
PendingIntentCompat.getBroadcast(activity, event.ordinal, intent, 0, false)!!
|
||||
|
||||
val text = activity.getString(title)
|
||||
val icon = IconCompat.createWithResource(activity, id)
|
||||
@ -468,7 +497,7 @@ object PlayerHelper {
|
||||
/**
|
||||
* Create a basic player, that is used for all types of playback situations inside the app
|
||||
*/
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun createPlayer(
|
||||
context: Context,
|
||||
trackSelector: DefaultTrackSelector,
|
||||
@ -501,7 +530,7 @@ object PlayerHelper {
|
||||
/**
|
||||
* Get the load controls for the player (buffering, etc)
|
||||
*/
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun getLoadControl(): LoadControl {
|
||||
return DefaultLoadControl.Builder()
|
||||
// cache the last three minutes
|
||||
@ -518,7 +547,7 @@ object PlayerHelper {
|
||||
/**
|
||||
* Load playback parameters such as speed and skip silence
|
||||
*/
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun ExoPlayer.loadPlaybackParams(isBackgroundMode: Boolean = false): ExoPlayer {
|
||||
skipSilenceEnabled = skipSilence
|
||||
val speed = if (isBackgroundMode) backgroundSpeed else playbackSpeed
|
||||
@ -767,12 +796,12 @@ object PlayerHelper {
|
||||
*/
|
||||
fun haveAudioTrackRoleFlagSet(@C.RoleFlags roleFlags: Int): Boolean {
|
||||
return isFlagSet(roleFlags, C.ROLE_FLAG_DESCRIBES_VIDEO) ||
|
||||
isFlagSet(roleFlags, C.ROLE_FLAG_DUB) ||
|
||||
isFlagSet(roleFlags, C.ROLE_FLAG_MAIN) ||
|
||||
isFlagSet(roleFlags, C.ROLE_FLAG_ALTERNATE)
|
||||
isFlagSet(roleFlags, C.ROLE_FLAG_DUB) ||
|
||||
isFlagSet(roleFlags, C.ROLE_FLAG_MAIN) ||
|
||||
isFlagSet(roleFlags, C.ROLE_FLAG_ALTERNATE)
|
||||
}
|
||||
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun getVideoStats(player: ExoPlayer, videoId: String): VideoStats {
|
||||
val videoInfo = "${player.videoFormat?.codecs.orEmpty()} ${
|
||||
TextUtils.formatBitrate(
|
||||
@ -812,12 +841,12 @@ object PlayerHelper {
|
||||
}
|
||||
|
||||
PlayerEvent.Forward -> {
|
||||
player.seekBy(PlayerHelper.seekIncrement)
|
||||
player.seekBy(seekIncrement)
|
||||
true
|
||||
}
|
||||
|
||||
PlayerEvent.Rewind -> {
|
||||
player.seekBy(-PlayerHelper.seekIncrement)
|
||||
player.seekBy(-seekIncrement)
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,7 @@ class OnlinePlayerService : LifecycleService() {
|
||||
* The [ExoPlayer] player. Followed tutorial [here](https://developer.android.com/codelabs/exoplayer-intro)
|
||||
*/
|
||||
var player: ExoPlayer? = null
|
||||
private var trackSelector: DefaultTrackSelector? = null
|
||||
private var isTransitioning = true
|
||||
|
||||
/**
|
||||
@ -163,6 +164,14 @@ class OnlinePlayerService : LifecycleService() {
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEvents(player: Player, events: Player.Events) {
|
||||
super.onEvents(player, events)
|
||||
|
||||
if (events.contains(Player.EVENT_TRACKS_CHANGED)) {
|
||||
PlayerHelper.setPreferredAudioQuality(this@OnlinePlayerService, player, trackSelector ?: return)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val playerActionReceiver = object : BroadcastReceiver() {
|
||||
@ -321,13 +330,12 @@ class OnlinePlayerService : LifecycleService() {
|
||||
private fun initializePlayer() {
|
||||
if (player != null) return
|
||||
|
||||
val trackSelector = DefaultTrackSelector(this)
|
||||
PlayerHelper.applyPreferredAudioQuality(this, trackSelector)
|
||||
trackSelector.updateParameters {
|
||||
trackSelector = DefaultTrackSelector(this)
|
||||
trackSelector!!.updateParameters {
|
||||
setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, true)
|
||||
}
|
||||
|
||||
player = PlayerHelper.createPlayer(this, trackSelector, true)
|
||||
player = PlayerHelper.createPlayer(this, trackSelector!!, true)
|
||||
// prevent android from putting LibreTube to sleep when locked
|
||||
player!!.setWakeMode(WAKE_MODE_NETWORK)
|
||||
|
||||
|
@ -273,6 +273,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
override fun onEvents(player: Player, events: Player.Events) {
|
||||
updateDisplayedDuration()
|
||||
super.onEvents(player, events)
|
||||
|
||||
if (events.containsAny(
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
Player.EVENT_IS_PLAYING_CHANGED,
|
||||
@ -281,6 +282,10 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
) {
|
||||
updatePlayPauseButton()
|
||||
}
|
||||
|
||||
if (events.contains(Player.EVENT_TRACKS_CHANGED)) {
|
||||
PlayerHelper.setPreferredAudioQuality(requireContext(), exoPlayer, trackSelector)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
@ -586,7 +591,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
playOnBackground()
|
||||
}
|
||||
|
||||
binding.relPlayerPip.isVisible = PictureInPictureCompat.isPictureInPictureAvailable(requireContext())
|
||||
binding.relPlayerPip.isVisible =
|
||||
PictureInPictureCompat.isPictureInPictureAvailable(requireContext())
|
||||
|
||||
binding.relPlayerPip.setOnClickListener {
|
||||
PictureInPictureCompat.enterPictureInPictureMode(requireActivity(), pipParams)
|
||||
@ -916,7 +922,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
|
||||
val videoStream = streams.videoStreams.firstOrNull()
|
||||
isShort = PlayingQueue.getCurrent()?.isShort == true ||
|
||||
(videoStream?.height ?: 0) > (videoStream?.width ?: 0)
|
||||
(videoStream?.height ?: 0) > (videoStream?.width ?: 0)
|
||||
|
||||
PlayingQueue.setOnQueueTapListener { streamItem ->
|
||||
streamItem.url?.toID()?.let { playNextVideo(it) }
|
||||
@ -952,7 +958,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
if (binding.playerMotionLayout.progress != 1.0f) {
|
||||
// show controllers when not in picture in picture mode
|
||||
val inPipMode = PlayerHelper.pipEnabled &&
|
||||
PictureInPictureCompat.isInPictureInPictureMode(requireActivity())
|
||||
PictureInPictureCompat.isInPictureInPictureMode(requireActivity())
|
||||
if (!inPipMode) {
|
||||
binding.player.useController = true
|
||||
}
|
||||
@ -1349,7 +1355,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
this.setPreferredVideoMimeType(mimeType)
|
||||
}
|
||||
}
|
||||
PlayerHelper.applyPreferredAudioQuality(requireContext(), trackSelector)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1570,7 +1575,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
|
||||
private fun shouldStartPiP(): Boolean {
|
||||
return shouldUsePip() && exoPlayer.isPlaying &&
|
||||
!BackgroundHelper.isBackgroundServiceRunning(requireContext())
|
||||
!BackgroundHelper.isBackgroundServiceRunning(requireContext())
|
||||
}
|
||||
|
||||
private fun killPlayerFragment() {
|
||||
|
@ -265,11 +265,13 @@
|
||||
<string-array name="audioQuality">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/worst_quality</item>
|
||||
<item>@string/best_quality</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="audioQualityValues">
|
||||
<item>auto</item>
|
||||
<item>worst</item>
|
||||
<item>best</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="audioQualityBitrates">
|
||||
|
@ -234,6 +234,7 @@
|
||||
<string name="playerAudioFormat">Audio format for player</string>
|
||||
<string name="playerAudioQuality">Audio quality</string>
|
||||
<string name="worst_quality">Worst</string>
|
||||
<string name="best_quality">Best</string>
|
||||
<string name="default_subtitle_language">Subtitle language</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="notify_new_streams">Show notifications for new streams</string>
|
||||
|
Loading…
Reference in New Issue
Block a user