Get mediaSources and select version of movie

This commit is contained in:
Jarne Demeulemeester 2021-07-11 16:44:46 +02:00
parent 7f5dea58bc
commit e69a653453
No known key found for this signature in database
GPG key ID: 60884A0C1EBA43E5
11 changed files with 133 additions and 61 deletions

View file

@ -12,7 +12,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Jellyfin"> android:theme="@style/Theme.Jellyfin">
<activity android:name=".PlayerActivity"></activity> <activity android:name=".PlayerActivity" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/title_activity_main" /> android:label="@string/title_activity_main" />

View file

@ -34,7 +34,7 @@ class PlayerActivity : AppCompatActivity() {
}) })
if (viewModel.player.value == null) { if (viewModel.player.value == null) {
viewModel.initializePlayer(args.itemId) viewModel.initializePlayer(args.itemId, args.mediaSourceId)
} }
hideSystemUI() hideSystemUI()
} }

View file

@ -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")
}
}

View file

@ -81,7 +81,8 @@ class HomeFragment : Fragment() {
findNavController().navigate( findNavController().navigate(
HomeFragmentDirections.actionNavigationHomeToMediaInfoFragment( HomeFragmentDirections.actionNavigationHomeToMediaInfoFragment(
item.id, item.id,
item.name item.name,
item.type ?: "Unknown"
) )
) )
} }

View file

@ -47,7 +47,8 @@ class LibraryFragment : Fragment() {
findNavController().navigate( findNavController().navigate(
LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment( LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
item.id, item.id,
item.name item.name,
item.type ?: "Unknown"
) )
) )
} }

View file

@ -14,6 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.PersonListAdapter import dev.jdtech.jellyfin.adapters.PersonListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding
import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment
import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.* 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 { binding.trailerButton.setOnClickListener {
val intent = Intent( val intent = Intent(
Intent.ACTION_VIEW, Intent.ACTION_VIEW,
@ -72,10 +77,24 @@ class MediaInfoFragment : Fragment() {
binding.peopleRecyclerView.adapter = PersonListAdapter() binding.peopleRecyclerView.adapter = PersonListAdapter()
binding.playButton.setOnClickListener { 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) { 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( findNavController().navigate(
MediaInfoFragmentDirections.actionMediaInfoFragmentToPlayerActivity(itemId) MediaInfoFragmentDirections.actionMediaInfoFragmentToPlayerActivity(
itemId,
mediaSourceId
)
) )
} }
} }

View file

@ -2,6 +2,7 @@ package dev.jdtech.jellyfin.repository
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.MediaSourceInfo
import java.util.* import java.util.*
interface JellyfinRepository { interface JellyfinRepository {
@ -15,5 +16,7 @@ interface JellyfinRepository {
suspend fun getEpisodes(seriesId: UUID, seasonId: UUID, fields: List<ItemFields>? = null): List<BaseItemDto> suspend fun getEpisodes(seriesId: UUID, seasonId: UUID, fields: List<ItemFields>? = null): List<BaseItemDto>
suspend fun getStreamUrl(itemId: UUID): String suspend fun getMediaSources(itemId: UUID): List<MediaSourceInfo>
suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String
} }

View file

@ -61,10 +61,8 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
return episodes return episodes
} }
override suspend fun getStreamUrl(itemId: UUID): String { override suspend fun getMediaSources(itemId: UUID): List<MediaSourceInfo> {
val streamUrl: String val mediaSourceInfoList: List<MediaSourceInfo>
withContext(Dispatchers.IO) {
try {
val mediaInfo by jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( val mediaInfo by jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
itemId, PlaybackInfoDto( itemId, PlaybackInfoDto(
userId = jellyfinApi.userId!!, userId = jellyfinApi.userId!!,
@ -98,15 +96,22 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
maxStreamingBitrate = 1_000_000_000, maxStreamingBitrate = 1_000_000_000,
) )
) )
Log.d("JellyfinRepository", mediaInfo.mediaSources.toString()) mediaSourceInfoList = mediaInfo.mediaSources ?: listOf()
} catch (e: Exception) { return mediaSourceInfoList
Log.e("JellyfinRepository", "${e.message}")
} }
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String {
var streamUrl: String = ""
withContext(Dispatchers.IO) {
try {
streamUrl = jellyfinApi.videosApi.getVideoStreamUrl( streamUrl = jellyfinApi.videosApi.getVideoStreamUrl(
itemId, itemId,
static = true, static = true,
mediaSourceId = itemId.toString() mediaSourceId = mediaSourceId
) )
} catch (e: Exception) {
Log.e("JellyfinRepository", "${e.message}")
}
} }
return streamUrl return streamUrl
} }

View file

