feat(phone): use custom track selection dialog for default player
Use media3 track type instead of mpv track type Simplify track selection dialog
This commit is contained in:
parent
db5eab1ab2
commit
55427036b2
5 changed files with 49 additions and 128 deletions
|
@ -27,18 +27,15 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.DefaultTimeBar
|
import androidx.media3.ui.DefaultTimeBar
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import androidx.media3.ui.TrackSelectionDialogBuilder
|
|
||||||
import androidx.navigation.navArgs
|
import androidx.navigation.navArgs
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding
|
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding
|
||||||
import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
|
import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
|
||||||
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
|
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
|
||||||
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
||||||
import dev.jdtech.jellyfin.mpv.TrackType
|
|
||||||
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
||||||
import dev.jdtech.jellyfin.utils.PreviewScrubListener
|
import dev.jdtech.jellyfin.utils.PreviewScrubListener
|
||||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||||
|
@ -46,7 +43,6 @@ import dev.jdtech.jellyfin.viewmodels.PlayerEvents
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import dev.jdtech.jellyfin.player.video.R as PlayerVideoR
|
|
||||||
|
|
||||||
var isControlsLocked: Boolean = false
|
var isControlsLocked: Boolean = false
|
||||||
|
|
||||||
|
@ -201,39 +197,11 @@ class PlayerActivity : BasePlayerActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
audioButton.setOnClickListener {
|
audioButton.setOnClickListener {
|
||||||
when (viewModel.player) {
|
TrackSelectionDialogFragment(C.TRACK_TYPE_AUDIO, viewModel).show(
|
||||||
is MPVPlayer -> {
|
|
||||||
TrackSelectionDialogFragment(TrackType.AUDIO, viewModel).show(
|
|
||||||
supportFragmentManager,
|
supportFragmentManager,
|
||||||
"trackselectiondialog",
|
"trackselectiondialog",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExoPlayer -> {
|
|
||||||
val mappedTrackInfo =
|
|
||||||
viewModel.trackSelector.currentMappedTrackInfo ?: return@setOnClickListener
|
|
||||||
|
|
||||||
var audioRenderer: Int? = null
|
|
||||||
for (i in 0 until mappedTrackInfo.rendererCount) {
|
|
||||||
if (isRendererType(mappedTrackInfo, i, C.TRACK_TYPE_AUDIO)) {
|
|
||||||
audioRenderer = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioRenderer == null) return@setOnClickListener
|
|
||||||
|
|
||||||
val trackSelectionDialogBuilder = TrackSelectionDialogBuilder(
|
|
||||||
this,
|
|
||||||
resources.getString(PlayerVideoR.string.select_audio_track),
|
|
||||||
viewModel.player,
|
|
||||||
C.TRACK_TYPE_AUDIO,
|
|
||||||
)
|
|
||||||
trackSelectionDialogBuilder.setShowDisableOption(true)
|
|
||||||
|
|
||||||
val trackSelectionDialog = trackSelectionDialogBuilder.build()
|
|
||||||
trackSelectionDialog.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val exoPlayerControlView = findViewById<FrameLayout>(R.id.player_controls)
|
val exoPlayerControlView = findViewById<FrameLayout>(R.id.player_controls)
|
||||||
val lockedLayout = findViewById<FrameLayout>(R.id.locked_player_view)
|
val lockedLayout = findViewById<FrameLayout>(R.id.locked_player_view)
|
||||||
|
@ -253,39 +221,11 @@ class PlayerActivity : BasePlayerActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
subtitleButton.setOnClickListener {
|
subtitleButton.setOnClickListener {
|
||||||
when (viewModel.player) {
|
TrackSelectionDialogFragment(C.TRACK_TYPE_TEXT, viewModel).show(
|
||||||
is MPVPlayer -> {
|
|
||||||
TrackSelectionDialogFragment(TrackType.SUBTITLE, viewModel).show(
|
|
||||||
supportFragmentManager,
|
supportFragmentManager,
|
||||||
"trackselectiondialog",
|
"trackselectiondialog",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExoPlayer -> {
|
|
||||||
val mappedTrackInfo =
|
|
||||||
viewModel.trackSelector.currentMappedTrackInfo ?: return@setOnClickListener
|
|
||||||
|
|
||||||
var subtitleRenderer: Int? = null
|
|
||||||
for (i in 0 until mappedTrackInfo.rendererCount) {
|
|
||||||
if (isRendererType(mappedTrackInfo, i, C.TRACK_TYPE_TEXT)) {
|
|
||||||
subtitleRenderer = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subtitleRenderer == null) return@setOnClickListener
|
|
||||||
|
|
||||||
val trackSelectionDialogBuilder = TrackSelectionDialogBuilder(
|
|
||||||
this,
|
|
||||||
resources.getString(PlayerVideoR.string.select_subtile_track),
|
|
||||||
viewModel.player,
|
|
||||||
C.TRACK_TYPE_TEXT,
|
|
||||||
)
|
|
||||||
trackSelectionDialogBuilder.setShowDisableOption(true)
|
|
||||||
|
|
||||||
val trackSelectionDialog = trackSelectionDialogBuilder.build()
|
|
||||||
trackSelectionDialog.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
speedButton.setOnClickListener {
|
speedButton.setOnClickListener {
|
||||||
SpeedSelectionDialogFragment(viewModel).show(
|
SpeedSelectionDialogFragment(viewModel).show(
|
||||||
|
|
|
@ -124,7 +124,7 @@ fun PlayerScreen(
|
||||||
val trackType = result.value.trackType
|
val trackType = result.value.trackType
|
||||||
val index = result.value.index
|
val index = result.value.index
|
||||||
|
|
||||||
if (result.value.index == -1) {
|
if (index == -1) {
|
||||||
viewModel.player.trackSelectionParameters = viewModel.player.trackSelectionParameters
|
viewModel.player.trackSelectionParameters = viewModel.player.trackSelectionParameters
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.clearOverridesOfType(trackType)
|
.clearOverridesOfType(trackType)
|
||||||
|
|
|
@ -6,28 +6,31 @@ import androidx.fragment.app.DialogFragment
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dev.jdtech.jellyfin.getTrackNames
|
import dev.jdtech.jellyfin.getTrackNames
|
||||||
import dev.jdtech.jellyfin.mpv.TrackType
|
|
||||||
import dev.jdtech.jellyfin.player.video.R
|
import dev.jdtech.jellyfin.player.video.R
|
||||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
class TrackSelectionDialogFragment(
|
class TrackSelectionDialogFragment(
|
||||||
private val type: TrackType,
|
private val type: @C.TrackType Int,
|
||||||
private val viewModel: PlayerActivityViewModel,
|
private val viewModel: PlayerActivityViewModel,
|
||||||
) : DialogFragment() {
|
) : DialogFragment() {
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
when (type) {
|
val titleResource = when (type) {
|
||||||
TrackType.AUDIO -> {
|
C.TRACK_TYPE_AUDIO -> R.string.select_audio_track
|
||||||
|
C.TRACK_TYPE_TEXT -> R.string.select_subtile_track
|
||||||
|
else -> throw IllegalStateException("TrackType must be AUDIO or TEXT")
|
||||||
|
}
|
||||||
|
val tracksGroups = viewModel.player.currentTracks.groups.filter { it.type == type && it.isSupported }
|
||||||
return activity?.let { activity ->
|
return activity?.let { activity ->
|
||||||
val builder = MaterialAlertDialogBuilder(activity)
|
val builder = MaterialAlertDialogBuilder(activity)
|
||||||
val tracksGroups = viewModel.player.currentTracks.groups.filter { it.type == C.TRACK_TYPE_AUDIO }
|
builder
|
||||||
builder.setTitle(getString(R.string.select_audio_track))
|
.setTitle(getString(titleResource))
|
||||||
.setSingleChoiceItems(
|
.setSingleChoiceItems(
|
||||||
arrayOf(getString(R.string.none)) + tracksGroups.getTrackNames(), // Add "None" at the top of the list
|
arrayOf(getString(R.string.none)) + tracksGroups.getTrackNames(), // Add "None" at the top of the list
|
||||||
tracksGroups.indexOfFirst { it.isSelected } + 1, // Add 1 to the index to account for the "None" item
|
tracksGroups.indexOfFirst { it.isSelected } + 1, // Add 1 to the index to account for the "None" item
|
||||||
) { dialog, which ->
|
) { dialog, which ->
|
||||||
viewModel.switchToTrack(
|
viewModel.switchToTrack(
|
||||||
TrackType.AUDIO,
|
type,
|
||||||
which - 1, // Minus 1 to get the correct group without the "None" item. "None" becomes -1
|
which - 1, // Minus 1 to get the correct group without the "None" item. "None" becomes -1
|
||||||
)
|
)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
@ -35,27 +38,4 @@ class TrackSelectionDialogFragment(
|
||||||
builder.create()
|
builder.create()
|
||||||
} ?: throw IllegalStateException("Activity cannot be null")
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
}
|
}
|
||||||
TrackType.SUBTITLE -> {
|
|
||||||
return activity?.let { activity ->
|
|
||||||
val builder = MaterialAlertDialogBuilder(activity)
|
|
||||||
val tracksGroups = viewModel.player.currentTracks.groups.filter { it.type == C.TRACK_TYPE_TEXT }
|
|
||||||
builder.setTitle(getString(R.string.select_subtile_track))
|
|
||||||
.setSingleChoiceItems(
|
|
||||||
arrayOf(getString(R.string.none)) + tracksGroups.getTrackNames(),
|
|
||||||
tracksGroups.indexOfFirst { it.isSelected } + 1,
|
|
||||||
) { dialog, which ->
|
|
||||||
viewModel.switchToTrack(
|
|
||||||
TrackType.SUBTITLE,
|
|
||||||
which - 1,
|
|
||||||
)
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
builder.create()
|
|
||||||
} ?: throw IllegalStateException("Activity cannot be null")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
throw IllegalStateException("TrackType must be AUDIO or SUBTITLE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -354,7 +354,7 @@ class MPVPlayer(
|
||||||
* @param id Id to select or [C.INDEX_UNSET] to disable [TrackType]
|
* @param id Id to select or [C.INDEX_UNSET] to disable [TrackType]
|
||||||
* @return true if the track is or was already selected
|
* @return true if the track is or was already selected
|
||||||
*/
|
*/
|
||||||
fun selectTrack(
|
private fun selectTrack(
|
||||||
trackType: TrackType,
|
trackType: TrackType,
|
||||||
id: String,
|
id: String,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.TrackSelectionOverride
|
||||||
import androidx.media3.common.TrackSelectionParameters
|
import androidx.media3.common.TrackSelectionParameters
|
||||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
@ -20,7 +21,6 @@ import dev.jdtech.jellyfin.AppPreferences
|
||||||
import dev.jdtech.jellyfin.models.Intro
|
import dev.jdtech.jellyfin.models.Intro
|
||||||
import dev.jdtech.jellyfin.models.PlayerItem
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
||||||
import dev.jdtech.jellyfin.mpv.TrackType
|
|
||||||
import dev.jdtech.jellyfin.player.video.R
|
import dev.jdtech.jellyfin.player.video.R
|
||||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||||
import dev.jdtech.jellyfin.utils.bif.BifData
|
import dev.jdtech.jellyfin.utils.bif.BifData
|
||||||
|
@ -319,21 +319,22 @@ constructor(
|
||||||
releasePlayer()
|
releasePlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToTrack(trackType: TrackType, index: Int) {
|
fun switchToTrack(trackType: @C.TrackType Int, index: Int) {
|
||||||
if (player is MPVPlayer) {
|
|
||||||
// Index -1 equals disable track
|
// Index -1 equals disable track
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
player.selectTrack(trackType, id = "no")
|
player.trackSelectionParameters = player.trackSelectionParameters
|
||||||
return
|
.buildUpon()
|
||||||
}
|
.clearOverridesOfType(trackType)
|
||||||
|
.setTrackTypeDisabled(trackType, true)
|
||||||
// Get track to select based on index
|
.build()
|
||||||
val tracksGroup = player.currentTracks.groups.filter { TrackType.fromMedia3TrackType(it.type) == trackType }[index]
|
} else {
|
||||||
val format = tracksGroup.mediaTrackGroup.getFormat(0)
|
player.trackSelectionParameters = player.trackSelectionParameters
|
||||||
if (format.id == null) {
|
.buildUpon()
|
||||||
return
|
.setOverrideForType(
|
||||||
}
|
TrackSelectionOverride(player.currentTracks.groups.filter { it.type == trackType && it.isSupported }[index].mediaTrackGroup, 0),
|
||||||
player.selectTrack(trackType, id = format.id!!)
|
)
|
||||||
|
.setTrackTypeDisabled(trackType, false)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue