Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: preference to force maximum audio quality #5934

Merged
merged 1 commit into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 64 additions & 35 deletions app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt
Original file line number Diff line number Diff line change
@@ -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

Check failure on line 9 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unused import Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:9:1: error: Unused import (standard:no-unused-imports)
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
Expand All @@ -19,7 +20,9 @@
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
Expand All @@ -45,14 +48,14 @@
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"
Expand Down Expand Up @@ -99,7 +102,7 @@
/**
* 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) {
Expand Down Expand Up @@ -339,15 +342,15 @@

val playAutomatically: Boolean
get() = PreferenceHelper.getBoolean(
PreferenceKeys.PLAY_AUTOMATICALLY,
true
)
PreferenceKeys.PLAY_AUTOMATICALLY,
true

Check failure on line 346 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Missing trailing comma before ")" Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:346:17: error: Missing trailing comma before ")" (standard:trailing-comma-on-call-site)
)

val disablePipedProxy: Boolean
get() = PreferenceHelper.getBoolean(
PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY,
false
)
PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY,
false

Check failure on line 352 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Missing trailing comma before ")" Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:352:18: error: Missing trailing comma before ")" (standard:trailing-comma-on-call-site)
)

fun shouldPlayNextVideo(isPlaylist: Boolean = false): Boolean {
// if there is no next video, it obviously should not be played
Expand All @@ -356,11 +359,11 @@
}

return autoPlayEnabled || (
isPlaylist && PreferenceHelper.getBoolean(
PreferenceKeys.AUTOPLAY_PLAYLISTS,
false
)
)
isPlaylist && PreferenceHelper.getBoolean(

Check failure on line 362 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected indentation (16) (should be 12) Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:362:1: error: Unexpected indentation (16) (should be 12) (standard:indent)

Check failure on line 362 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 A multiline expression should start on a new line Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:362:31: error: A multiline expression should start on a new line (standard:multiline-expression-wrapping)
PreferenceKeys.AUTOPLAY_PLAYLISTS,

Check failure on line 363 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected indentation (20) (should be 16) Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:363:1: error: Unexpected indentation (20) (should be 16) (standard:indent)
false

Check failure on line 364 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected indentation (20) (should be 16) Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:364:1: error: Unexpected indentation (20) (should be 16) (standard:indent)

Check failure on line 364 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Missing trailing comma before ")" Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:364:26: error: Missing trailing comma before ")" (standard:trailing-comma-on-call-site)
)

Check failure on line 365 in app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected indentation (16) (should be 12) Raw Output: app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt:365:1: error: Unexpected indentation (16) (should be 12) (standard:indent)
)
}

private val handleAudioFocus
Expand All @@ -382,19 +385,44 @@
.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)
}
}
}
Expand All @@ -412,7 +440,8 @@
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)
Expand Down Expand Up @@ -468,7 +497,7 @@
/**
* 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,
Expand Down Expand Up @@ -501,7 +530,7 @@
/**
* 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
Expand All @@ -518,7 +547,7 @@
/**
* 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
Expand Down Expand Up @@ -767,12 +796,12 @@
*/
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(
Expand Down Expand Up @@ -812,12 +841,12 @@
}

PlayerEvent.Forward -> {
player.seekBy(PlayerHelper.seekIncrement)
player.seekBy(seekIncrement)
true
}

PlayerEvent.Rewind -> {
player.seekBy(-PlayerHelper.seekIncrement)
player.seekBy(-seekIncrement)
true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) }
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -1349,7 +1355,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
this.setPreferredVideoMimeType(mimeType)
}
}
PlayerHelper.applyPreferredAudioQuality(requireContext(), trackSelector)
}

/**
Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/array.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading