feat: Transcoding stream in player selection /code: prep repo for next commit transcoding downloads

This commit is contained in:
nomadics9 2024-07-19 02:20:55 +03:00
parent db79b50629
commit ccc6788a02
8 changed files with 489 additions and 4 deletions

View file

@ -33,6 +33,7 @@ import androidx.media3.ui.DefaultTimeBar
import androidx.media3.ui.PlayerControlView
import androidx.media3.ui.PlayerView
import androidx.navigation.navArgs
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding
import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
@ -82,6 +83,10 @@ class PlayerActivity : BasePlayerActivity() {
binding = ActivityPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
val changeQualityButton: ImageButton = findViewById(R.id.btnChangeQuality)
changeQualityButton.setOnClickListener {
showQualitySelectionDialog()
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding.playerView.player = viewModel.player
@ -342,6 +347,23 @@ class PlayerActivity : BasePlayerActivity() {
} catch (_: IllegalArgumentException) { }
}
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")
}
MaterialAlertDialogBuilder(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

@ -73,6 +73,24 @@
android:layout_height="0dp"
android:layout_weight="1" />
<!--TODO: Content Desc to Strings-->
<ImageButton
android:id="@+id/btnChangeQuality"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/transparent_circle_background"
android:contentDescription="Quality"
android:padding="16dp"
android:src="@drawable/ic_quality"
android:layout_gravity="end"
app:tint="@android:color/white"
/>
<Space
android:layout_width="16dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageButton
android:id="@+id/btn_pip"
android:layout_width="wrap_content"

View file

@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:pathData="M10,7.75a0.75,0.75 0,0 1,1.142 -0.638l3.664,2.249a0.75,0.75 0,0 1,0 1.278l-3.664,2.25a0.75,0.75 0,0 1,-1.142 -0.64z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M12,17v4"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M8,21h8"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M4,3L20,3A2,2 0,0 1,22 5L22,15A2,2 0,0 1,20 17L4,17A2,2 0,0 1,2 15L2,5A2,2 0,0 1,4 3z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
</vector>

View file

@ -11,9 +11,13 @@ import dev.jdtech.jellyfin.models.FindroidSource
import dev.jdtech.jellyfin.models.Intro
import dev.jdtech.jellyfin.models.SortBy
import kotlinx.coroutines.flow.Flow
import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.EncodingContext
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.PlaybackInfoResponse
import org.jellyfin.sdk.model.api.PublicSystemInfo
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.UserConfiguration
@ -81,7 +85,7 @@ interface JellyfinRepository {
suspend fun getMediaSources(itemId: UUID, includePath: Boolean = false): List<FindroidSource>
suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String
suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String? = null): String
suspend fun getIntroTimestamps(itemId: UUID): Intro?
@ -112,4 +116,18 @@ interface JellyfinRepository {
suspend fun getDownloads(): List<FindroidItem>
fun getUserId(): UUID
suspend fun getDeviceId(): String
suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int>
suspend fun buildDeviceProfile(maxBitrate: Int, container: String, context: EncodingContext): DeviceProfile
suspend fun getVideoStreambyContainerUrl(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int, container: String): String
suspend fun getTranscodedVideoStream(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int): String
suspend fun getPostedPlaybackInfo(itemId: UUID, enableDirectStream: Boolean, deviceProfile: DeviceProfile ,maxBitrate: Int): Response<PlaybackInfoResponse>
suspend fun stopEncodingProcess(playSessionId: String)
}

View file

@ -28,23 +28,35 @@ import io.ktor.util.toByteArray
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.api.client.extensions.dynamicHlsApi
import org.jellyfin.sdk.api.client.extensions.get
import org.jellyfin.sdk.api.client.extensions.hlsSegmentApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ClientCapabilitiesDto
import org.jellyfin.sdk.model.api.DeviceOptionsDto
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.EncodingContext
import org.jellyfin.sdk.model.api.GeneralCommandType
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.ItemFilter
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.MediaStreamProtocol
import org.jellyfin.sdk.model.api.MediaType
import org.jellyfin.sdk.model.api.PlaybackInfoDto
import org.jellyfin.sdk.model.api.PlaybackInfoResponse
import org.jellyfin.sdk.model.api.ProfileCondition
import org.jellyfin.sdk.model.api.ProfileConditionType
import org.jellyfin.sdk.model.api.ProfileConditionValue
import org.jellyfin.sdk.model.api.PublicSystemInfo
import org.jellyfin.sdk.model.api.SortOrder
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 org.jellyfin.sdk.model.api.UserConfiguration
import timber.log.Timber
import java.io.File
@ -322,13 +334,14 @@ class JellyfinRepositoryImpl(
sources
}
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String =
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String?): String =
withContext(Dispatchers.IO) {
try {
jellyfinApi.videosApi.getVideoStreamUrl(
itemId,
static = true,
mediaSourceId = mediaSourceId,
playSessionId = playSessionId
)
} catch (e: Exception) {
Timber.e(e)
@ -536,4 +549,165 @@ class JellyfinRepositoryImpl(
override fun getUserId(): UUID {
return jellyfinApi.userId!!
}
override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int> {
return 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, context: EncodingContext): DeviceProfile {
val deviceProfile = ClientCapabilitiesDto(
supportedCommands = emptyList(),
playableMediaTypes = emptyList(),
supportsMediaControl = true,
supportsPersistentIdentifier = true,
deviceProfile = DeviceProfile(
name = "AnanasUser",
id = getUserId().toString(),
maxStaticBitrate = maxBitrate,
maxStreamingBitrate = maxBitrate,
codecProfiles = emptyList(),
containerProfiles = listOf(),
directPlayProfiles = listOf(
DirectPlayProfile(type = DlnaProfileType.VIDEO),
DirectPlayProfile(type = DlnaProfileType.AUDIO),
),
transcodingProfiles = listOf(
TranscodingProfile(
container = container,
context = context,
protocol = MediaStreamProtocol.HLS,
audioCodec = "aac,ac3,eac3",
videoCodec = "hevc,h264",
type = DlnaProfileType.VIDEO,
conditions = listOf(
ProfileCondition(
condition = ProfileConditionType.LESS_THAN_EQUAL,
property = ProfileConditionValue.VIDEO_BITRATE,
value = "8000000",
isRequired = true,
)
),
copyTimestamps = true,
enableSubtitlesInManifest = true,
transcodeSeekInfo = TranscodeSeekInfo.AUTO,
),
),
subtitleProfiles = listOf(
SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("sub", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("vtt", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("ssa", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("pgs", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("dvb_teletext", SubtitleDeliveryMethod.EXTERNAL),
SubtitleProfile("dvd_subtitle", SubtitleDeliveryMethod.EXTERNAL)
),
)
)
return deviceProfile.deviceProfile!!
}
override suspend fun getPostedPlaybackInfo(itemId: UUID ,enableDirectStream: Boolean ,deviceProfile: DeviceProfile ,maxBitrate: Int): Response<PlaybackInfoResponse> {
val playbackInfo = jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
itemId = itemId,
PlaybackInfoDto(
userId = jellyfinApi.userId!!,
enableTranscoding = true,
enableDirectPlay = false,
enableDirectStream = enableDirectStream,
autoOpenLiveStream = true,
deviceProfile = deviceProfile,
allowAudioStreamCopy = true,
allowVideoStreamCopy = true,
maxStreamingBitrate = maxBitrate,
)
)
return playbackInfo
}
override suspend fun getVideoStreambyContainerUrl(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int, container: String): String {
val url = jellyfinApi.videosApi.getVideoStreamByContainerUrl(
itemId,
static = false,
deviceId = deviceId,
mediaSourceId = mediaSourceId,
playSessionId = playSessionId,
videoBitRate = videoBitrate,
audioBitRate = 384000,
videoCodec = "hevc",
audioCodec = "aac,ac3,eac3",
container = container,
startTimeTicks = 0,
copyTimestamps = true,
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL
)
return url
}
override suspend fun getTranscodedVideoStream(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int): String {
val isAuto = videoBitrate == 12000000
val url = if (!isAuto) {
jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl(
itemId,
static = false,
deviceId = deviceId,
mediaSourceId = mediaSourceId,
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",
startTimeTicks = 0,
copyTimestamps = true,
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL,
context = EncodingContext.STREAMING,
segmentContainer = "ts",
transcodeReasons = "ContainerBitrateExceedsLimit",
)
} else {
jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl(
itemId,
static = false,
deviceId = deviceId,
mediaSourceId = mediaSourceId,
playSessionId = playSessionId,
enableAdaptiveBitrateStreaming = true,
videoCodec = "hevc",
audioCodec = "aac,ac3,eac3",
startTimeTicks = 0,
copyTimestamps = true,
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL,
context = EncodingContext.STREAMING,
segmentContainer = "ts",
transcodeReasons = "ContainerBitrateExceedsLimit",
)
}
return url
}
override suspend fun getDeviceId(): String {
val devices = jellyfinApi.devicesApi.getDevices(getUserId())
return devices.content.items?.firstOrNull()?.id!!
}
override suspend fun stopEncodingProcess(playSessionId: String) {
val deviceId = getDeviceId()
jellyfinApi.api.hlsSegmentApi.stopEncodingProcess(
deviceId = deviceId,
playSessionId = playSessionId
)
}
}

View file

@ -23,9 +23,13 @@ import dev.jdtech.jellyfin.models.toIntro
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.EncodingContext
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.PlaybackInfoResponse
import org.jellyfin.sdk.model.api.PublicSystemInfo
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.UserConfiguration
@ -173,7 +177,7 @@ class JellyfinRepositoryOfflineImpl(
database.getSources(itemId).map { it.toFindroidSource(database) }
}
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String {
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String?): String {
TODO("Not yet implemented")
}
@ -285,4 +289,54 @@ class JellyfinRepositoryOfflineImpl(
override fun getUserId(): UUID {
return jellyfinApi.userId!!
}
override suspend fun getDeviceId(): String {
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,
context: EncodingContext
): DeviceProfile {
TODO("Not yet implemented")
}
override suspend fun getVideoStreambyContainerUrl(
itemId: UUID,
deviceId: String,
mediaSourceId: String,
playSessionId: String,
videoBitrate: Int,
container: String
): String {
TODO("Not yet implemented")
}
override suspend fun getTranscodedVideoStream(
itemId: UUID,
deviceId: String,
mediaSourceId: String,
playSessionId: String,
videoBitrate: Int
): String {
TODO("Not yet implemented")
}
override suspend fun getPostedPlaybackInfo(
itemId: UUID,
enableDirectStream: Boolean,
deviceProfile: DeviceProfile,
maxBitrate: Int
): Response<PlaybackInfoResponse> {
TODO("Not yet implemented")
}
override suspend fun stopEncodingProcess(playSessionId: String) {
TODO("Not yet implemented")
}
}

View file

@ -3,8 +3,10 @@ package dev.jdtech.jellyfin.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.core.net.toUri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -12,6 +14,7 @@ import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.TrackSelectionParameters
@ -20,6 +23,7 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.models.Intro
import dev.jdtech.jellyfin.models.PlayerChapter
import dev.jdtech.jellyfin.models.PlayerItem
@ -38,6 +42,8 @@ import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.EncodingContext
import org.jellyfin.sdk.model.api.MediaStreamType
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@ -49,10 +55,12 @@ 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 {
val player: Player
private var originalHeight: Int = 0
private val _uiState = MutableStateFlow(
UiState(
@ -455,8 +463,141 @@ constructor(
super.onIsPlayingChanged(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) {
val mediaId = player.currentMediaItem?.mediaId ?: 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
)
val deviceProfile = jellyfinRepository.buildDeviceProfile(videoBitRate, "mkv", EncodingContext.STREAMING)
val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(currentItem.itemId,true,deviceProfile,videoBitRate)
val playSessionId = playbackInfo.content.playSessionId
if (playSessionId != null) {
jellyfinRepository.stopEncodingProcess(playSessionId)
}
val mediaSources = jellyfinRepository.getMediaSources(currentItem.itemId, true)
// TODO: can maybe tidy the sub stuff up
val externalSubtitles = currentItem.externalSubtitles.map { externalSubtitle ->
MediaItem.SubtitleConfiguration.Builder(externalSubtitle.uri)
.setLabel(externalSubtitle.title.ifBlank { application.getString(R.string.external) })
.setLanguage(externalSubtitle.language.ifBlank { "Unknown" })
.setMimeType(externalSubtitle.mimeType)
.build()
}
val embeddedSubtitles = mediaSources[currentMediaItemIndex].mediaStreams
.filter { it.type == MediaStreamType.SUBTITLE && !it.isExternal && it.path != null }
.map { mediaStream ->
val test = mediaStream.codec
Timber.d("Deliver: %s", test)
var deliveryUrl = mediaStream.path
Timber.d("Deliverurl: %s", deliveryUrl)
if (mediaStream.codec == "webvtt") {
deliveryUrl = deliveryUrl?.replace("Stream.srt", "Stream.vtt")}
MediaItem.SubtitleConfiguration.Builder(Uri.parse(deliveryUrl))
.setMimeType(
when (mediaStream.codec) {
"subrip" -> MimeTypes.APPLICATION_SUBRIP
"webvtt" -> MimeTypes.TEXT_VTT
"ssa" -> MimeTypes.TEXT_SSA
"pgs" -> MimeTypes.APPLICATION_PGS
"ass" -> MimeTypes.TEXT_SSA
"srt" -> MimeTypes.APPLICATION_SUBRIP
"vtt" -> MimeTypes.TEXT_VTT
"ttml" -> MimeTypes.APPLICATION_TTML
"dfxp" -> MimeTypes.APPLICATION_TTML
"stl" -> MimeTypes.APPLICATION_TTML
"sbv" -> MimeTypes.APPLICATION_SUBRIP
else -> MimeTypes.TEXT_UNKNOWN
}
)
.setLanguage(mediaStream.language.ifBlank { "Unknown" })
.setLabel("Embedded")
.build()
}
.toMutableList()
val allSubtitles =
if (transcodingResolution == 1080) {
externalSubtitles
}else {
embeddedSubtitles.apply { addAll(externalSubtitles) }
}
val url = if (transcodingResolution == 1080){
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 uriBuilder = url.toUri().buildUpon()
val apiKey = jellyfinApi.api.accessToken // TODO: add in repo
uriBuilder.appendQueryParameter("api_key",apiKey )
val newUri = uriBuilder.build()
newUri.toString()
}
Timber.e("URI IS %s", url)
val mediaItemBuilder = MediaItem.Builder()
.setMediaId(currentItem.itemId.toString())
.setUri(url)
.setSubtitleConfigurations(allSubtitles)
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(currentItem.name)
.build(),
)
player.pause()
player.setMediaItem(mediaItemBuilder.build())
player.prepare()
player.seekTo(currentPosition)
playWhenReady = true
player.play()
val originalHeight = mediaSources[currentMediaItemIndex].mediaStreams
.filter { it.type == MediaStreamType.VIDEO }
.map {mediaStream -> mediaStream.height}.first() ?: 1080
// Store the original height
this@PlayerActivityViewModel.originalHeight = originalHeight
//isQualityChangeInProgress = true
} catch (e: Exception) {
Timber.e(e)
}
}
}
fun getOriginalHeight(): Int {
return originalHeight
}
}
sealed interface PlayerEvents {
data object NavigateBack : PlayerEvents
data class IsPlayingChanged(val isPlaying: Boolean) : PlayerEvents

View file

@ -136,7 +136,28 @@ class PlayerViewModel @Inject internal constructor(
} else {
mediaSources[mediaSourceIndex]
}
val externalSubtitles = mediaSource.mediaStreams
// Embedded Sub externally for offline prep next commit
val externalSubtitles = if (mediaSource.type.toString() == "LOCAL" ) {
mediaSource.mediaStreams
.filter { mediaStream ->
mediaStream.type == MediaStreamType.SUBTITLE && !mediaStream.path.isNullOrBlank()
}
.map { mediaStream ->
ExternalSubtitle(
mediaStream.title,
mediaStream.language,
Uri.parse(mediaStream.path!!),
when (mediaStream.codec) {
"subrip" -> MimeTypes.APPLICATION_SUBRIP
"webvtt" -> MimeTypes.APPLICATION_SUBRIP
"pgs" -> MimeTypes.APPLICATION_PGS
"ass" -> MimeTypes.TEXT_SSA
else -> MimeTypes.TEXT_UNKNOWN
},
)
}
}else {
mediaSource.mediaStreams
.filter { mediaStream ->
mediaStream.isExternal && mediaStream.type == MediaStreamType.SUBTITLE && !mediaStream.path.isNullOrBlank()
}
@ -148,11 +169,13 @@ class PlayerViewModel @Inject internal constructor(
when (mediaStream.codec) {
"subrip" -> MimeTypes.APPLICATION_SUBRIP
"webvtt" -> MimeTypes.APPLICATION_SUBRIP
"pgs" -> MimeTypes.APPLICATION_PGS
"ass" -> MimeTypes.TEXT_SSA
else -> MimeTypes.TEXT_UNKNOWN
},
)
}
}
val trickplayInfo = when (this) {
is FindroidSources -> {
this.trickplayInfo?.get(mediaSource.id)?.let {