Upgrade ExoPlayer to 2.18 (#126)

* Upgrade ExoPlayer to 2.18

* Change the position polling from every 2 to every 5 sec

* Make internalMediaItems non-nullable

* Clean up mpv track names

* Reduce explayer ffmpeg extension size by not including all decoders

Removed decoders vorbis, opus, flac, pcm_mulaw, pcm_alaw, mp3, aac because these are already supported by Android.

* Clean up preferredLanguage preferences
This commit is contained in:
Jarne Demeulemeester 2022-07-03 14:02:32 +02:00 committed by GitHub
parent 6f0d5a13a8
commit bcdada538d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 197 deletions

View file

@ -25,7 +25,7 @@ Home | Library | Movie | Season | Episode
- ExoPlayer
- Video codecs: H.263, H.264, H.265, VP8, VP9, AV1
- Support depends on Android device
- Audio codecs: Vorbis, Opus, FLAC, ALAC, PCM µ-law, PCM A-law, MP1, MP2, MP3, AMR-NB, AMR-WB, AAC, AC-3, E-AC-3, DTS, DTS-HD, TrueHD
- Audio codecs: Vorbis, Opus, FLAC, ALAC, PCM, MP3, AMR-NB, AMR-WB, AAC, AC-3, E-AC-3, DTS, DTS-HD, TrueHD
- Support provided by ExoPlayer FFmpeg extension
- Subtitle codecs: SRT, VTT, SSA/ASS, PGSSUB
- SSA/ASS has limited styling support see [this issue](https://github.com/google/ExoPlayer/issues/8435)

View file

@ -109,7 +109,7 @@ dependencies {
kapt("com.google.dagger:hilt-compiler:$hiltVersion")
// ExoPlayer
val exoplayerVersion = "2.17.1"
val exoplayerVersion = "2.18.0"
implementation("com.google.android.exoplayer:exoplayer-core:$exoplayerVersion")
implementation("com.google.android.exoplayer:exoplayer-ui:$exoplayerVersion")
implementation(files("libs/extension-ffmpeg-release.aar"))

View file

@ -103,8 +103,10 @@ class PlayerActivity : BasePlayerActivity() {
if (audioRenderer == null) return@setOnClickListener
val trackSelectionDialogBuilder = TrackSelectionDialogBuilder(
this, resources.getString(R.string.select_audio_track),
viewModel.trackSelector, audioRenderer
this,
resources.getString(R.string.select_audio_track),
viewModel.player,
C.TRACK_TYPE_AUDIO
)
val trackSelectionDialog = trackSelectionDialogBuilder.build()
trackSelectionDialog.show()
@ -134,8 +136,10 @@ class PlayerActivity : BasePlayerActivity() {
if (subtitleRenderer == null) return@setOnClickListener
val trackSelectionDialogBuilder = TrackSelectionDialogBuilder(
this, resources.getString(R.string.select_subtile_track),
viewModel.trackSelector, subtitleRenderer
this,
resources.getString(R.string.select_subtile_track),
viewModel.player,
C.TRACK_TYPE_TEXT
)
trackSelectionDialogBuilder.setShowDisableOption(true)

View file

@ -5,6 +5,7 @@ import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import java.lang.IllegalStateException
@ -14,21 +15,13 @@ class TrackSelectionDialogFragment(
private val viewModel: PlayerActivityViewModel
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val trackNames: List<String>
when (type) {
TrackType.AUDIO -> {
trackNames = viewModel.currentAudioTracks.map { track ->
val nameParts: MutableList<String> = mutableListOf()
if (track.title.isNotEmpty()) nameParts.add(track.title)
if (track.lang.isNotEmpty()) nameParts.add(track.lang)
if (track.codec.isNotEmpty()) nameParts.add(track.codec)
nameParts.joinToString(separator = " - ")
}
return activity?.let { activity ->
val builder = MaterialAlertDialogBuilder(activity)
builder.setTitle(getString(R.string.select_audio_track))
.setSingleChoiceItems(
trackNames.toTypedArray(),
getTrackNames(viewModel.currentAudioTracks),
viewModel.currentAudioTracks.indexOfFirst { it.selected }) { dialog, which ->
viewModel.switchToTrack(
TrackType.AUDIO,
@ -40,18 +33,11 @@ class TrackSelectionDialogFragment(
} ?: throw IllegalStateException("Activity cannot be null")
}
TrackType.SUBTITLE -> {
trackNames = viewModel.currentSubtitleTracks.map { track ->
val nameParts: MutableList<String> = mutableListOf()
if (track.title.isNotEmpty()) nameParts.add(track.title)
if (track.lang.isNotEmpty()) nameParts.add(track.lang)
if (track.codec.isNotEmpty()) nameParts.add(track.codec)
nameParts.joinToString(separator = " - ")
}
return activity?.let { activity ->
val builder = MaterialAlertDialogBuilder(activity)
builder.setTitle(getString(R.string.select_subtile_track))
.setSingleChoiceItems(
trackNames.toTypedArray(),
getTrackNames(viewModel.currentSubtitleTracks),
viewModel.currentSubtitleTracks.indexOfFirst { if (viewModel.disableSubtitle) it.ffIndex == -1 else it.selected }) { dialog, which ->
viewModel.switchToTrack(
TrackType.SUBTITLE,
@ -67,4 +53,14 @@ class TrackSelectionDialogFragment(
}
}
}
private fun getTrackNames(tracks: List<MPVPlayer.Companion.Track>): Array<String> {
return tracks.map { track ->
val nameParts: MutableList<String> = mutableListOf()
if (track.title.isNotEmpty()) nameParts.add(track.title)
if (track.lang.isNotEmpty()) nameParts.add(track.lang)
if (track.codec.isNotEmpty()) nameParts.add(track.codec)
nameParts.joinToString(separator = " - ")
}.toTypedArray()
}
}

View file

@ -17,14 +17,10 @@ import androidx.core.content.getSystemService
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.Player.Commands
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.TrackGroup
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.text.CueGroup
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.util.*
import com.google.android.exoplayer2.video.VideoSize
import kotlinx.parcelize.Parcelize
@ -153,7 +149,7 @@ class MPVPlayer(
CopyOnWriteArraySet<Player.Listener>()
// Internal state.
private var internalMediaItems: List<MediaItem>? = null
private var internalMediaItems: List<MediaItem> = emptyList()
@Player.State
private var playbackState: Int = Player.STATE_IDLE
@ -161,7 +157,7 @@ class MPVPlayer(
@Player.RepeatMode
private val repeatMode: Int = REPEAT_MODE_OFF
private var tracksInfo: TracksInfo = TracksInfo.EMPTY
private var tracks: Tracks = Tracks.EMPTY
private var playbackParameters: PlaybackParameters = PlaybackParameters.DEFAULT
// MPV Custom
@ -170,7 +166,7 @@ class MPVPlayer(
private var currentPositionMs: Long? = null
private var currentDurationMs: Long? = null
private var currentCacheDurationMs: Long? = null
var currentTracks: List<Track> = emptyList()
var currentMpvTracks: List<Track> = emptyList()
private var initialCommands = mutableListOf<Array<String>>()
private var initialSeekTo: Long = 0L
@ -183,18 +179,18 @@ class MPVPlayer(
handler.post {
when (property) {
"track-list" -> {
val (tracks, newTracksInfo) = getMPVTracks(value)
tracks.forEach { Log.i("mpv", "${it.ffIndex} ${it.type} ${it.codec}") }
currentTracks = tracks
val (mpvTracks, newTracks) = getMPVTracks(value)
mpvTracks.forEach { Log.i("mpv", "${it.ffIndex} ${it.type} ${it.codec}") }
currentMpvTracks = mpvTracks
if (isPlayerReady) {
if (newTracksInfo != tracksInfo) {
tracksInfo = newTracksInfo
if (newTracks != tracks) {
tracks = newTracks
listeners.sendEvent(Player.EVENT_TRACKS_CHANGED) { listener ->
listener.onTracksInfoChanged(currentTracksInfo)
listener.onTracksChanged(currentTracks)
}
}
} else {
tracksInfo = newTracksInfo
tracks = newTracks
}
}
}
@ -206,7 +202,7 @@ class MPVPlayer(
when (property) {
"eof-reached" -> {
if (value && isPlayerReady) {
if (currentIndex < (internalMediaItems?.size ?: 0)) {
if (currentIndex < (internalMediaItems.size)) {
currentIndex += 1
prepareMediaItem(currentIndex)
play()
@ -299,7 +295,7 @@ class MPVPlayer(
if (!isPlayerReady) {
isPlayerReady = true
listeners.sendEvent(Player.EVENT_TRACKS_CHANGED) { listener ->
listener.onTracksInfoChanged(currentTracksInfo)
listener.onTracksChanged(currentTracks)
}
seekTo(C.TIME_UNSET)
if (playWhenReady) {
@ -372,8 +368,8 @@ class MPVPlayer(
index: Int
): Boolean {
if (index != C.INDEX_UNSET) {
Log.i("mpv", "${currentTracks.size}")
currentTracks.firstOrNull {
Log.i("mpv", "${currentMpvTracks.size}")
currentMpvTracks.firstOrNull {
it.type == trackType && (if (isExternal) it.title else "${it.ffIndex}") == "$index"
}.let { track ->
if (track != null) {
@ -386,7 +382,7 @@ class MPVPlayer(
}
}
} else {
if (currentTracks.indexOfFirst { it.type == trackType && it.selected } != C.INDEX_UNSET) {
if (currentMpvTracks.indexOfFirst { it.type == trackType && it.selected } != C.INDEX_UNSET) {
MPVLib.setPropertyString(trackType, "no")
}
}
@ -399,7 +395,7 @@ class MPVPlayer(
* Returns the number of windows in the timeline.
*/
override fun getWindowCount(): Int {
return internalMediaItems?.size ?: 0
return internalMediaItems.size
}
/**
@ -417,7 +413,7 @@ class MPVPlayer(
defaultPositionProjectionUs: Long
): Window {
val currentMediaItem =
internalMediaItems?.get(windowIndex) ?: MediaItem.Builder().build()
internalMediaItems.getOrNull(windowIndex) ?: MediaItem.Builder().build()
return window.set(
/* uid= */ windowIndex,
/* mediaItem= */ currentMediaItem,
@ -440,7 +436,7 @@ class MPVPlayer(
* Returns the number of periods in the timeline.
*/
override fun getPeriodCount(): Int {
return internalMediaItems?.size ?: 0
return internalMediaItems.size
}
/**
@ -678,7 +674,7 @@ class MPVPlayer(
currentPositionMs = null
currentDurationMs = null
currentCacheDurationMs = null
tracksInfo = TracksInfo.EMPTY
tracks = Tracks.EMPTY
playbackParameters = PlaybackParameters.DEFAULT
initialCommands.clear()
//initialSeekTo = 0L
@ -686,7 +682,7 @@ class MPVPlayer(
/** Prepares the player. */
override fun prepare() {
internalMediaItems?.forEach { mediaItem ->
internalMediaItems.forEach { mediaItem ->
MPVLib.command(
arrayOf(
"loadfile",
@ -837,7 +833,7 @@ class MPVPlayer(
}
private fun prepareMediaItem(index: Int) {
internalMediaItems?.get(index)?.let { mediaItem ->
internalMediaItems.getOrNull(index)?.let { mediaItem ->
resetInternalState()
mediaItem.localConfiguration?.subtitleConfigurations?.forEach { subtitle ->
initialCommands.add(
@ -926,33 +922,8 @@ class MPVPlayer(
currentIndex = 0
}
/**
* Returns the available track groups.
*
* @see com.google.android.exoplayer2.Player.Listener.onTracksChanged
*/
@Deprecated("Deprecated in Java")
override fun getCurrentTrackGroups(): TrackGroupArray {
return TrackGroupArray.EMPTY
}
/**
* Returns the current track selections.
*
*
* A concrete implementation may include null elements if it has a fixed number of renderer
* components, wishes to report a TrackSelection for each of them, and has one or more renderer
* components that is not assigned any selected tracks.
*
* @see com.google.android.exoplayer2.Player.Listener.onTracksChanged
*/
@Deprecated("Deprecated in Java")
override fun getCurrentTrackSelections(): TrackSelectionArray {
return TrackSelectionArray()
}
override fun getCurrentTracksInfo(): TracksInfo {
return tracksInfo
override fun getCurrentTracks(): Tracks {
return tracks
}
override fun getTrackSelectionParameters(): TrackSelectionParameters {
@ -1203,7 +1174,7 @@ class MPVPlayer(
}
/** Returns the current [Cues][Cue]. This list may be empty. */
override fun getCurrentCues(): MutableList<Cue> {
override fun getCurrentCues(): CueGroup {
TODO("Not yet implemented")
}
@ -1258,78 +1229,6 @@ class MPVPlayer(
throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.")
}
/*private class CurrentTrackSelection(
private val currentTrackGroup: TrackGroup,
private val index: Int
) : TrackSelection {
/**
* Returns an integer specifying the type of the selection, or [.TYPE_UNSET] if not
* specified.
*
*
* Track selection types are specific to individual applications, but should be defined
* starting from [.TYPE_CUSTOM_BASE] to ensure they don't conflict with any types that may
* be added to the library in the future.
*/
override fun getType(): Int {
return TrackSelection.TYPE_UNSET
}
/** Returns the [TrackGroup] to which the selected tracks belong. */
override fun getTrackGroup(): TrackGroup {
return currentTrackGroup
}
/** Returns the number of tracks in the selection. */
override fun length(): Int {
return if (index != C.INDEX_UNSET) 1 else 0
}
/**
* Returns the format of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The format of the selected track.
*/
override fun getFormat(index: Int): Format {
return currentTrackGroup.getFormat(index)
}
/**
* Returns the index in the track group of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The index of the selected track.
*/
override fun getIndexInTrackGroup(index: Int): Int {
return index
}
/**
* Returns the index in the selection of the track with the specified format. The format is
* located by identity so, for example, `selection.indexOf(selection.getFormat(index)) ==
* index` even if multiple selected tracks have formats that contain the same values.
*
* @param format The format.
* @return The index in the selection, or [C.INDEX_UNSET] if the track with the specified
* format is not part of the selection.
*/
override fun indexOf(format: Format): Int {
return currentTrackGroup.indexOf(format)
}
/**
* Returns the index in the selection of the track with the specified index in the track group.
*
* @param indexInTrackGroup The index in the track group.
* @return The index in the selection, or [C.INDEX_UNSET] if the track with the specified
* index is not part of the selection.
*/
override fun indexOf(indexInTrackGroup: Int): Int {
return indexInTrackGroup
}
}*/
companion object {
/**
* Fraction to which audio volume is ducked on loss of audio focus
@ -1449,10 +1348,10 @@ class MPVPlayer(
}
}
private fun getMPVTracks(trackList: String): Pair<List<Track>, TracksInfo> {
val tracks = mutableListOf<Track>()
var tracksInfo = TracksInfo.EMPTY
val trackGroupInfos = mutableListOf<TracksInfo.TrackGroupInfo>()
private fun getMPVTracks(trackList: String): Pair<List<Track>, Tracks> {
val mpvTracks = mutableListOf<Track>()
var tracks = Tracks.EMPTY
val trackGroups = mutableListOf<Tracks.Group>()
val trackListVideo = mutableListOf<Format>()
val trackListAudio = mutableListOf<Format>()
@ -1475,7 +1374,7 @@ class MPVPlayer(
width = null,
height = null
)
tracks.add(emptyTrack)
mpvTracks.add(emptyTrack)
trackListText.add(emptyTrack.toFormat())
val currentTrackList = JSONArray(trackList)
for (index in 0 until currentTrackList.length()) {
@ -1483,21 +1382,21 @@ class MPVPlayer(
val currentFormat = currentTrack.toFormat()
when (currentTrack.type) {
TrackType.VIDEO -> {
tracks.add(currentTrack)
mpvTracks.add(currentTrack)
trackListVideo.add(currentFormat)
if (currentTrack.selected) {
indexCurrentVideo = trackListVideo.indexOf(currentFormat)
}
}
TrackType.AUDIO -> {
tracks.add(currentTrack)
mpvTracks.add(currentTrack)
trackListAudio.add(currentFormat)
if (currentTrack.selected) {
indexCurrentAudio = trackListAudio.indexOf(currentFormat)
}
}
TrackType.SUBTITLE -> {
tracks.add(currentTrack)
mpvTracks.add(currentTrack)
trackListText.add(currentFormat)
if (currentTrack.selected) {
indexCurrentText = trackListText.indexOf(currentFormat)
@ -1507,71 +1406,45 @@ class MPVPlayer(
}
}
if (trackListText.size == 1 && trackListText[0].id == emptyTrack.id.toString()) {
tracks.remove(emptyTrack)
mpvTracks.remove(emptyTrack)
trackListText.removeFirst()
}
if (trackListVideo.isNotEmpty()) {
with(TrackGroup(*trackListVideo.toTypedArray())) {
TracksInfo.TrackGroupInfo(
Tracks.Group(
this,
true,
intArrayOf(C.FORMAT_HANDLED),
C.TRACK_TYPE_VIDEO,
BooleanArray(this.length) { it == indexCurrentVideo }
)
}
}
if (trackListAudio.isNotEmpty()) {
with(TrackGroup(*trackListAudio.toTypedArray())) {
TracksInfo.TrackGroupInfo(
Tracks.Group(
this,
true,
IntArray(this.length) { C.FORMAT_HANDLED },
C.TRACK_TYPE_AUDIO,
BooleanArray(this.length) { it == indexCurrentAudio }
)
}
}
if (trackListText.isNotEmpty()) {
with(TrackGroup(*trackListText.toTypedArray())) {
TracksInfo.TrackGroupInfo(
Tracks.Group(
this,
true,
IntArray(this.length) { C.FORMAT_HANDLED },
C.TRACK_TYPE_TEXT,
BooleanArray(this.length) { it == indexCurrentText }
)
}
}
if (trackGroupInfos.isNotEmpty()) {
tracksInfo = TracksInfo(trackGroupInfos)
if (trackGroups.isNotEmpty()) {
tracks = Tracks(trackGroups)
}
} catch (e: JSONException) {
}
return Pair(tracks, tracksInfo)
}
/**
* Merges multiple [subtitleSources] into a single [videoSource]
*/
fun mergeMediaSources(
videoSource: MediaSource,
subtitleSources: Array<MediaSource>,
dataSource: DataSource.Factory
): MediaSource {
return when {
subtitleSources.isEmpty() -> videoSource
else -> {
val subtitleConfigurations = mutableListOf<MediaItem.SubtitleConfiguration>()
subtitleSources.forEach { subtitleSource ->
subtitleSource.mediaItem.localConfiguration?.subtitleConfigurations?.forEach { subtitle ->
subtitleConfigurations.add(subtitle)
}
}
ProgressiveMediaSource.Factory(dataSource)
.createMediaSource(
videoSource.mediaItem.buildUpon()
.setSubtitleConfigurations(subtitleConfigurations).build()
)
}
}
return Pair(mpvTracks, tracks)
}
}
}

View file

@ -63,8 +63,8 @@ constructor(
init {
val useMpv = sp.getBoolean("mpv_player", false)
val preferredAudioLanguage = sp.getString("audio_language", null) ?: ""
val preferredSubtitleLanguage = sp.getString("subtitle_language", null) ?: ""
val preferredAudioLanguage = sp.getString("audio_language", "")!!
val preferredSubtitleLanguage = sp.getString("subtitle_language", "")!!
if (useMpv) {
val preferredLanguages = mapOf(
@ -180,7 +180,7 @@ constructor(
}
}
}
handler.postDelayed(this, 2000)
handler.postDelayed(this, 5000)
}
}
handler.post(runnable)
@ -225,7 +225,7 @@ constructor(
currentSubtitleTracks.clear()
when (player) {
is MPVPlayer -> {
player.currentTracks.forEach {
player.currentMpvTracks.forEach {
when (it.type) {
TrackType.AUDIO -> {
currentAudioTracks.add(it)