Merge pull request #5934 from Bnyro/master

feat: preference to force maximum audio quality
This commit is contained in:
Bnyro 2024-04-23 15:20:45 +02:00 committed by GitHub
commit faecb1dceb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 89 additions and 44 deletions

View File

@ -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) {
@ -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
@ -772,7 +801,7 @@ object PlayerHelper {
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
}

View File

@ -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)

View File

@ -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)
@ -1349,7 +1355,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
this.setPreferredVideoMimeType(mimeType)
}
}
PlayerHelper.applyPreferredAudioQuality(requireContext(), trackSelector)
}
/**

View File

@ -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">

View File

@ -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>