@ -12,6 +12,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemPerson import org.jellyfin.sdk.model.api.BaseItemPerson
import org.jellyfin.sdk.model.api.MediaSourceInfo
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -49,7 +50,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _seasons = MutableLiveData<List<BaseItemDto>>() private val _seasons = MutableLiveData<List<BaseItemDto>>()
val seasons: LiveData<List<BaseItemDto>> = _seasons val seasons: LiveData<List<BaseItemDto>> = _seasons
fun loadData(itemId: UUID) { private val _mediaSources = MutableLiveData<List<MediaSourceInfo>>()
val mediaSources: LiveData<List<MediaSourceInfo>> = _mediaSources
private val _navigateToPlayer = MutableLiveData<MediaSourceInfo>()
val navigateToPlayer: LiveData<MediaSourceInfo> = _navigateToPlayer
fun loadData(itemId: UUID, itemType: String) {
viewModelScope.launch { viewModelScope.launch {
_item.value = jellyfinRepository.getItem(itemId) _item.value = jellyfinRepository.getItem(itemId)
_actors.value = getActors(_item.value!!) _actors.value = getActors(_item.value!!)
@ -60,10 +67,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
_genresString.value = _item.value?.genres?.joinToString(separator = ", ") _genresString.value = _item.value?.genres?.joinToString(separator = ", ")
_runTime.value = "${_item.value?.runTimeTicks?.div(600000000)} min" _runTime.value = "${_item.value?.runTimeTicks?.div(600000000)} min"
_dateString.value = getDateString(_item.value!!) _dateString.value = getDateString(_item.value!!)
if (_item.value!!.type == "Series") { if (itemType == "Series") {
_nextUp.value = getNextUp(itemId) _nextUp.value = getNextUp(itemId)
_seasons.value = jellyfinRepository.getSeasons(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 else -> dateString
} }
} }
fun navigateToPlayer(mediaSource: MediaSourceInfo) {
_navigateToPlayer.value = mediaSource
}
} }

View file

@ -1,9 +1,6 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.app.Application import android.app.Application
import android.content.Context
import android.media.MediaCodecInfo
import android.media.MediaCodecList
import android.util.Log import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -11,7 +8,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.util.MimeTypes
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -37,7 +33,7 @@ constructor(
playbackStateListener = PlaybackStateListener() playbackStateListener = PlaybackStateListener()
} }
fun initializePlayer(itemId: UUID) { fun initializePlayer(itemId: UUID, mediaSourceId: String) {
if (player.value == null) { if (player.value == null) {
val trackSelector = DefaultTrackSelector(application) val trackSelector = DefaultTrackSelector(application)
val renderersFactory = DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) val renderersFactory = DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
@ -50,7 +46,7 @@ constructor(
player.value?.addListener(playbackStateListener) player.value?.addListener(playbackStateListener)
viewModelScope.launch { viewModelScope.launch {
val streamUrl = jellyfinRepository.getStreamUrl(itemId) val streamUrl = jellyfinRepository.getStreamUrl(itemId, mediaSourceId)
val mediaItem = val mediaItem =
MediaItem.Builder() MediaItem.Builder()
.setMediaId(itemId.toString()) .setMediaId(itemId.toString())
@ -80,16 +76,16 @@ constructor(
var stateString = "UNKNOWN_STATE -" var stateString = "UNKNOWN_STATE -"
when (state) { when (state) {
ExoPlayer.STATE_IDLE -> { ExoPlayer.STATE_IDLE -> {
stateString = "ExoPlayer.STATE_IDLE -"; stateString = "ExoPlayer.STATE_IDLE -"
} }
ExoPlayer.STATE_BUFFERING -> { ExoPlayer.STATE_BUFFERING -> {
stateString = "ExoPlayer.STATE_BUFFERING -"; stateString = "ExoPlayer.STATE_BUFFERING -"
} }
ExoPlayer.STATE_READY -> { ExoPlayer.STATE_READY -> {
stateString = "ExoPlayer.STATE_READY -"; stateString = "ExoPlayer.STATE_READY -"
} }
ExoPlayer.STATE_ENDED -> { ExoPlayer.STATE_ENDED -> {
stateString = "ExoPlayer.STATE_ENDED -"; stateString = "ExoPlayer.STATE_ENDED -"
} }
} }
Log.d("PlayerActivity", "changed state to $stateString") Log.d("PlayerActivity", "changed state to $stateString")

View file

@ -91,6 +91,9 @@
<action <action
android:id="@+id/action_mediaInfoFragment_to_playerActivity" android:id="@+id/action_mediaInfoFragment_to_playerActivity"
app:destination="@id/playerActivity" /> app:destination="@id/playerActivity" />
<argument
android:name="itemType"
app:argType="string" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/seasonFragment" android:id="@+id/seasonFragment"
@ -134,5 +137,8 @@
<argument <argument
android:name="itemId" android:name="itemId"
app:argType="java.util.UUID" /> app:argType="java.util.UUID" />
<argument
android:name="mediaSourceId"
app:argType="string" />
</activity> </activity>
</navigation> </navigation>