feat: Quality change in player

This commit is contained in:
nomadics9 2024-07-14 01:38:34 +03:00
parent 2253780903
commit ab090a01d7
7 changed files with 204 additions and 0 deletions

View file

@ -1,5 +1,6 @@
package com.nomadics9.ananas
import android.app.AlertDialog
import android.app.AppOpsManager
import android.app.PictureInPictureParams
import android.content.Context
@ -86,6 +87,12 @@ class PlayerActivity : BasePlayerActivity() {
binding = ActivityPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
val changeQualityButton: Button = findViewById(R.id.btnChangeQuality)
changeQualityButton.setOnClickListener {
showQualitySelectionDialog()
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding.playerView.player = viewModel.player
@ -418,6 +425,19 @@ class PlayerActivity : BasePlayerActivity() {
} catch (_: IllegalArgumentException) { }
}
private fun showQualitySelectionDialog() {
val qualities = arrayOf("1080p", "720p", "480p", "360p")
AlertDialog.Builder(this)
.setTitle("Select Video Quality")
.setItems(qualities) { _, which ->
val selectedQuality = qualities[which]
viewModel.changeVideoQuality(selectedQuality)
}
.show()
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration,

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Quality" />
<Button
android:id="@+id/btnQuality1080"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1080p" />
<Button
android:id="@+id/btnQuality720"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="720p" />
<Button
android:id="@+id/btnQuality480"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="480p" />
<Button
android:id="@+id/btnQuality360"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="360p" />
</LinearLayout>

View file

@ -10,6 +10,7 @@
android:layout_height="match_parent"
android:layout_gravity="center">
<!-- Video surface will be inserted as the first child of the content frame. -->
<View
@ -89,6 +90,12 @@
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/btnChangeQuality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change Quality" />
<Button
android:id="@+id/btn_skip_intro"
style="@style/Widget.Material3.Button.Icon"

View file

@ -112,4 +112,6 @@ interface JellyfinRepository {
suspend fun getDownloads(): List<FindroidItem>
fun getUserId(): UUID
fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int?, Int?>
}

View file

@ -564,4 +564,14 @@ class JellyfinRepositoryImpl(
override fun getUserId(): UUID {
return jellyfinApi.userId!!
}
override fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int?, Int?> {
return when (transcodeResolution) {
1080 -> 14616000 to 384000
720 -> 7616000 to 384000
480 -> 2616000 to 384000
360 -> 292000 to 128000
else -> null to null
}
}
}

View file

@ -285,4 +285,8 @@ class JellyfinRepositoryOfflineImpl(
override fun getUserId(): UUID {
return jellyfinApi.userId!!
}
override fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int?, Int?> {
TODO("Not yet implemented")
}
}

View file

@ -3,6 +3,7 @@ package com.nomadics9.ananas.viewmodels
import android.app.Application
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.SavedStateHandle
@ -20,6 +21,7 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import dagger.hilt.android.lifecycle.HiltViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.api.JellyfinApi
import com.nomadics9.ananas.models.FindroidSegment
import com.nomadics9.ananas.models.PlayerChapter
import com.nomadics9.ananas.models.PlayerItem
@ -38,6 +40,16 @@ import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.ClientCapabilitiesDto
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.DirectPlayProfile
import org.jellyfin.sdk.model.api.DlnaProfileType
import org.jellyfin.sdk.model.api.MediaStreamProtocol
import org.jellyfin.sdk.model.api.PlaybackInfoDto
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
import org.jellyfin.sdk.model.api.SubtitleProfile
import org.jellyfin.sdk.model.api.TranscodeSeekInfo
import org.jellyfin.sdk.model.api.TranscodingProfile
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@ -49,6 +61,7 @@ class PlayerActivityViewModel
constructor(
private val application: Application,
private val jellyfinRepository: JellyfinRepository,
private val jellyfinApi: JellyfinApi,
private val appPreferences: AppPreferences,
private val savedStateHandle: SavedStateHandle,
) : ViewModel(), Player.Listener {
@ -469,6 +482,118 @@ constructor(
super.onIsPlayingChanged(isPlaying)
eventsChannel.trySend(PlayerEvents.IsPlayingChanged(isPlaying))
}
private fun getTranscodeResolutions(preferredQuality: String): Int {
return when (preferredQuality) {
"1080p" -> 1080
"720p" -> 720
"480p" -> 480
"360p" -> 360
else -> 1080
}
}
fun changeVideoQuality(quality: String) {
val mediaId = player.currentMediaItem?.mediaId ?: return
val itemId = UUID.fromString(mediaId)
//val playerItem = playerItemMap[itemId] ?: return
val currentItem = items.firstOrNull { it.itemId.toString() == mediaId } ?: return
val currentPosition = player.currentPosition
viewModelScope.launch {
try {
val transcodingResolution = getTranscodeResolutions(quality)
val (videoBitRate, audioBitRate) = jellyfinRepository.getVideoTranscodeBitRate(transcodingResolution)
if (transcodingResolution != null) {
val deviceProfile = ClientCapabilitiesDto(
supportedCommands = emptyList(),
playableMediaTypes = emptyList(),
supportsMediaControl = true,
supportsPersistentIdentifier = true,
deviceProfile = DeviceProfile(
name = "Ananas User",
id = jellyfinRepository.getUserId().toString(),
maxStaticBitrate = 1_000_000_000,
maxStreamingBitrate = 1_000_000_000,
codecProfiles = emptyList(),
containerProfiles = emptyList(),
directPlayProfiles = listOf(
DirectPlayProfile(type = DlnaProfileType.VIDEO),
DirectPlayProfile(type = DlnaProfileType.AUDIO),
),
transcodingProfiles = listOf(
TranscodingProfile(
container = "ts",
protocol = MediaStreamProtocol.HLS,
audioCodec = "aac",
videoCodec = "hevc",
type = DlnaProfileType.VIDEO,
conditions = emptyList(),
copyTimestamps = true,
enableSubtitlesInManifest = true,
transcodeSeekInfo = TranscodeSeekInfo.AUTO,
)
),
subtitleProfiles = listOf(
SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL),
),
)
)
val playbackInfo =
jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
itemId,
PlaybackInfoDto(
userId = jellyfinApi.userId!!,
enableTranscoding = true,
enableDirectPlay = true,
enableDirectStream = true,
autoOpenLiveStream = true,
deviceProfile = deviceProfile.deviceProfile,
maxStreamingBitrate = videoBitRate,
),
)
val playSessionId = playbackInfo.content.playSessionId
val mediaSource = playbackInfo.content.mediaSources.firstOrNull()
val transcodingUrl = mediaSource?.transcodingUrl
// URL METHOD
val baseUrl = jellyfinApi.api.baseUrl
val cleanBaseUrl = baseUrl?.removePrefix("http://")?.removePrefix("https://")
val newUri = Uri.parse(transcodingUrl).buildUpon()
.scheme("https")
.authority(cleanBaseUrl)
//.appendQueryParameter("ffmpegTranscoding", "true")
.appendQueryParameter("maxVideoBitrate", videoBitRate.toString())
.appendQueryParameter("TranscodeReasons", "ContainerBitrateExceedsLimit")
.appendQueryParameter("static", "false")
.appendQueryParameter("maxHeight", videoBitRate.toString())
.appendQueryParameter("PlaySessionId", playSessionId)
.build()
val mediaItemBuilder = MediaItem.Builder()
.setMediaId(currentItem.itemId.toString())
.setUri(newUri)
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(currentItem.name)
.build(),
)
//.setSubtitleConfigurations(player.currentMediaItem!!.subtitleConfigurations)
player.setMediaItem(mediaItemBuilder.build())
player.prepare()
player.seekTo(currentPosition)
player.play()
//isQualityChangeInProgress = true
}else if (transcodingResolution == 1080) {
jellyfinRepository.getStreamUrl(itemId, currentItem.mediaSourceId)
}
} catch (e: Exception) {
Timber.e(e)
}
}
}
}
sealed interface PlayerEvents {