bugfixes: deviceId / code: New Enum VideoQuality
This commit is contained in:
parent
ba580f8769
commit
6dded2e726
11 changed files with 121 additions and 109 deletions
|
@ -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<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)
|
||||
.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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,13 +26,31 @@
|
|||
<item>opensles</item>
|
||||
</string-array>
|
||||
<string-array name="quality_entries">
|
||||
<item>Auto</item>
|
||||
<item>Original</item>
|
||||
<item>720p - 2Mbps</item>
|
||||
<item>480p - 1Mbps</item>
|
||||
<item>1080p - 8Mbps</item>
|
||||
<item>720p - 3Mbps</item>
|
||||
<item>480p - 1.5Mbps</item>
|
||||
<item>360p - 800Kbps</item>
|
||||
</string-array>
|
||||
<string-array name="quality_values">
|
||||
<item>Auto</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>480p</item>
|
||||
<item>360p</item>
|
||||
|
|
|
@ -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" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -143,8 +143,6 @@ interface JellyfinRepository {
|
|||
|
||||
suspend fun getDeviceId(): String
|
||||
|
||||
suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int>
|
||||
|
||||
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(
|
||||
|
|
|
@ -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<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(
|
||||
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) {
|
||||
|
|
|
@ -330,10 +330,6 @@ class JellyfinRepositoryOfflineImpl(
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int> {
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
|
|
Loading…
Reference in a new issue