Rework player to allow for playing multiple episodes in a row
This commit is contained in:
parent
23a3937e86
commit
d67f195789
10 changed files with 122 additions and 103 deletions
|
@ -33,14 +33,14 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
playerView.player = it
|
playerView.player = it
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.playbackStateListener.navigateBack.observe(this, {
|
viewModel.navigateBack.observe(this, {
|
||||||
if (it) {
|
if (it) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (viewModel.player.value == null) {
|
if (viewModel.player.value == null) {
|
||||||
viewModel.initializePlayer(args.itemId, args.mediaSourceId, args.playbackPosition)
|
viewModel.initializePlayer(args.items, args.playbackPosition)
|
||||||
}
|
}
|
||||||
hideSystemUI()
|
hideSystemUI()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.jdtech.jellyfin.R
|
import dev.jdtech.jellyfin.R
|
||||||
import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding
|
import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding
|
||||||
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
|
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -33,14 +34,11 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
|
||||||
binding.viewModel = viewModel
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
binding.playButton.setOnClickListener {
|
binding.playButton.setOnClickListener {
|
||||||
viewModel.mediaSources.value?.get(0)?.id?.let { mediaSourceId ->
|
|
||||||
navigateToPlayerActivity(
|
navigateToPlayerActivity(
|
||||||
args.episodeId,
|
viewModel.playerItems.toTypedArray(),
|
||||||
mediaSourceId,
|
|
||||||
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
|
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
binding.checkButton.setOnClickListener {
|
binding.checkButton.setOnClickListener {
|
||||||
when (viewModel.played.value) {
|
when (viewModel.played.value) {
|
||||||
|
@ -95,14 +93,12 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToPlayerActivity(
|
private fun navigateToPlayerActivity(
|
||||||
itemId: UUID,
|
playerItems: Array<PlayerItem>,
|
||||||
mediaSourceId: String,
|
|
||||||
playbackPosition: Long
|
playbackPosition: Long
|
||||||
) {
|
) {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
EpisodeBottomSheetFragmentDirections.actionEpisodeBottomSheetFragmentToPlayerActivity(
|
EpisodeBottomSheetFragmentDirections.actionEpisodeBottomSheetFragmentToPlayerActivity(
|
||||||
itemId,
|
playerItems,
|
||||||
mediaSourceId,
|
|
||||||
playbackPosition
|
playbackPosition
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,9 +17,9 @@ 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.dialogs.VideoVersionDialogFragment
|
||||||
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
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.*
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MediaInfoFragment : Fragment() {
|
class MediaInfoFragment : Fragment() {
|
||||||
|
@ -84,13 +84,10 @@ class MediaInfoFragment : Fragment() {
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.navigateToPlayer.observe(viewLifecycleOwner, { mediaSource ->
|
viewModel.navigateToPlayer.observe(viewLifecycleOwner, { mediaSource ->
|
||||||
mediaSource.id?.let {
|
|
||||||
navigateToPlayerActivity(
|
navigateToPlayerActivity(
|
||||||
args.itemId,
|
arrayOf(PlayerItem(args.itemId, mediaSource.id!!)),
|
||||||
it,
|
|
||||||
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
|
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.played.observe(viewLifecycleOwner, {
|
viewModel.played.observe(viewLifecycleOwner, {
|
||||||
|
@ -139,9 +136,8 @@ class MediaInfoFragment : Fragment() {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
navigateToPlayerActivity(
|
navigateToPlayerActivity(
|
||||||
args.itemId,
|
arrayOf(PlayerItem(args.itemId, viewModel.mediaSources.value!![0].id!!)),
|
||||||
viewModel.mediaSources.value!![0].id!!,
|
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000),
|
||||||
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,14 +181,12 @@ class MediaInfoFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToPlayerActivity(
|
private fun navigateToPlayerActivity(
|
||||||
itemId: UUID,
|
playerItems: Array<PlayerItem>,
|
||||||
mediaSourceId: String,
|
playbackPosition: Long,
|
||||||
playbackPosition: Long
|
|
||||||
) {
|
) {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
MediaInfoFragmentDirections.actionMediaInfoFragmentToPlayerActivity(
|
MediaInfoFragmentDirections.actionMediaInfoFragmentToPlayerActivity(
|
||||||
itemId,
|
playerItems,
|
||||||
mediaSourceId,
|
|
||||||
playbackPosition
|
playbackPosition
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
11
app/src/main/java/dev/jdtech/jellyfin/models/PlayerItem.kt
Normal file
11
app/src/main/java/dev/jdtech/jellyfin/models/PlayerItem.kt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.jdtech.jellyfin.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PlayerItem(
|
||||||
|
val itemId: UUID,
|
||||||
|
val mediaSourceId: String
|
||||||
|
) : Parcelable
|
|
@ -24,7 +24,12 @@ interface JellyfinRepository {
|
||||||
|
|
||||||
suspend fun getNextUp(seriesId: UUID? = null): List<BaseItemDto>
|
suspend fun getNextUp(seriesId: UUID? = null): List<BaseItemDto>
|
||||||
|
|
||||||
suspend fun getEpisodes(seriesId: UUID, seasonId: UUID, fields: List<ItemFields>? = null): List<BaseItemDto>
|
suspend fun getEpisodes(
|
||||||
|
seriesId: UUID,
|
||||||
|
seasonId: UUID,
|
||||||
|
fields: List<ItemFields>? = null,
|
||||||
|
startIndex: Int? = null
|
||||||
|
): List<BaseItemDto>
|
||||||
|
|
||||||
suspend fun getMediaSources(itemId: UUID): List<MediaSourceInfo>
|
suspend fun getMediaSources(itemId: UUID): List<MediaSourceInfo>
|
||||||
|
|
||||||
|
|
|
@ -105,12 +105,17 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
|
||||||
override suspend fun getEpisodes(
|
override suspend fun getEpisodes(
|
||||||
seriesId: UUID,
|
seriesId: UUID,
|
||||||
seasonId: UUID,
|
seasonId: UUID,
|
||||||
fields: List<ItemFields>?
|
fields: List<ItemFields>?,
|
||||||
|
startIndex: Int?
|
||||||
): List<BaseItemDto> {
|
): List<BaseItemDto> {
|
||||||
val episodes: List<BaseItemDto>
|
val episodes: List<BaseItemDto>
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
episodes = jellyfinApi.showsApi.getEpisodes(
|
episodes = jellyfinApi.showsApi.getEpisodes(
|
||||||
seriesId, jellyfinApi.userId!!, seasonId = seasonId, fields = fields
|
seriesId,
|
||||||
|
jellyfinApi.userId!!,
|
||||||
|
seasonId = seasonId,
|
||||||
|
fields = fields,
|
||||||
|
startIndex = startIndex
|
||||||
).content.items ?: listOf()
|
).content.items ?: listOf()
|
||||||
}
|
}
|
||||||
return episodes
|
return episodes
|
||||||
|
|
|
@ -6,10 +6,10 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
@ -32,15 +32,14 @@ constructor(
|
||||||
private val _dateString = MutableLiveData<String>()
|
private val _dateString = MutableLiveData<String>()
|
||||||
val dateString: LiveData<String> = _dateString
|
val dateString: LiveData<String> = _dateString
|
||||||
|
|
||||||
private val _mediaSources = MutableLiveData<List<MediaSourceInfo>>()
|
|
||||||
val mediaSources: LiveData<List<MediaSourceInfo>> = _mediaSources
|
|
||||||
|
|
||||||
private val _played = MutableLiveData<Boolean>()
|
private val _played = MutableLiveData<Boolean>()
|
||||||
val played: LiveData<Boolean> = _played
|
val played: LiveData<Boolean> = _played
|
||||||
|
|
||||||
private val _favorite = MutableLiveData<Boolean>()
|
private val _favorite = MutableLiveData<Boolean>()
|
||||||
val favorite: LiveData<Boolean> = _favorite
|
val favorite: LiveData<Boolean> = _favorite
|
||||||
|
|
||||||
|
var playerItems: MutableList<PlayerItem> = mutableListOf()
|
||||||
|
|
||||||
fun loadEpisode(episodeId: UUID) {
|
fun loadEpisode(episodeId: UUID) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
|
@ -48,7 +47,7 @@ constructor(
|
||||||
_item.value = item
|
_item.value = item
|
||||||
_runTime.value = "${item.runTimeTicks?.div(600000000)} min"
|
_runTime.value = "${item.runTimeTicks?.div(600000000)} min"
|
||||||
_dateString.value = getDateString(item)
|
_dateString.value = getDateString(item)
|
||||||
_mediaSources.value = jellyfinRepository.getMediaSources(episodeId)
|
createPlayerItems(item)
|
||||||
_played.value = item.userData?.played
|
_played.value = item.userData?.played
|
||||||
_favorite.value = item.userData?.isFavorite
|
_favorite.value = item.userData?.isFavorite
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -57,6 +56,14 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun createPlayerItems(startEpisode: BaseItemDto) {
|
||||||
|
val episodes = jellyfinRepository.getEpisodes(startEpisode.seriesId!!, startEpisode.seasonId!!, startIndex = startEpisode.indexNumber?.minus(1))
|
||||||
|
for (episode in episodes) {
|
||||||
|
val mediaSources = jellyfinRepository.getMediaSources(episode.id)
|
||||||
|
playerItems.add(PlayerItem(episode.id, mediaSources[0].id!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun markAsPlayed(itemId: UUID) {
|
fun markAsPlayed(itemId: UUID) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
jellyfinRepository.markAsPlayed(itemId)
|
jellyfinRepository.markAsPlayed(itemId)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.preference.PreferenceManager
|
||||||
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 dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -24,29 +25,23 @@ class PlayerActivityViewModel
|
||||||
constructor(
|
constructor(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val jellyfinRepository: JellyfinRepository
|
private val jellyfinRepository: JellyfinRepository
|
||||||
) : ViewModel() {
|
) : ViewModel(), Player.Listener {
|
||||||
private var _player = MutableLiveData<SimpleExoPlayer>()
|
private var _player = MutableLiveData<SimpleExoPlayer>()
|
||||||
var player: LiveData<SimpleExoPlayer> = _player
|
var player: LiveData<SimpleExoPlayer> = _player
|
||||||
|
|
||||||
|
private val _navigateBack = MutableLiveData<Boolean>()
|
||||||
|
val navigateBack: LiveData<Boolean> = _navigateBack
|
||||||
|
|
||||||
private var playWhenReady = true
|
private var playWhenReady = true
|
||||||
private var currentWindow = 0
|
private var currentWindow = 0
|
||||||
private var playbackPosition: Long = 0
|
private var playbackPosition: Long = 0
|
||||||
private var _playbackStateListener: PlaybackStateListener
|
|
||||||
|
|
||||||
private var itemId: UUID? = null
|
|
||||||
|
|
||||||
val playbackStateListener: PlaybackStateListener
|
|
||||||
get() = _playbackStateListener
|
|
||||||
|
|
||||||
private val sp = PreferenceManager.getDefaultSharedPreferences(application)
|
private val sp = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
|
|
||||||
init {
|
fun initializePlayer(
|
||||||
_playbackStateListener = PlaybackStateListener()
|
items: Array<PlayerItem>,
|
||||||
}
|
playbackPosition: Long
|
||||||
|
) {
|
||||||
fun initializePlayer(itemId: UUID, mediaSourceId: String, playbackPosition: Long) {
|
|
||||||
this.itemId = itemId
|
|
||||||
|
|
||||||
val renderersFactory =
|
val renderersFactory =
|
||||||
DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
|
DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
|
||||||
|
@ -61,33 +56,38 @@ constructor(
|
||||||
.setTrackSelector(trackSelector)
|
.setTrackSelector(trackSelector)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
player.addListener(_playbackStateListener)
|
player.addListener(this)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val streamUrl = jellyfinRepository.getStreamUrl(itemId, mediaSourceId)
|
val mediaItems: MutableList<MediaItem> = mutableListOf()
|
||||||
|
|
||||||
|
for (item in items) {
|
||||||
|
val streamUrl = jellyfinRepository.getStreamUrl(item.itemId, item.mediaSourceId)
|
||||||
Timber.d("Stream url: $streamUrl")
|
Timber.d("Stream url: $streamUrl")
|
||||||
val mediaItem =
|
val mediaItem =
|
||||||
MediaItem.Builder()
|
MediaItem.Builder()
|
||||||
.setMediaId(itemId.toString())
|
.setMediaId(item.itemId.toString())
|
||||||
.setUri(streamUrl)
|
.setUri(streamUrl)
|
||||||
.build()
|
.build()
|
||||||
player.setMediaItem(mediaItem, playbackPosition)
|
mediaItems.add(mediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
player.setMediaItems(mediaItems, currentWindow, playbackPosition)
|
||||||
player.playWhenReady = playWhenReady
|
player.playWhenReady = playWhenReady
|
||||||
player.prepare()
|
player.prepare()
|
||||||
_player.value = player
|
_player.value = player
|
||||||
|
|
||||||
jellyfinRepository.postPlaybackStart(itemId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pollPosition(player, itemId)
|
pollPosition(player)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun releasePlayer() {
|
private fun releasePlayer() {
|
||||||
itemId?.let { itemId ->
|
|
||||||
_player.value?.let { player ->
|
_player.value?.let { player ->
|
||||||
runBlocking {
|
runBlocking {
|
||||||
jellyfinRepository.postPlaybackStop(itemId, player.currentPosition.times(10000))
|
jellyfinRepository.postPlaybackStop(
|
||||||
}
|
UUID.fromString(player.currentMediaItem?.mediaId),
|
||||||
|
player.currentPosition.times(10000)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,32 +95,37 @@ constructor(
|
||||||
playWhenReady = player.value!!.playWhenReady
|
playWhenReady = player.value!!.playWhenReady
|
||||||
playbackPosition = player.value!!.currentPosition
|
playbackPosition = player.value!!.currentPosition
|
||||||
currentWindow = player.value!!.currentWindowIndex
|
currentWindow = player.value!!.currentWindowIndex
|
||||||
player.value!!.removeListener(_playbackStateListener)
|
player.value!!.removeListener(this)
|
||||||
player.value!!.release()
|
player.value!!.release()
|
||||||
_player.value = null
|
_player.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollPosition(player: SimpleExoPlayer, itemId: UUID) {
|
private fun pollPosition(player: SimpleExoPlayer) {
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
val runnable: Runnable = object : Runnable {
|
val runnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
if (player.currentMediaItem != null) {
|
||||||
jellyfinRepository.postPlaybackProgress(
|
jellyfinRepository.postPlaybackProgress(
|
||||||
itemId,
|
UUID.fromString(player.currentMediaItem!!.mediaId),
|
||||||
player.currentPosition.times(10000),
|
player.currentPosition.times(10000),
|
||||||
!player.isPlaying
|
!player.isPlaying
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
handler.postDelayed(this, 2000)
|
handler.postDelayed(this, 2000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler.post(runnable)
|
handler.post(runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlaybackStateListener : Player.Listener {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
private val _navigateBack = MutableLiveData<Boolean>()
|
Timber.d("Playing MediaItem: ${mediaItem?.mediaId}")
|
||||||
val navigateBack: LiveData<Boolean> = _navigateBack
|
viewModelScope.launch {
|
||||||
|
jellyfinRepository.postPlaybackStart(UUID.fromString(mediaItem?.mediaId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(state: Int) {
|
override fun onPlaybackStateChanged(state: Int) {
|
||||||
var stateString = "UNKNOWN_STATE -"
|
var stateString = "UNKNOWN_STATE -"
|
||||||
|
@ -141,7 +146,6 @@ constructor(
|
||||||
}
|
}
|
||||||
Timber.d("Changed player state to $stateString")
|
Timber.d("Changed player state to $stateString")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
|
|
@ -151,11 +151,8 @@
|
||||||
android:label="activity_player"
|
android:label="activity_player"
|
||||||
tools:layout="@layout/activity_player">
|
tools:layout="@layout/activity_player">
|
||||||
<argument
|
<argument
|
||||||
android:name="itemId"
|
android:name="items"
|
||||||
app:argType="java.util.UUID" />
|
app:argType="dev.jdtech.jellyfin.models.PlayerItem[]" />
|
||||||
<argument
|
|
||||||
android:name="mediaSourceId"
|
|
||||||
app:argType="string" />
|
|
||||||
<argument
|
<argument
|
||||||
android:name="playbackPosition"
|
android:name="playbackPosition"
|
||||||
app:argType="long" />
|
app:argType="long" />
|
||||||
|
|
Loading…
Reference in a new issue