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:supportsRtl="true"
android:theme="@style/Theme.Jellyfin">
<activity android:name=".PlayerActivity"></activity>
<activity android:name=".PlayerActivity" />
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" />

View file

@ -34,7 +34,7 @@ class PlayerActivity : AppCompatActivity() {
})
if (viewModel.player.value == null) {
viewModel.initializePlayer(args.itemId)
viewModel.initializePlayer(args.itemId, args.mediaSourceId)
}
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(
HomeFragmentDirections.actionNavigationHomeToMediaInfoFragment(
item.id,
item.name
item.name,
item.type ?: "Unknown"
)
)
}

View file

@ -47,7 +47,8 @@ class LibraryFragment : Fragment() {
findNavController().navigate(
LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
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.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
)
)
}
}

View file

@ -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<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,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<MediaSourceInfo> {
val mediaSourceInfoList: List<MediaSourceInfo>
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
}

View file

@ -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<List<BaseItemDto>>()
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 {
_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
}
}

View file

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

View file

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