From e69a6534533d07a3679f00c039b7340e983c6fe5 Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Sun, 11 Jul 2021 16:44:46 +0200 Subject: [PATCH] Get mediaSources and select version of movie --- app/src/main/AndroidManifest.xml | 2 +- .../dev/jdtech/jellyfin/PlayerActivity.kt | 2 +- .../dialogs/VideoVersionDialogFragment.kt | 24 ++++++ .../jdtech/jellyfin/fragments/HomeFragment.kt | 3 +- .../jellyfin/fragments/LibraryFragment.kt | 3 +- .../jellyfin/fragments/MediaInfoFragment.kt | 30 ++++++- .../jellyfin/repository/JellyfinRepository.kt | 5 +- .../repository/JellyfinRepositoryImpl.kt | 85 ++++++++++--------- .../jellyfin/viewmodels/MediaInfoViewModel.kt | 18 +++- .../viewmodels/PlayerActivityViewModel.kt | 16 ++-- .../main/res/navigation/main_navigation.xml | 6 ++ 11 files changed, 133 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/dev/jdtech/jellyfin/dialogs/VideoVersionDialogFragment.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 91e46363..c69357d6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Jellyfin"> - + diff --git a/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index b474facf..594f1bc4 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -34,7 +34,7 @@ class PlayerActivity : AppCompatActivity() { }) if (viewModel.player.value == null) { - viewModel.initializePlayer(args.itemId) + viewModel.initializePlayer(args.itemId, args.mediaSourceId) } hideSystemUI() } diff --git a/app/src/main/java/dev/jdtech/jellyfin/dialogs/VideoVersionDialogFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/dialogs/VideoVersionDialogFragment.kt new file mode 100644 index 00000000..912d4074 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/dialogs/VideoVersionDialogFragment.kt @@ -0,0 +1,24 @@ +package dev.jdtech.jellyfin.dialogs + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel +import java.lang.IllegalStateException + +class VideoVersionDialogFragment( + private val viewModel: MediaInfoViewModel +) : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val items = viewModel.mediaSources.value!!.map { it.name } + return activity?.let { + val builder = AlertDialog.Builder(it) + builder.setTitle("Select a version") + .setItems(items.toTypedArray()) { _, which -> + viewModel.navigateToPlayer(viewModel.mediaSources.value!![which]) + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt index b9ae3b4c..cbb387da 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt @@ -81,7 +81,8 @@ class HomeFragment : Fragment() { findNavController().navigate( HomeFragmentDirections.actionNavigationHomeToMediaInfoFragment( item.id, - item.name + item.name, + item.type ?: "Unknown" ) ) } diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt index ea132a93..68c59992 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt @@ -47,7 +47,8 @@ class LibraryFragment : Fragment() { findNavController().navigate( LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment( item.id, - item.name + item.name, + item.type ?: "Unknown" ) ) } diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt index eba68249..169a8870 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt @@ -14,6 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint import dev.jdtech.jellyfin.adapters.PersonListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding +import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel import org.jellyfin.sdk.model.api.BaseItemDto import java.util.* @@ -53,6 +54,10 @@ class MediaInfoFragment : Fragment() { } }) + viewModel.navigateToPlayer.observe(viewLifecycleOwner, { mediaSource -> + mediaSource.id?.let { navigateToPlayerActivity(args.itemId, it) } + }) + binding.trailerButton.setOnClickListener { val intent = Intent( Intent.ACTION_VIEW, @@ -72,10 +77,24 @@ class MediaInfoFragment : Fragment() { binding.peopleRecyclerView.adapter = PersonListAdapter() binding.playButton.setOnClickListener { - navigateToPlayerActivity(args.itemId) + if (args.itemType == "Movie") { + if (!viewModel.mediaSources.value.isNullOrEmpty()) { + if (viewModel.mediaSources.value!!.size > 1) { + VideoVersionDialogFragment(viewModel).show( + parentFragmentManager, + "videoversiondialog" + ) + } else { + navigateToPlayerActivity( + args.itemId, + viewModel.mediaSources.value!![0].id!! + ) + } + } + } } - viewModel.loadData(args.itemId) + viewModel.loadData(args.itemId, args.itemType) } private fun navigateToEpisodeBottomSheetFragment(episode: BaseItemDto) { @@ -97,9 +116,12 @@ class MediaInfoFragment : Fragment() { ) } - private fun navigateToPlayerActivity(itemId: UUID) { + private fun navigateToPlayerActivity(itemId: UUID, mediaSourceId: String) { findNavController().navigate( - MediaInfoFragmentDirections.actionMediaInfoFragmentToPlayerActivity(itemId) + MediaInfoFragmentDirections.actionMediaInfoFragmentToPlayerActivity( + itemId, + mediaSourceId + ) ) } } \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt index ff2fcb74..b5298f61 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt @@ -2,6 +2,7 @@ package dev.jdtech.jellyfin.repository import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.ItemFields +import org.jellyfin.sdk.model.api.MediaSourceInfo import java.util.* interface JellyfinRepository { @@ -15,5 +16,7 @@ interface JellyfinRepository { suspend fun getEpisodes(seriesId: UUID, seasonId: UUID, fields: List? = null): List - suspend fun getStreamUrl(itemId: UUID): String + suspend fun getMediaSources(itemId: UUID): List + + suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String } \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt index 0b7167d5..dfcdbae7 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt @@ -61,52 +61,57 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep return episodes } - override suspend fun getStreamUrl(itemId: UUID): String { - val streamUrl: String + override suspend fun getMediaSources(itemId: UUID): List { + val mediaSourceInfoList: List + val mediaInfo by jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( + itemId, PlaybackInfoDto( + userId = jellyfinApi.userId!!, + deviceProfile = DeviceProfile( + name = "Direct play all", + maxStaticBitrate = 1_000_000_000, + maxStreamingBitrate = 1_000_000_000, + codecProfiles = listOf(), + containerProfiles = listOf(), + directPlayProfiles = listOf( + DirectPlayProfile( + type = DlnaProfileType.VIDEO + ), DirectPlayProfile(type = DlnaProfileType.AUDIO) + ), + transcodingProfiles = listOf(), + responseProfiles = listOf(), + enableAlbumArtInDidl = false, + enableMsMediaReceiverRegistrar = false, + enableSingleAlbumArtLimit = false, + enableSingleSubtitleLimit = false, + ignoreTranscodeByteRangeRequests = false, + maxAlbumArtHeight = 1_000_000_000, + maxAlbumArtWidth = 1_000_000_000, + requiresPlainFolders = false, + requiresPlainVideoItems = false, + timelineOffsetSeconds = 0 + ), + startTimeTicks = null, + audioStreamIndex = null, + subtitleStreamIndex = null, + maxStreamingBitrate = 1_000_000_000, + ) + ) + mediaSourceInfoList = mediaInfo.mediaSources ?: listOf() + return mediaSourceInfoList + } + + override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String { + var streamUrl: String = "" withContext(Dispatchers.IO) { try { - val mediaInfo by jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( - itemId, PlaybackInfoDto( - userId = jellyfinApi.userId!!, - deviceProfile = DeviceProfile( - name = "Direct play all", - maxStaticBitrate = 1_000_000_000, - maxStreamingBitrate = 1_000_000_000, - codecProfiles = listOf(), - containerProfiles = listOf(), - directPlayProfiles = listOf( - DirectPlayProfile( - type = DlnaProfileType.VIDEO - ), DirectPlayProfile(type = DlnaProfileType.AUDIO) - ), - transcodingProfiles = listOf(), - responseProfiles = listOf(), - enableAlbumArtInDidl = false, - enableMsMediaReceiverRegistrar = false, - enableSingleAlbumArtLimit = false, - enableSingleSubtitleLimit = false, - ignoreTranscodeByteRangeRequests = false, - maxAlbumArtHeight = 1_000_000_000, - maxAlbumArtWidth = 1_000_000_000, - requiresPlainFolders = false, - requiresPlainVideoItems = false, - timelineOffsetSeconds = 0 - ), - startTimeTicks = null, - audioStreamIndex = null, - subtitleStreamIndex = null, - maxStreamingBitrate = 1_000_000_000, - ) + streamUrl = jellyfinApi.videosApi.getVideoStreamUrl( + itemId, + static = true, + mediaSourceId = mediaSourceId ) - Log.d("JellyfinRepository", mediaInfo.mediaSources.toString()) } catch (e: Exception) { Log.e("JellyfinRepository", "${e.message}") } - streamUrl = jellyfinApi.videosApi.getVideoStreamUrl( - itemId, - static = true, - mediaSourceId = itemId.toString() - ) } return streamUrl } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt index 05cf40ae..15c5dcd0 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemPerson +import org.jellyfin.sdk.model.api.MediaSourceInfo import java.util.* import javax.inject.Inject @@ -49,7 +50,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { private val _seasons = MutableLiveData>() val seasons: LiveData> = _seasons - fun loadData(itemId: UUID) { + private val _mediaSources = MutableLiveData>() + val mediaSources: LiveData> = _mediaSources + + private val _navigateToPlayer = MutableLiveData() + val navigateToPlayer: LiveData = _navigateToPlayer + + fun loadData(itemId: UUID, itemType: String) { viewModelScope.launch { _item.value = jellyfinRepository.getItem(itemId) _actors.value = getActors(_item.value!!) @@ -60,10 +67,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { _genresString.value = _item.value?.genres?.joinToString(separator = ", ") _runTime.value = "${_item.value?.runTimeTicks?.div(600000000)} min" _dateString.value = getDateString(_item.value!!) - if (_item.value!!.type == "Series") { + if (itemType == "Series") { _nextUp.value = getNextUp(itemId) _seasons.value = jellyfinRepository.getSeasons(itemId) } + if (itemType == "Movie") { + _mediaSources.value = jellyfinRepository.getMediaSources(itemId) + } } } @@ -120,4 +130,8 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { else -> dateString } } + + fun navigateToPlayer(mediaSource: MediaSourceInfo) { + _navigateToPlayer.value = mediaSource + } } \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index e3812395..e2d54c37 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -1,9 +1,6 @@ package dev.jdtech.jellyfin.viewmodels import android.app.Application -import android.content.Context -import android.media.MediaCodecInfo -import android.media.MediaCodecList import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -11,7 +8,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.exoplayer2.* import com.google.android.exoplayer2.trackselection.DefaultTrackSelector -import com.google.android.exoplayer2.util.MimeTypes import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.repository.JellyfinRepository import kotlinx.coroutines.launch @@ -37,7 +33,7 @@ constructor( playbackStateListener = PlaybackStateListener() } - fun initializePlayer(itemId: UUID) { + fun initializePlayer(itemId: UUID, mediaSourceId: String) { if (player.value == null) { val trackSelector = DefaultTrackSelector(application) val renderersFactory = DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) @@ -50,7 +46,7 @@ constructor( player.value?.addListener(playbackStateListener) viewModelScope.launch { - val streamUrl = jellyfinRepository.getStreamUrl(itemId) + val streamUrl = jellyfinRepository.getStreamUrl(itemId, mediaSourceId) val mediaItem = MediaItem.Builder() .setMediaId(itemId.toString()) @@ -80,16 +76,16 @@ constructor( var stateString = "UNKNOWN_STATE -" when (state) { ExoPlayer.STATE_IDLE -> { - stateString = "ExoPlayer.STATE_IDLE -"; + stateString = "ExoPlayer.STATE_IDLE -" } ExoPlayer.STATE_BUFFERING -> { - stateString = "ExoPlayer.STATE_BUFFERING -"; + stateString = "ExoPlayer.STATE_BUFFERING -" } ExoPlayer.STATE_READY -> { - stateString = "ExoPlayer.STATE_READY -"; + stateString = "ExoPlayer.STATE_READY -" } ExoPlayer.STATE_ENDED -> { - stateString = "ExoPlayer.STATE_ENDED -"; + stateString = "ExoPlayer.STATE_ENDED -" } } Log.d("PlayerActivity", "changed state to $stateString") diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 3ef322e7..8c74de73 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -91,6 +91,9 @@ + + \ No newline at end of file