bugfixes: deviceId / code: New Enum VideoQuality

This commit is contained in:
nomadics9 2024-07-20 08:36:23 +03:00
parent ba580f8769
commit 6dded2e726
11 changed files with 121 additions and 109 deletions

View file

@ -45,6 +45,7 @@ import dev.jdtech.jellyfin.viewmodels.PlayerEvents
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
var isControlsLocked: Boolean = false var isControlsLocked: Boolean = false
@ -348,20 +349,44 @@ class PlayerActivity : BasePlayerActivity() {
} }
private fun showQualitySelectionDialog() { private fun showQualitySelectionDialog() {
val height = viewModel.getOriginalHeight() // TODO: rewrite getting height stuff I don't like that its only update after changing quality val height = viewModel.getOriginalHeight()
val qualities = when (height) { val qualityEntries = resources.getStringArray(CoreR.array.quality_entries).toList()
0 -> arrayOf("Auto", "Original - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps") val qualityValues = resources.getStringArray(CoreR.array.quality_values).toList()
in 1001..1999 -> arrayOf("Auto", "Original (1080p) - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps")
in 2000..3000 -> arrayOf("Auto", "Original (4K) - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps") // Map entries to values
else -> arrayOf("Auto", "Original - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps") val qualityMap = qualityEntries.zip(qualityValues).toMap()
}
val qualities: List<String> =
when (height) {
0 -> qualityEntries
in 1001..1999 ->
listOf(
qualityEntries[0],
"${qualityEntries[1]} (1080p)",
qualityEntries[2],
qualityEntries[3],
qualityEntries[4],
qualityEntries[5],
)
in 2000..3000 ->
listOf(
qualityEntries[0],
"${qualityEntries[1]} (4K)",
qualityEntries[2],
qualityEntries[3],
qualityEntries[4],
qualityEntries[5],
)
else -> qualityEntries
}
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle("Select Video Quality") .setTitle("Select Video Quality")
.setItems(qualities) { _, which -> .setItems(qualities.toTypedArray()) { _, which ->
val selectedQuality = qualities[which] val selectedQualityEntry = qualities[which]
viewModel.changeVideoQuality(selectedQuality) val selectedQualityValue =
} qualityMap.entries.find { it.key.contains(selectedQualityEntry.split(" ")[0]) }?.value ?: selectedQualityEntry
.show() viewModel.changeVideoQuality(selectedQualityValue)
}.show()
} }
override fun onPictureInPictureModeChanged( override fun onPictureInPictureModeChanged(

View file

@ -413,8 +413,8 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
} }
private fun createPickQualityDialog() { private fun createPickQualityDialog() {
val qualityEntries = resources.getStringArray(CoreR.array.quality_entries) val qualityEntries = resources.getStringArray(CoreR.array.download_quality_entries)
val qualityValues = resources.getStringArray(CoreR.array.quality_values) val qualityValues = resources.getStringArray(CoreR.array.download_quality_values)
val quality = appPreferences.downloadQuality val quality = appPreferences.downloadQuality
val currentQualityIndex = qualityValues.indexOf(quality) val currentQualityIndex = qualityValues.indexOf(quality)
var selectedQuality = quality var selectedQuality = quality

View file

@ -506,8 +506,8 @@ class MovieFragment : Fragment() {
} }
private fun createPickQualityDialog() { private fun createPickQualityDialog() {
val qualityEntries = resources.getStringArray(CoreR.array.quality_entries) val qualityEntries = resources.getStringArray(CoreR.array.download_quality_entries)
val qualityValues = resources.getStringArray(CoreR.array.quality_values) val qualityValues = resources.getStringArray(CoreR.array.download_quality_values)
val quality = appPreferences.downloadQuality val quality = appPreferences.downloadQuality
val currentQualityIndex = qualityValues.indexOf(quality) val currentQualityIndex = qualityValues.indexOf(quality)
var selectedQuality = quality var selectedQuality = quality

View file

@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.models.FindroidSource
import dev.jdtech.jellyfin.models.FindroidSources import dev.jdtech.jellyfin.models.FindroidSources
import dev.jdtech.jellyfin.models.FindroidTrickplayInfo import dev.jdtech.jellyfin.models.FindroidTrickplayInfo
import dev.jdtech.jellyfin.models.UiText import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.models.VideoQuality
import dev.jdtech.jellyfin.models.toFindroidEpisodeDto import dev.jdtech.jellyfin.models.toFindroidEpisodeDto
import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto
import dev.jdtech.jellyfin.models.toFindroidMovieDto import dev.jdtech.jellyfin.models.toFindroidMovieDto
@ -395,19 +396,12 @@ class DownloaderImpl(
itemId: UUID, itemId: UUID,
quality: String, quality: String,
): Uri? { ): Uri? {
val maxBitrate = val videoQuality = VideoQuality.fromString(quality)!!
when (quality) {
"720p" -> 2000000 // 2 Mbps
"480p" -> 1000000 // 1 Mbps
"360p" -> 800000 // 800Kbps
else -> 2000000
}
return try { return try {
val deviceProfile = val deviceProfile =
jellyfinRepository.buildDeviceProfile(maxBitrate, "mkv", EncodingContext.STATIC) jellyfinRepository.buildDeviceProfile(VideoQuality.getBitrate(videoQuality), "mkv", EncodingContext.STATIC)
val playbackInfo = val playbackInfo =
jellyfinRepository.getPostedPlaybackInfo(itemId, false, deviceProfile, maxBitrate) jellyfinRepository.getPostedPlaybackInfo(itemId, false, deviceProfile, VideoQuality.getBitrate(videoQuality))
val mediaSourceId = val mediaSourceId =
playbackInfo.content.mediaSources playbackInfo.content.mediaSources
.firstOrNull() .firstOrNull()
@ -420,36 +414,14 @@ class DownloaderImpl(
deviceId, deviceId,
mediaSourceId, mediaSourceId,
playSessionId, playSessionId,
maxBitrate, VideoQuality.getBitrate(videoQuality),
"ts", "ts",
VideoQuality.getQualityInt(videoQuality)
) )
val transcodeUri = buildTranscodeUri(downloadUrl, maxBitrate, quality) downloadUrl.toUri()
transcodeUri
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
} }
// TODO: I believe building upon the uri is not necessary anymore all is handled in the sdk api
private fun buildTranscodeUri(
transcodingUrl: String,
maxBitrate: Int,
quality: String,
): Uri {
val resolution =
when (quality) {
"720p" -> "720"
"480p" -> "480"
"360p" -> "360"
else -> "720"
}
return Uri
.parse(transcodingUrl)
.buildUpon()
.appendQueryParameter("MaxVideoHeight", resolution)
.appendQueryParameter("MaxVideoBitRate", maxBitrate.toString())
.appendQueryParameter("subtitleMethod", "External")
.build()
}
} }

View file

@ -26,13 +26,31 @@
<item>opensles</item> <item>opensles</item>
</string-array> </string-array>
<string-array name="quality_entries"> <string-array name="quality_entries">
<item>Auto</item>
<item>Original</item> <item>Original</item>
<item>720p - 2Mbps</item> <item>1080p - 8Mbps</item>
<item>480p - 1Mbps</item> <item>720p - 3Mbps</item>
<item>480p - 1.5Mbps</item>
<item>360p - 800Kbps</item> <item>360p - 800Kbps</item>
</string-array> </string-array>
<string-array name="quality_values"> <string-array name="quality_values">
<item>Auto</item>
<item>Original</item> <item>Original</item>
<item>1080p</item>
<item>720p</item>
<item>480p</item>
<item>360p</item>
</string-array>
<string-array name="download_quality_entries">
<item>Original</item>
<item>1080p - 8Mbps</item>
<item>720p - 3Mbps</item>
<item>480p - 1.5Mbps</item>
<item>360p - 800Kbps</item>
</string-array>
<string-array name="download_quality_values">
<item>Original</item>
<item>1080p</item>
<item>720p</item> <item>720p</item>
<item>480p</item> <item>480p</item>
<item>360p</item> <item>360p</item>

View file

@ -14,8 +14,8 @@
android:key="pref_downloads_quality" android:key="pref_downloads_quality"
android:title="Download Quality" android:title="Download Quality"
android:defaultValue="Original" android:defaultValue="Original"
android:entries="@array/quality_entries" android:entries="@array/download_quality_entries"
android:entryValues="@array/quality_values" android:entryValues="@array/download_quality_values"
android:summary="%s" /> android:summary="%s" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"

View file

@ -0,0 +1,25 @@
package dev.jdtech.jellyfin.models
enum class VideoQuality(
val bitrate: Int,
val qualityString: String,
val qualityInt: Int,
) {
PAuto(10000000, "Auto", 1080),
POriginal(1000000000, "Original", 1080),
P1080(8000000, "1080p", 1080),
P720(3000000, "720p", 720),
P480(1500000, "480p", 480),
P360(800000, "360p", 360),
;
companion object {
fun fromString(quality: String): VideoQuality? = entries.find { it.qualityString == quality }
fun getBitrate(quality: VideoQuality): Int = quality.bitrate
fun getQualityString(quality: VideoQuality): String = quality.qualityString
fun getQualityInt(quality: VideoQuality): Int = quality.qualityInt
}
}

View file

@ -143,8 +143,6 @@ interface JellyfinRepository {
suspend fun getDeviceId(): String suspend fun getDeviceId(): String
suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int>
suspend fun buildDeviceProfile( suspend fun buildDeviceProfile(
maxBitrate: Int, maxBitrate: Int,
container: String, container: String,
@ -156,10 +154,9 @@ interface JellyfinRepository {
deviceId: String, deviceId: String,
mediaSourceId: String, mediaSourceId: String,
playSessionId: String, playSessionId: String,
videoBitrate: videoBitrate: Int,
@Suppress("ktlint:standard:max-line-length")
Int,
container: String, container: String,
maxHeight: Int,
): String ): String
suspend fun getTranscodedVideoStream( suspend fun getTranscodedVideoStream(

View file

@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.FindroidSource
import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.Intro
import dev.jdtech.jellyfin.models.SortBy import dev.jdtech.jellyfin.models.SortBy
import dev.jdtech.jellyfin.models.VideoQuality
import dev.jdtech.jellyfin.models.toFindroidCollection import dev.jdtech.jellyfin.models.toFindroidCollection
import dev.jdtech.jellyfin.models.toFindroidEpisode import dev.jdtech.jellyfin.models.toFindroidEpisode
import dev.jdtech.jellyfin.models.toFindroidItem import dev.jdtech.jellyfin.models.toFindroidItem
@ -609,15 +610,6 @@ class JellyfinRepositoryImpl(
override fun getUserId(): UUID = jellyfinApi.userId!! override fun getUserId(): UUID = jellyfinApi.userId!!
override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int> =
when (transcodeResolution) {
1080 -> 8000000 to 384000 // Adjusted for personal can be other values
720 -> 2000000 to 384000 // 720p
480 -> 1000000 to 384000 // 480p
360 -> 800000 to 128000 // 360p
else -> 12000000 to 384000 // its adaptive but setting max here
}
override suspend fun buildDeviceProfile( override suspend fun buildDeviceProfile(
maxBitrate: Int, maxBitrate: Int,
container: String, container: String,
@ -631,7 +623,7 @@ class JellyfinRepositoryImpl(
supportsPersistentIdentifier = true, supportsPersistentIdentifier = true,
deviceProfile = deviceProfile =
DeviceProfile( DeviceProfile(
name = "AnanasUser", name = "FindroidUser",
id = getUserId().toString(), id = getUserId().toString(),
maxStaticBitrate = maxBitrate, maxStaticBitrate = maxBitrate,
maxStreamingBitrate = maxBitrate, maxStreamingBitrate = maxBitrate,
@ -648,8 +640,8 @@ class JellyfinRepositoryImpl(
container = container, container = container,
context = context, context = context,
protocol = MediaStreamProtocol.HLS, protocol = MediaStreamProtocol.HLS,
audioCodec = "aac,ac3,eac3", audioCodec = "aac",
videoCodec = "hevc,h264", videoCodec = "h264",
type = DlnaProfileType.VIDEO, type = DlnaProfileType.VIDEO,
conditions = conditions =
listOf( listOf(
@ -712,6 +704,7 @@ class JellyfinRepositoryImpl(
playSessionId: String, playSessionId: String,
videoBitrate: Int, videoBitrate: Int,
container: String, container: String,
maxHeight: Int,
): String { ): String {
val url = val url =
jellyfinApi.videosApi.getVideoStreamByContainerUrl( jellyfinApi.videosApi.getVideoStreamByContainerUrl(
@ -721,10 +714,11 @@ class JellyfinRepositoryImpl(
mediaSourceId = mediaSourceId, mediaSourceId = mediaSourceId,
playSessionId = playSessionId, playSessionId = playSessionId,
videoBitRate = videoBitrate, videoBitRate = videoBitrate,
audioBitRate = 384000, audioBitRate = 128000,
videoCodec = "hevc", videoCodec = "h264",
audioCodec = "aac,ac3,eac3", audioCodec = "aac",
container = container, container = container,
maxHeight = maxHeight,
startTimeTicks = 0, startTimeTicks = 0,
copyTimestamps = true, copyTimestamps = true,
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL,
@ -739,7 +733,7 @@ class JellyfinRepositoryImpl(
playSessionId: String, playSessionId: String,
videoBitrate: Int, videoBitrate: Int,
): String { ): String {
val isAuto = videoBitrate == 12000000 val isAuto = videoBitrate == VideoQuality.getBitrate(VideoQuality.PAuto)
val url = val url =
if (!isAuto) { if (!isAuto) {
jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl( jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl(
@ -750,9 +744,9 @@ class JellyfinRepositoryImpl(
playSessionId = playSessionId, playSessionId = playSessionId,
videoBitRate = videoBitrate, videoBitRate = videoBitrate,
enableAdaptiveBitrateStreaming = false, enableAdaptiveBitrateStreaming = false,
audioBitRate = 384000, // could also be passed with audioBitrate but i preferred not as its not much data anyways audioBitRate = 128000,
videoCodec = "hevc,h264", videoCodec = "h264",
audioCodec = "aac,ac3,eac3", audioCodec = "aac",
startTimeTicks = 0, startTimeTicks = 0,
copyTimestamps = true, copyTimestamps = true,
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL,
@ -768,8 +762,8 @@ class JellyfinRepositoryImpl(
mediaSourceId = mediaSourceId, mediaSourceId = mediaSourceId,
playSessionId = playSessionId, playSessionId = playSessionId,
enableAdaptiveBitrateStreaming = true, enableAdaptiveBitrateStreaming = true,
videoCodec = "hevc", videoCodec = "h264",
audioCodec = "aac,ac3,eac3", audioCodec = "aac",
startTimeTicks = 0, startTimeTicks = 0,
copyTimestamps = true, copyTimestamps = true,
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL,
@ -782,10 +776,7 @@ class JellyfinRepositoryImpl(
} }
override suspend fun getDeviceId(): String { override suspend fun getDeviceId(): String {
val devices = jellyfinApi.devicesApi.getDevices(getUserId()) return jellyfinApi.api.deviceInfo.id
return devices.content.items
?.firstOrNull()
?.id!!
} }
override suspend fun stopEncodingProcess(playSessionId: String) { override suspend fun stopEncodingProcess(playSessionId: String) {

View file

@ -330,10 +330,6 @@ class JellyfinRepositoryOfflineImpl(
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int> {
TODO("Not yet implemented")
}
override suspend fun buildDeviceProfile( override suspend fun buildDeviceProfile(
maxBitrate: Int, maxBitrate: Int,
container: String, container: String,
@ -349,6 +345,7 @@ class JellyfinRepositoryOfflineImpl(
playSessionId: String, playSessionId: String,
videoBitrate: Int, videoBitrate: Int,
container: String, container: String,
maxHeight: Int,
): String { ): String {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View file

@ -28,6 +28,7 @@ import dev.jdtech.jellyfin.models.Intro
import dev.jdtech.jellyfin.models.PlayerChapter import dev.jdtech.jellyfin.models.PlayerChapter
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.models.Trickplay import dev.jdtech.jellyfin.models.Trickplay
import dev.jdtech.jellyfin.models.VideoQuality
import dev.jdtech.jellyfin.mpv.MPVPlayer import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.player.video.R import dev.jdtech.jellyfin.player.video.R
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
@ -464,17 +465,6 @@ constructor(
eventsChannel.trySend(PlayerEvents.IsPlayingChanged(isPlaying)) eventsChannel.trySend(PlayerEvents.IsPlayingChanged(isPlaying))
} }
private fun getTranscodeResolutions(preferredQuality: String): Int {
return when (preferredQuality) {
"1080p" -> 1080 // TODO: 1080p this logic is based on 1080p being original
"720p - 2Mbps" -> 720
"480p - 1Mbps" -> 480
"360p - 800kbps" -> 360
"Auto" -> 1
else -> 1080 //default to Original
}
}
fun changeVideoQuality(quality: String) { fun changeVideoQuality(quality: String) {
val mediaId = player.currentMediaItem?.mediaId ?: return val mediaId = player.currentMediaItem?.mediaId ?: return
val currentItem = items.firstOrNull { it.itemId.toString() == mediaId } ?: return val currentItem = items.firstOrNull { it.itemId.toString() == mediaId } ?: return
@ -482,12 +472,9 @@ constructor(
viewModelScope.launch { viewModelScope.launch {
try { try {
val transcodingResolution = getTranscodeResolutions(quality) val videoQuality = VideoQuality.fromString(quality)!!
val (videoBitRate, audioBitRate) = jellyfinRepository.getVideoTranscodeBitRate( val deviceProfile = jellyfinRepository.buildDeviceProfile(VideoQuality.getBitrate(videoQuality), "mkv", EncodingContext.STREAMING)
transcodingResolution val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(currentItem.itemId,true,deviceProfile,VideoQuality.getBitrate(videoQuality))
)
val deviceProfile = jellyfinRepository.buildDeviceProfile(videoBitRate, "mkv", EncodingContext.STREAMING)
val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(currentItem.itemId,true,deviceProfile,videoBitRate)
val playSessionId = playbackInfo.content.playSessionId val playSessionId = playbackInfo.content.playSessionId
if (playSessionId != null) { if (playSessionId != null) {
jellyfinRepository.stopEncodingProcess(playSessionId) jellyfinRepository.stopEncodingProcess(playSessionId)
@ -537,18 +524,18 @@ constructor(
val allSubtitles = val allSubtitles =
if (transcodingResolution == 1080) { if (VideoQuality.getQualityString(videoQuality) == "Original") {
externalSubtitles externalSubtitles
}else { }else {
embeddedSubtitles.apply { addAll(externalSubtitles) } embeddedSubtitles.apply { addAll(externalSubtitles) }
} }
val url = if (transcodingResolution == 1080){ val url = if (VideoQuality.getQualityString(videoQuality) == "Original"){
jellyfinRepository.getStreamUrl(currentItem.itemId, currentItem.mediaSourceId, playSessionId) jellyfinRepository.getStreamUrl(currentItem.itemId, currentItem.mediaSourceId, playSessionId)
} else { } else {
val mediaSourceId = mediaSources[currentMediaItemIndex].id val mediaSourceId = mediaSources[currentMediaItemIndex].id
val deviceId = jellyfinRepository.getDeviceId() val deviceId = jellyfinRepository.getDeviceId()
val url = jellyfinRepository.getTranscodedVideoStream(currentItem.itemId, deviceId ,mediaSourceId, playSessionId!!, videoBitRate) val url = jellyfinRepository.getTranscodedVideoStream(currentItem.itemId, deviceId ,mediaSourceId, playSessionId!!, VideoQuality.getBitrate(videoQuality))
val uriBuilder = url.toUri().buildUpon() val uriBuilder = url.toUri().buildUpon()
val apiKey = jellyfinApi.api.accessToken // TODO: add in repo val apiKey = jellyfinApi.api.accessToken // TODO: add in repo
uriBuilder.appendQueryParameter("api_key",apiKey ) uriBuilder.appendQueryParameter("api_key",apiKey )