Skip to content

Commit

Permalink
WIP: Abstract JellyfinMediaSource
Browse files Browse the repository at this point in the history
  • Loading branch information
neBM committed Jun 30, 2024
1 parent ed904f2 commit 5812873
Show file tree
Hide file tree
Showing 16 changed files with 157 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface DownloadDao {
fun getAllDownloads(): Flow<List<DownloadEntity>>

@Query("SELECT * FROM $TABLE_NAME WHERE item_id LIKE :downloadId")
suspend fun get(downloadId: String): DownloadEntity
suspend fun get(downloadId: String): DownloadEntity?

@Query("SELECT download_folder_uri FROM $TABLE_NAME WHERE item_id LIKE :downloadId")
suspend fun getDownloadFolderUri(downloadId: String): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.serialization.json.Json.Default.decodeFromString
import org.jellyfin.mobile.data.entity.DownloadEntity.Key.ITEM_ID
import org.jellyfin.mobile.data.entity.DownloadEntity.Key.TABLE_NAME
import org.jellyfin.mobile.player.source.RemoteJellyfinMediaSource

@Entity(
tableName = TABLE_NAME,
Expand All @@ -28,6 +30,8 @@ data class DownloadEntity(
@ColumnInfo(name = DOWNLOAD_LENGTH)
val downloadLength: Long,
) {
fun asMediaSource() = decodeFromString<RemoteJellyfinMediaSource>(mediaSource)

constructor(itemId: String, mediaUri: String, mediaSource: String, downloadFolderUri: String, downloadLength: Long) :
this(0, itemId, mediaUri, mediaSource, downloadFolderUri, downloadLength)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import kotlinx.serialization.json.Json
import org.jellyfin.mobile.data.entity.DownloadEntity
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.source.RemoteJellyfinMediaSource
import org.jellyfin.mobile.utils.Constants
import java.io.File
import java.util.Locale

data class DownloadItem(private val download: DownloadEntity) {
val mediaSource: JellyfinMediaSource = Json.decodeFromString(download.mediaSource)
val mediaSource: RemoteJellyfinMediaSource = Json.decodeFromString(download.mediaSource)
val thumbnail: Bitmap? = BitmapFactory.decodeFile(
File(download.downloadFolderUri, Constants.DOWNLOAD_THUMBNAIL_FILENAME).canonicalPath,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.data.dao.DownloadDao
import org.jellyfin.mobile.data.entity.DownloadEntity
import org.jellyfin.mobile.player.deviceprofile.DeviceProfileBuilder
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.source.MediaSourceResolver
import org.jellyfin.mobile.player.source.RemoteJellyfinMediaSource
import org.jellyfin.mobile.utils.AndroidVersion
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.requestPermission
Expand Down Expand Up @@ -79,7 +79,7 @@ class DownloadUtils(
private var downloadTracker: DownloadTracker
private val jellyfinDownloadTracker: DownloadUtils.JellyfinDownloadTracker = JellyfinDownloadTracker()

private var jellyfinMediaSource: JellyfinMediaSource? = null
private var jellyfinMediaSource: RemoteJellyfinMediaSource? = null

init {
val regex = Regex("""Items/([a-f0-9]{32})/Download""")
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/jellyfin/mobile/events/ActivityEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package org.jellyfin.mobile.events

import android.net.Uri
import org.jellyfin.mobile.player.interaction.PlayOptions
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.source.RemoteJellyfinMediaSource
import org.json.JSONArray

sealed class ActivityEvent {
class ChangeFullscreen(val isFullscreen: Boolean) : ActivityEvent()
class LaunchNativePlayer(val playOptions: PlayOptions) : ActivityEvent()
class OpenUrl(val uri: String) : ActivityEvent()
class DownloadFile(val uri: Uri, val title: String, val filename: String) : ActivityEvent()
class RemoveDownload(val download: JellyfinMediaSource, val force: Boolean = false) : ActivityEvent()
class RemoveDownload(val download: RemoteJellyfinMediaSource, val force: Boolean = false) : ActivityEvent()
class CastMessage(val action: String, val args: JSONArray) : ActivityEvent()
data object RequestBluetoothPermission : ActivityEvent()
data object OpenSettings : ActivityEvent()
Expand Down
27 changes: 13 additions & 14 deletions app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.jellyfin.mobile.player.interaction.PlayerMediaSessionCallback
import org.jellyfin.mobile.player.interaction.PlayerNotificationHelper
import org.jellyfin.mobile.player.queue.QueueManager
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.source.RemoteJellyfinMediaSource
import org.jellyfin.mobile.player.ui.DecoderType
import org.jellyfin.mobile.player.ui.DisplayPreferences
import org.jellyfin.mobile.player.ui.PlayState
Expand Down Expand Up @@ -241,7 +242,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
_player.value = null
}

fun load(jellyfinMediaSource: JellyfinMediaSource, exoMediaSource: MediaSource, playWhenReady: Boolean) {
fun load(jellyfinMediaSource: RemoteJellyfinMediaSource, exoMediaSource: MediaSource, playWhenReady: Boolean) {
val player = playerOrNull ?: return

player.setMediaSource(exoMediaSource)
Expand All @@ -261,12 +262,11 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}

private fun startProgressUpdates() {
if (queueManager.currentMediaSourceOrNull?.isDownload == false) {
progressUpdateJob = viewModelScope.launch {
while (true) {
delay(Constants.PLAYER_TIME_UPDATE_RATE)
playerOrNull?.reportPlaybackState()
}
if (mediaSourceOrNull != null && mediaSourceOrNull !is RemoteJellyfinMediaSource) return
progressUpdateJob = viewModelScope.launch {
while (true) {
delay(Constants.PLAYER_TIME_UPDATE_RATE)
playerOrNull?.reportPlaybackState()
}
}
}
Expand Down Expand Up @@ -294,8 +294,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
queueManager.tryRestartPlayback()
}

private suspend fun Player.reportPlaybackStart(mediaSource: JellyfinMediaSource) {
if (mediaSource.isDownload) return
private suspend fun Player.reportPlaybackStart(mediaSource: RemoteJellyfinMediaSource) {
if (mediaSourceOrNull != null && mediaSourceOrNull !is RemoteJellyfinMediaSource) return
try {
playStateApi.reportPlaybackStart(
PlaybackStartInfo(
Expand All @@ -318,9 +318,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}

private suspend fun Player.reportPlaybackState() {
val mediaSource = mediaSourceOrNull ?: return
val mediaSource = mediaSourceOrNull as? RemoteJellyfinMediaSource ?: return
val playbackPositionMillis = currentPosition
if (playbackState != Player.STATE_ENDED && !mediaSource.isDownload) {
if (playbackState != Player.STATE_ENDED) {
val stream = AudioManager.STREAM_MUSIC
val volumeRange = audioManager.getVolumeRange(stream)
val currentVolume = audioManager.getStreamVolume(stream)
Expand All @@ -347,9 +347,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}

private fun reportPlaybackStop() {
val mediaSource = mediaSourceOrNull ?: return
val mediaSource = mediaSourceOrNull as? RemoteJellyfinMediaSource ?: return
val player = playerOrNull ?: return
if (mediaSourceOrNull?.isDownload == true) return
val hasFinished = player.playbackState == Player.STATE_ENDED
val lastPositionTicks = when {
hasFinished -> mediaSource.runTimeTicks
Expand Down Expand Up @@ -383,7 +382,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
}

suspend fun stopTranscoding(mediaSource: JellyfinMediaSource) {
suspend fun stopTranscoding(mediaSource: RemoteJellyfinMediaSource) {
if (mediaSource.playMethod == PlayMethod.TRANSCODE) {
hlsSegmentApi.stopEncodingProcess(
deviceId = apiClient.deviceInfo.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.data.dao.DownloadDao
import org.jellyfin.mobile.player.PlayerViewModel
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.source.LocalJellyfinMediaSource
import org.jellyfin.mobile.player.source.RemoteJellyfinMediaSource
import org.jellyfin.mobile.utils.AndroidVersion
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.VIDEO_PLAYER_NOTIFICATION_ID
Expand Down Expand Up @@ -148,21 +150,24 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom
}

private suspend fun loadImage(mediaSource: JellyfinMediaSource): Bitmap? {
if (mediaSource.isDownload) {
val downloadFolder = File(downloadDao.getDownloadFolderUri(mediaSource.id))
val thumbnailFile = File(downloadFolder, Constants.DOWNLOAD_THUMBNAIL_FILENAME)
return BitmapFactory.decodeFile(thumbnailFile.canonicalPath)
} else {
val size = context.resources.getDimensionPixelSize(R.dimen.media_notification_height)

val imageUrl = imageApi.getItemImageUrl(
itemId = mediaSource.itemId,
imageType = ImageType.PRIMARY,
maxWidth = size,
maxHeight = size,
)
val imageRequest = ImageRequest.Builder(context).data(imageUrl).build()
return imageLoader.execute(imageRequest).drawable?.toBitmap()
return when (mediaSource) {
is LocalJellyfinMediaSource -> {
val downloadFolder = File(downloadDao.getDownloadFolderUri(mediaSource.id))
val thumbnailFile = File(downloadFolder, Constants.DOWNLOAD_THUMBNAIL_FILENAME)
BitmapFactory.decodeFile(thumbnailFile.canonicalPath)
}
is RemoteJellyfinMediaSource -> {
val size = context.resources.getDimensionPixelSize(R.dimen.media_notification_height)

val imageUrl = imageApi.getItemImageUrl(
itemId = mediaSource.itemId,
imageType = ImageType.PRIMARY,
maxWidth = size,
maxHeight = size,
)
val imageRequest = ImageRequest.Builder(context).data(imageUrl).build()
imageLoader.execute(imageRequest).drawable?.toBitmap()
}
}
}

Expand Down
Loading

0 comments on commit 5812873

Please sign in to comment.