From 6dded2e72641868731d4716e6b35e906496b9799 Mon Sep 17 00:00:00 2001 From: nomadics9 Date: Sat, 20 Jul 2024 08:36:23 +0300 Subject: [PATCH] bugfixes: deviceId / code: New Enum VideoQuality --- .../dev/jdtech/jellyfin/PlayerActivity.kt | 49 ++++++++++++++----- .../fragments/EpisodeBottomSheetFragment.kt | 4 +- .../jellyfin/fragments/MovieFragment.kt | 4 +- .../jdtech/jellyfin/utils/DownloaderImpl.kt | 42 +++------------- core/src/main/res/values/string_arrays.xml | 22 ++++++++- .../res/xml/fragment_settings_downloads.xml | 4 +- .../jdtech/jellyfin/models/VideoQuality.kt | 25 ++++++++++ .../jellyfin/repository/JellyfinRepository.kt | 7 +-- .../repository/JellyfinRepositoryImpl.kt | 41 ++++++---------- .../JellyfinRepositoryOfflineImpl.kt | 5 +- .../viewmodels/PlayerActivityViewModel.kt | 27 +++------- 11 files changed, 121 insertions(+), 109 deletions(-) create mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/VideoQuality.kt diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 891170e8..1b4eb03f 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -45,6 +45,7 @@ import dev.jdtech.jellyfin.viewmodels.PlayerEvents import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import dev.jdtech.jellyfin.core.R as CoreR var isControlsLocked: Boolean = false @@ -348,20 +349,44 @@ class PlayerActivity : BasePlayerActivity() { } private fun showQualitySelectionDialog() { - val height = viewModel.getOriginalHeight() // TODO: rewrite getting height stuff I don't like that its only update after changing quality - val qualities = when (height) { - 0 -> arrayOf("Auto", "Original - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps") - 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") - else -> arrayOf("Auto", "Original - Max", "720p - 2Mbps", "480p - 1Mbps", "360p - 800kbps") - } + val height = viewModel.getOriginalHeight() + val qualityEntries = resources.getStringArray(CoreR.array.quality_entries).toList() + val qualityValues = resources.getStringArray(CoreR.array.quality_values).toList() + + // Map entries to values + val qualityMap = qualityEntries.zip(qualityValues).toMap() + + val qualities: List = + 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) .setTitle("Select Video Quality") - .setItems(qualities) { _, which -> - val selectedQuality = qualities[which] - viewModel.changeVideoQuality(selectedQuality) - } - .show() + .setItems(qualities.toTypedArray()) { _, which -> + val selectedQualityEntry = qualities[which] + val selectedQualityValue = + qualityMap.entries.find { it.key.contains(selectedQualityEntry.split(" ")[0]) }?.value ?: selectedQualityEntry + viewModel.changeVideoQuality(selectedQualityValue) + }.show() } override fun onPictureInPictureModeChanged( diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt index 925f70f3..83d13407 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt @@ -413,8 +413,8 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() { } private fun createPickQualityDialog() { - val qualityEntries = resources.getStringArray(CoreR.array.quality_entries) - val qualityValues = resources.getStringArray(CoreR.array.quality_values) + val qualityEntries = resources.getStringArray(CoreR.array.download_quality_entries) + val qualityValues = resources.getStringArray(CoreR.array.download_quality_values) val quality = appPreferences.downloadQuality val currentQualityIndex = qualityValues.indexOf(quality) var selectedQuality = quality diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt index b5aded7c..a70d4456 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt @@ -506,8 +506,8 @@ class MovieFragment : Fragment() { } private fun createPickQualityDialog() { - val qualityEntries = resources.getStringArray(CoreR.array.quality_entries) - val qualityValues = resources.getStringArray(CoreR.array.quality_values) + val qualityEntries = resources.getStringArray(CoreR.array.download_quality_entries) + val qualityValues = resources.getStringArray(CoreR.array.download_quality_values) val quality = appPreferences.downloadQuality val currentQualityIndex = qualityValues.indexOf(quality) var selectedQuality = quality diff --git a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt index 0a463d73..34ae76f0 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt @@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.FindroidSources import dev.jdtech.jellyfin.models.FindroidTrickplayInfo import dev.jdtech.jellyfin.models.UiText +import dev.jdtech.jellyfin.models.VideoQuality import dev.jdtech.jellyfin.models.toFindroidEpisodeDto import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto import dev.jdtech.jellyfin.models.toFindroidMovieDto @@ -395,19 +396,12 @@ class DownloaderImpl( itemId: UUID, quality: String, ): Uri? { - val maxBitrate = - when (quality) { - "720p" -> 2000000 // 2 Mbps - "480p" -> 1000000 // 1 Mbps - "360p" -> 800000 // 800Kbps - else -> 2000000 - } - + val videoQuality = VideoQuality.fromString(quality)!! return try { val deviceProfile = - jellyfinRepository.buildDeviceProfile(maxBitrate, "mkv", EncodingContext.STATIC) + jellyfinRepository.buildDeviceProfile(VideoQuality.getBitrate(videoQuality), "mkv", EncodingContext.STATIC) val playbackInfo = - jellyfinRepository.getPostedPlaybackInfo(itemId, false, deviceProfile, maxBitrate) + jellyfinRepository.getPostedPlaybackInfo(itemId, false, deviceProfile, VideoQuality.getBitrate(videoQuality)) val mediaSourceId = playbackInfo.content.mediaSources .firstOrNull() @@ -420,36 +414,14 @@ class DownloaderImpl( deviceId, mediaSourceId, playSessionId, - maxBitrate, + VideoQuality.getBitrate(videoQuality), "ts", + VideoQuality.getQualityInt(videoQuality) ) - val transcodeUri = buildTranscodeUri(downloadUrl, maxBitrate, quality) - transcodeUri + downloadUrl.toUri() } catch (e: Exception) { 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() - } } diff --git a/core/src/main/res/values/string_arrays.xml b/core/src/main/res/values/string_arrays.xml index d198af5a..b5dd2095 100644 --- a/core/src/main/res/values/string_arrays.xml +++ b/core/src/main/res/values/string_arrays.xml @@ -26,13 +26,31 @@ opensles + Auto Original - 720p - 2Mbps - 480p - 1Mbps + 1080p - 8Mbps + 720p - 3Mbps + 480p - 1.5Mbps 360p - 800Kbps + Auto Original + 1080p + 720p + 480p + 360p + + + Original + 1080p - 8Mbps + 720p - 3Mbps + 480p - 1.5Mbps + 360p - 800Kbps + + + Original + 1080p 720p 480p 360p diff --git a/core/src/main/res/xml/fragment_settings_downloads.xml b/core/src/main/res/xml/fragment_settings_downloads.xml index 9ebeb356..c88d3b81 100644 --- a/core/src/main/res/xml/fragment_settings_downloads.xml +++ b/core/src/main/res/xml/fragment_settings_downloads.xml @@ -14,8 +14,8 @@ android:key="pref_downloads_quality" android:title="Download Quality" android:defaultValue="Original" - android:entries="@array/quality_entries" - android:entryValues="@array/quality_values" + android:entries="@array/download_quality_entries" + android:entryValues="@array/download_quality_values" android:summary="%s" /> - suspend fun buildDeviceProfile( maxBitrate: Int, container: String, @@ -156,10 +154,9 @@ interface JellyfinRepository { deviceId: String, mediaSourceId: String, playSessionId: String, - videoBitrate: - @Suppress("ktlint:standard:max-line-length") - Int, + videoBitrate: Int, container: String, + maxHeight: Int, ): String suspend fun getTranscodedVideoStream( diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt index c8d6d66d..40d86bc6 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt @@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.SortBy +import dev.jdtech.jellyfin.models.VideoQuality import dev.jdtech.jellyfin.models.toFindroidCollection import dev.jdtech.jellyfin.models.toFindroidEpisode import dev.jdtech.jellyfin.models.toFindroidItem @@ -609,15 +610,6 @@ class JellyfinRepositoryImpl( override fun getUserId(): UUID = jellyfinApi.userId!! - override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair = - 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( maxBitrate: Int, container: String, @@ -631,7 +623,7 @@ class JellyfinRepositoryImpl( supportsPersistentIdentifier = true, deviceProfile = DeviceProfile( - name = "AnanasUser", + name = "FindroidUser", id = getUserId().toString(), maxStaticBitrate = maxBitrate, maxStreamingBitrate = maxBitrate, @@ -648,8 +640,8 @@ class JellyfinRepositoryImpl( container = container, context = context, protocol = MediaStreamProtocol.HLS, - audioCodec = "aac,ac3,eac3", - videoCodec = "hevc,h264", + audioCodec = "aac", + videoCodec = "h264", type = DlnaProfileType.VIDEO, conditions = listOf( @@ -712,6 +704,7 @@ class JellyfinRepositoryImpl( playSessionId: String, videoBitrate: Int, container: String, + maxHeight: Int, ): String { val url = jellyfinApi.videosApi.getVideoStreamByContainerUrl( @@ -721,10 +714,11 @@ class JellyfinRepositoryImpl( mediaSourceId = mediaSourceId, playSessionId = playSessionId, videoBitRate = videoBitrate, - audioBitRate = 384000, - videoCodec = "hevc", - audioCodec = "aac,ac3,eac3", + audioBitRate = 128000, + videoCodec = "h264", + audioCodec = "aac", container = container, + maxHeight = maxHeight, startTimeTicks = 0, copyTimestamps = true, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, @@ -739,7 +733,7 @@ class JellyfinRepositoryImpl( playSessionId: String, videoBitrate: Int, ): String { - val isAuto = videoBitrate == 12000000 + val isAuto = videoBitrate == VideoQuality.getBitrate(VideoQuality.PAuto) val url = if (!isAuto) { jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl( @@ -750,9 +744,9 @@ class JellyfinRepositoryImpl( playSessionId = playSessionId, videoBitRate = videoBitrate, enableAdaptiveBitrateStreaming = false, - audioBitRate = 384000, // could also be passed with audioBitrate but i preferred not as its not much data anyways - videoCodec = "hevc,h264", - audioCodec = "aac,ac3,eac3", + audioBitRate = 128000, + videoCodec = "h264", + audioCodec = "aac", startTimeTicks = 0, copyTimestamps = true, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, @@ -768,8 +762,8 @@ class JellyfinRepositoryImpl( mediaSourceId = mediaSourceId, playSessionId = playSessionId, enableAdaptiveBitrateStreaming = true, - videoCodec = "hevc", - audioCodec = "aac,ac3,eac3", + videoCodec = "h264", + audioCodec = "aac", startTimeTicks = 0, copyTimestamps = true, subtitleMethod = SubtitleDeliveryMethod.EXTERNAL, @@ -782,10 +776,7 @@ class JellyfinRepositoryImpl( } override suspend fun getDeviceId(): String { - val devices = jellyfinApi.devicesApi.getDevices(getUserId()) - return devices.content.items - ?.firstOrNull() - ?.id!! + return jellyfinApi.api.deviceInfo.id } override suspend fun stopEncodingProcess(playSessionId: String) { diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt index 9658541f..dcc4a39e 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt @@ -330,10 +330,6 @@ class JellyfinRepositoryOfflineImpl( TODO("Not yet implemented") } - override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair { - TODO("Not yet implemented") - } - override suspend fun buildDeviceProfile( maxBitrate: Int, container: String, @@ -349,6 +345,7 @@ class JellyfinRepositoryOfflineImpl( playSessionId: String, videoBitrate: Int, container: String, + maxHeight: Int, ): String { TODO("Not yet implemented") } diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index e804df6e..3ca1771f 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -28,6 +28,7 @@ import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.PlayerChapter import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.Trickplay +import dev.jdtech.jellyfin.models.VideoQuality import dev.jdtech.jellyfin.mpv.MPVPlayer import dev.jdtech.jellyfin.player.video.R import dev.jdtech.jellyfin.repository.JellyfinRepository @@ -464,17 +465,6 @@ constructor( 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) { val mediaId = player.currentMediaItem?.mediaId ?: return val currentItem = items.firstOrNull { it.itemId.toString() == mediaId } ?: return @@ -482,12 +472,9 @@ constructor( viewModelScope.launch { try { - val transcodingResolution = getTranscodeResolutions(quality) - val (videoBitRate, audioBitRate) = jellyfinRepository.getVideoTranscodeBitRate( - transcodingResolution - ) - val deviceProfile = jellyfinRepository.buildDeviceProfile(videoBitRate, "mkv", EncodingContext.STREAMING) - val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(currentItem.itemId,true,deviceProfile,videoBitRate) + val videoQuality = VideoQuality.fromString(quality)!! + val deviceProfile = jellyfinRepository.buildDeviceProfile(VideoQuality.getBitrate(videoQuality), "mkv", EncodingContext.STREAMING) + val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(currentItem.itemId,true,deviceProfile,VideoQuality.getBitrate(videoQuality)) val playSessionId = playbackInfo.content.playSessionId if (playSessionId != null) { jellyfinRepository.stopEncodingProcess(playSessionId) @@ -537,18 +524,18 @@ constructor( val allSubtitles = - if (transcodingResolution == 1080) { + if (VideoQuality.getQualityString(videoQuality) == "Original") { externalSubtitles }else { embeddedSubtitles.apply { addAll(externalSubtitles) } } - val url = if (transcodingResolution == 1080){ + val url = if (VideoQuality.getQualityString(videoQuality) == "Original"){ jellyfinRepository.getStreamUrl(currentItem.itemId, currentItem.mediaSourceId, playSessionId) } else { val mediaSourceId = mediaSources[currentMediaItemIndex].id 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 apiKey = jellyfinApi.api.accessToken // TODO: add in repo uriBuilder.appendQueryParameter("api_key",apiKey )