Get mediaSources and select version of movie
This commit is contained in:
parent
7f5dea58bc
commit
e69a653453
11 changed files with 133 additions and 61 deletions
|
@ -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" />
|
||||
|
|
|
@ -34,7 +34,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
})
|
||||
|
||||
if (viewModel.player.value == null) {
|
||||
viewModel.initializePlayer(args.itemId)
|
||||
viewModel.initializePlayer(args.itemId, args.mediaSourceId)
|
||||
}
|
||||
hideSystemUI()
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -81,7 +81,8 @@ class HomeFragment : Fragment() {
|
|||
findNavController().navigate(
|
||||
HomeFragmentDirections.actionNavigationHomeToMediaInfoFragment(
|
||||
item.id,
|
||||
item.name
|
||||
item.name,
|
||||
item.type ?: "Unknown"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ class LibraryFragment : Fragment() {
|
|||
findNavController().navigate(
|
||||
LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
|
||||
item.id,
|
||||
item.name
|
||||
item.name,
|
||||
item.type ?: "Unknown"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue