Select audio and subtitle tracks

This commit is contained in:
jarnedemeulemeester 2021-09-15 23:13:59 +02:00
parent 6d340bd7ab
commit 9cddd50d0e
No known key found for this signature in database
GPG key ID: B61B7B150DB6A6D2
6 changed files with 188 additions and 11 deletions

View file

@ -4,11 +4,19 @@ import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.navigation.navArgs 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 dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding 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 dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import timber.log.Timber import timber.log.Timber
@ -37,6 +45,71 @@ class PlayerActivity : AppCompatActivity() {
videoNameTextView.text = title videoNameTextView.text = title
}) })
val audioButton = binding.playerView.findViewById<ImageButton>(R.id.btn_audio_track)
val subtitleButton = binding.playerView.findViewById<ImageButton>(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, { viewModel.navigateBack.observe(this, {
if (it) { if (it) {
onBackPressed() onBackPressed()
@ -68,5 +141,18 @@ class PlayerActivity : AppCompatActivity() {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) 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
}
} }

View file

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

View file

@ -140,7 +140,7 @@ class MPVPlayer(
private var currentPositionMs: Long? = null private var currentPositionMs: Long? = null
private var currentDurationMs: Long? = null private var currentDurationMs: Long? = null
private var currentCacheDurationMs: Long? = null private var currentCacheDurationMs: Long? = null
private var currentTracks: List<Track> = emptyList() var currentTracks: List<Track> = emptyList()
private var initialCommands = mutableListOf<Array<String>>() private var initialCommands = mutableListOf<Array<String>>()
private var initialSeekTo: Long = 0L private var initialSeekTo: Long = 0L

View file

@ -13,6 +13,7 @@ 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.models.PlayerItem
import dev.jdtech.jellyfin.mpv.MPVPlayer import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.mpv.TrackType
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
@ -27,7 +28,7 @@ constructor(
application: Application, application: Application,
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel(), Player.Listener { ) : ViewModel(), Player.Listener {
var player: BasePlayer val player: BasePlayer
private val _navigateBack = MutableLiveData<Boolean>() private val _navigateBack = MutableLiveData<Boolean>()
val navigateBack: LiveData<Boolean> = _navigateBack val navigateBack: LiveData<Boolean> = _navigateBack
@ -35,8 +36,12 @@ constructor(
private val _currentItemTitle = MutableLiveData<String>() private val _currentItemTitle = MutableLiveData<String>()
val currentItemTitle: LiveData<String> = _currentItemTitle val currentItemTitle: LiveData<String> = _currentItemTitle
var currentAudioTracks: MutableList<MPVPlayer.Companion.Track> = mutableListOf()
var currentSubtitleTracks: MutableList<MPVPlayer.Companion.Track> = mutableListOf()
private var items: Array<PlayerItem> = arrayOf() private var items: Array<PlayerItem> = arrayOf()
val trackSelector = DefaultTrackSelector(application)
var playWhenReady = true var playWhenReady = true
private var currentWindow = 0 private var currentWindow = 0
private var playbackPosition: Long = 0 private var playbackPosition: Long = 0
@ -51,7 +56,6 @@ constructor(
} else { } else {
val renderersFactory = val renderersFactory =
DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) DefaultRenderersFactory(application).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
val trackSelector = DefaultTrackSelector(application)
trackSelector.setParameters( trackSelector.setParameters(
trackSelector.buildUponParameters() trackSelector.buildUponParameters()
.setTunnelingEnabled(true) .setTunnelingEnabled(true)
@ -175,6 +179,25 @@ constructor(
} }
ExoPlayer.STATE_READY -> { ExoPlayer.STATE_READY -> {
stateString = "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 -> { ExoPlayer.STATE_ENDED -> {
stateString = "ExoPlayer.STATE_ENDED -" stateString = "ExoPlayer.STATE_ENDED -"
@ -189,4 +212,12 @@ constructor(
Timber.d("Clearing Player ViewModel") Timber.d("Clearing Player ViewModel")
releasePlayer() 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) {
}
}
} }

View file

@ -6,14 +6,11 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".PlayerActivity"> tools:context=".PlayerActivity">
<com.google.android.exoplayer2.ui.StyledPlayerView <com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view" android:id="@+id/player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black" android:background="@color/black"
app:show_subtitle_button="true" app:show_buffering="always" />
app:show_buffering="always"
app:controller_layout_id="@layout/exo_player_styled_control_view"
app:animation_enabled="false"/>
</FrameLayout> </FrameLayout>

View file

@ -55,7 +55,7 @@
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<ImageButton <ImageButton
android:id="@+id/exo_audio_track" android:id="@+id/btn_audio_track"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
@ -68,7 +68,7 @@
android:layout_height="0dp" /> android:layout_height="0dp" />
<ImageButton <ImageButton
android:id="@+id/exo_subtitle" android:id="@+id/btn_subtitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
@ -106,7 +106,7 @@
android:src="@drawable/ic_rewind" /> android:src="@drawable/ic_rewind" />
<ImageButton <ImageButton
android:id="@+id/exo_play_pause" android:id="@+id/exo_play"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/circle_background" android:background="@drawable/circle_background"