diff --git a/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index e8fdce78..48e59dd8 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -4,11 +4,19 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import android.view.WindowManager +import android.widget.ImageButton import android.widget.TextView import androidx.activity.viewModels import androidx.navigation.navArgs +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.SimpleExoPlayer +import com.google.android.exoplayer2.trackselection.MappingTrackSelector +import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder import dagger.hilt.android.AndroidEntryPoint import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding +import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment +import dev.jdtech.jellyfin.mpv.MPVPlayer +import dev.jdtech.jellyfin.mpv.TrackType import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel import timber.log.Timber @@ -37,6 +45,71 @@ class PlayerActivity : AppCompatActivity() { videoNameTextView.text = title }) + val audioButton = binding.playerView.findViewById(R.id.btn_audio_track) + val subtitleButton = binding.playerView.findViewById(R.id.btn_subtitle) + + audioButton.setOnClickListener { + when (viewModel.player) { + is MPVPlayer -> { + TrackSelectionDialogFragment(TrackType.AUDIO, viewModel).show( + supportFragmentManager, + "trackselectiondialog" + ) + } + is SimpleExoPlayer -> { + 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, "Select audio track", + viewModel.trackSelector, audioRenderer + ) + val trackSelectionDialog = trackSelectionDialogBuilder.build() + trackSelectionDialog.show() + } + } + } + + subtitleButton.setOnClickListener { + when (viewModel.player) { + is MPVPlayer -> { + TrackSelectionDialogFragment(TrackType.SUBTITLE, viewModel).show( + supportFragmentManager, + "trackselectiondialog" + ) + } + is SimpleExoPlayer -> { + 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, "Select subtitle track", + viewModel.trackSelector, subtitleRenderer + ) + val trackSelectionDialog = trackSelectionDialogBuilder.build() + trackSelectionDialog.show() + } + } + } + viewModel.navigateBack.observe(this, { if (it) { onBackPressed() @@ -68,5 +141,18 @@ class PlayerActivity : AppCompatActivity() { window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) } + + private fun isRendererType( + mappedTrackInfo: MappingTrackSelector.MappedTrackInfo, + rendererIndex: Int, + type: Int + ): Boolean { + val trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex) + if (trackGroupArray.length == 0) { + return false + } + val trackType = mappedTrackInfo.getRendererType(rendererIndex) + return type == trackType + } } diff --git a/app/src/main/java/dev/jdtech/jellyfin/dialogs/TrackSelectionDialogFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/dialogs/TrackSelectionDialogFragment.kt new file mode 100644 index 00000000..1fb3a0ff --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/dialogs/TrackSelectionDialogFragment.kt @@ -0,0 +1,63 @@ +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.mpv.TrackType +import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel +import java.lang.IllegalStateException + +class TrackSelectionDialogFragment( + private val type: String, + private val viewModel: PlayerActivityViewModel +): DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val trackNames: List + when (type) { + TrackType.AUDIO -> { + trackNames = viewModel.currentAudioTracks.map { + if (it.title.isEmpty()) { + "${it.lang} - ${it.codec}" + } else { + "${it.title} - ${it.lang} - ${it.codec}" + } + } + return activity?.let { + val builder = AlertDialog.Builder(it) + builder.setTitle("Select audio track") + .setItems(trackNames.toTypedArray()) { _, which -> + viewModel.switchToTrack(TrackType.AUDIO, viewModel.currentAudioTracks[which]) + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + TrackType.SUBTITLE -> { + trackNames = viewModel.currentSubtitleTracks.map { + if (it.title.isEmpty()) { + "${it.lang} - ${it.codec}" + } else { + "${it.title} - ${it.lang} - ${it.codec}" + } + } + return activity?.let { + val builder = AlertDialog.Builder(it) + builder.setTitle("Select subtitle track") + .setItems(trackNames.toTypedArray()) { _, which -> + viewModel.switchToTrack(TrackType.SUBTITLE, viewModel.currentSubtitleTracks[which]) + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + else -> { + trackNames = listOf() + return activity?.let { + val builder = AlertDialog.Builder(it) + builder.setTitle("Select ? track") + .setMessage("Unknown track type") + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt b/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt index 998d34b1..b18568eb 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt @@ -140,7 +140,7 @@ class MPVPlayer( private var currentPositionMs: Long? = null private var currentDurationMs: Long? = null private var currentCacheDurationMs: Long? = null - private var currentTracks: List = emptyList() + var currentTracks: List = emptyList() private var initialCommands = mutableListOf>() private var initialSeekTo: Long = 0L diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 8ad468b6..c86b787a 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -13,6 +13,7 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.mpv.MPVPlayer +import dev.jdtech.jellyfin.mpv.TrackType import dev.jdtech.jellyfin.repository.JellyfinRepository import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -27,7 +28,7 @@ constructor( application: Application, private val jellyfinRepository: JellyfinRepository ) : ViewModel(), Player.Listener { - var player: BasePlayer + val player: BasePlayer private val _navigateBack = MutableLiveData() val navigateBack: LiveData = _navigateBack @@ -35,8 +36,12 @@ constructor( private val _currentItemTitle = MutableLiveData() val currentItemTitle: LiveData = _currentItemTitle + var currentAudioTracks: MutableList = mutableListOf() + var currentSubtitleTracks: MutableList = mutableListOf() + private var items: Array = arrayOf() + val trackSelector = DefaultTrackSelector(application) var playWhenReady = true private var currentWindow = 0 private var playbackPosition: Long = 0 @@ -51,7 +56,6 @@ constructor( } else { val renderersFactory = DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) - val trackSelector = DefaultTrackSelector(application) trackSelector.setParameters( trackSelector.buildUponParameters() .setTunnelingEnabled(true) @@ -175,6 +179,25 @@ constructor( } ExoPlayer.STATE_READY -> { stateString = "ExoPlayer.STATE_READY -" + currentAudioTracks.clear() + currentSubtitleTracks.clear() + when (player) { + is MPVPlayer -> { + player.currentTracks.forEach { + when (it.type) { + TrackType.AUDIO -> { + currentAudioTracks.add(it) + } + TrackType.SUBTITLE -> { + currentSubtitleTracks.add(it) + } + } + } + } + is SimpleExoPlayer -> { + Timber.d(player.currentTrackGroups.length.toString()) + } + } } ExoPlayer.STATE_ENDED -> { stateString = "ExoPlayer.STATE_ENDED -" @@ -189,4 +212,12 @@ constructor( Timber.d("Clearing Player ViewModel") releasePlayer() } + + fun switchToTrack(trackType: String, track: MPVPlayer.Companion.Track) { + if (player is MPVPlayer) { + player.selectTrack(trackType, isExternal = false, index = track.ffIndex) + } else if (player is SimpleExoPlayer) { + + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_player.xml b/app/src/main/res/layout/activity_player.xml index 428cc72d..d8ae92df 100644 --- a/app/src/main/res/layout/activity_player.xml +++ b/app/src/main/res/layout/activity_player.xml @@ -6,14 +6,11 @@ android:layout_height="match_parent" tools:context=".PlayerActivity"> - + app:show_buffering="always" /> diff --git a/app/src/main/res/layout/exo_player_styled_control_view.xml b/app/src/main/res/layout/exo_player_control_view.xml similarity index 98% rename from app/src/main/res/layout/exo_player_styled_control_view.xml rename to app/src/main/res/layout/exo_player_control_view.xml index 89eedf8e..06c6f37f 100644 --- a/app/src/main/res/layout/exo_player_styled_control_view.xml +++ b/app/src/main/res/layout/exo_player_control_view.xml @@ -55,7 +55,7 @@ app:layout_constraintTop_toTopOf="parent">