Upgrade ExoPlayer to 2.16.1 (#115)

* Upgrade ExoPlayer to 2.26.1 and fix mpv player

* Re-enable ffmpeg extension
This commit is contained in:
Jarne Demeulemeester 2022-05-02 21:44:22 +02:00 committed by GitHub
parent 0b0bdab9d3
commit a785d6d3f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 133 deletions

View file

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

View file

@ -17,15 +17,13 @@ import androidx.core.content.getSystemService
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.Player.Commands import com.google.android.exoplayer2.Player.Commands
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.device.DeviceInfo
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.TrackGroup import com.google.android.exoplayer2.source.TrackGroup
import com.google.android.exoplayer2.source.TrackGroupArray import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.text.Cue import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.trackselection.TrackSelection
import com.google.android.exoplayer2.trackselection.TrackSelectionArray import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters
import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.util.* import com.google.android.exoplayer2.util.*
import com.google.android.exoplayer2.video.VideoSize import com.google.android.exoplayer2.video.VideoSize
@ -61,7 +59,8 @@ class MPVPlayer(
val file = File(mpvDir, fileName) val file = File(mpvDir, fileName)
Log.i("mpv", "File ${file.absolutePath}") Log.i("mpv", "File ${file.absolutePath}")
if (!file.exists()) { if (!file.exists()) {
context.assets.open(fileName, AssetManager.ACCESS_STREAMING).copyTo(FileOutputStream(file)) context.assets.open(fileName, AssetManager.ACCESS_STREAMING)
.copyTo(FileOutputStream(file))
} }
} }
MPVLib.create(context) MPVLib.create(context)
@ -144,26 +143,26 @@ class MPVPlayer(
} }
// Listeners and notification. // Listeners and notification.
@Suppress("DEPRECATION") private val listeners: ListenerSet<Player.Listener> = ListenerSet(
private val listeners: ListenerSet<Player.EventListener> = ListenerSet(
context.mainLooper, context.mainLooper,
Clock.DEFAULT Clock.DEFAULT
) { listener: Player.EventListener, flags: FlagSet -> ) { listener: Player.Listener, flags: FlagSet ->
listener.onEvents( /* player= */this, Player.Events(flags)) listener.onEvents( /* player= */this, Player.Events(flags))
} }
@Suppress("DEPRECATION") private val videoListeners =
private val videoListeners = CopyOnWriteArraySet<com.google.android.exoplayer2.video.VideoListener>() CopyOnWriteArraySet<Player.Listener>()
// Internal state. // Internal state.
private var internalMediaItems: List<MediaItem>? = null private var internalMediaItems: List<MediaItem>? = null
private var internalMediaItem: MediaItem? = null private var internalMediaItem: MediaItem? = null
@Player.State @Player.State
private var playbackState: Int = Player.STATE_IDLE private var playbackState: Int = Player.STATE_IDLE
private var currentPlayWhenReady: Boolean = false private var currentPlayWhenReady: Boolean = false
@Player.RepeatMode @Player.RepeatMode
private val repeatMode: Int = REPEAT_MODE_OFF private val repeatMode: Int = REPEAT_MODE_OFF
private var trackGroupArray: TrackGroupArray = TrackGroupArray.EMPTY private var tracksInfo: TracksInfo = TracksInfo.EMPTY
private var trackSelectionArray: TrackSelectionArray = TrackSelectionArray()
private var playbackParameters: PlaybackParameters = PlaybackParameters.DEFAULT private var playbackParameters: PlaybackParameters = PlaybackParameters.DEFAULT
// MPV Custom // MPV Custom
@ -185,20 +184,18 @@ class MPVPlayer(
handler.post { handler.post {
when (property) { when (property) {
"track-list" -> { "track-list" -> {
val (tracks, newTrackGroupArray, newTrackSelectionArray) = getMPVTracks(value) val (tracks, newTracksInfo) = getMPVTracks(value)
tracks.forEach { Log.i("mpv", "${it.ffIndex} ${it.type} ${it.codec}") } tracks.forEach { Log.i("mpv", "${it.ffIndex} ${it.type} ${it.codec}") }
currentTracks = tracks currentTracks = tracks
if (isPlayerReady) { if (isPlayerReady) {
if (newTrackGroupArray != trackGroupArray || newTrackSelectionArray != trackSelectionArray) { if (newTracksInfo != tracksInfo) {
trackGroupArray = newTrackGroupArray tracksInfo = newTracksInfo
trackSelectionArray = newTrackSelectionArray
listeners.sendEvent(Player.EVENT_TRACKS_CHANGED) { listener -> listeners.sendEvent(Player.EVENT_TRACKS_CHANGED) { listener ->
listener.onTracksChanged(currentTrackGroups, currentTrackSelections) listener.onTracksInfoChanged(currentTracksInfo)
} }
} }
} else { } else {
trackGroupArray = newTrackGroupArray tracksInfo = newTracksInfo
trackSelectionArray = newTrackSelectionArray
} }
} }
} }
@ -237,7 +234,10 @@ class MPVPlayer(
if (isSeekable != value) { if (isSeekable != value) {
isSeekable = value isSeekable = value
listeners.sendEvent(Player.EVENT_TIMELINE_CHANGED) { listener -> listeners.sendEvent(Player.EVENT_TIMELINE_CHANGED) { listener ->
listener.onTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) listener.onTimelineChanged(
timeline,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE
)
} }
} }
} }
@ -253,7 +253,10 @@ class MPVPlayer(
if (currentDurationMs != value * C.MILLIS_PER_SECOND) { if (currentDurationMs != value * C.MILLIS_PER_SECOND) {
currentDurationMs = value * C.MILLIS_PER_SECOND currentDurationMs = value * C.MILLIS_PER_SECOND
listeners.sendEvent(Player.EVENT_TIMELINE_CHANGED) { listener -> listeners.sendEvent(Player.EVENT_TIMELINE_CHANGED) { listener ->
listener.onTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) listener.onTimelineChanged(
timeline,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE
)
} }
} }
} }
@ -297,7 +300,8 @@ class MPVPlayer(
if (!isPlayerReady) { if (!isPlayerReady) {
isPlayerReady = true isPlayerReady = true
listeners.sendEvent(Player.EVENT_TRACKS_CHANGED) { listener -> listeners.sendEvent(Player.EVENT_TRACKS_CHANGED) { listener ->
listener.onTracksChanged(currentTrackGroups, currentTrackSelections) //listener.onTracksChanged(currentTrackGroups, currentTrackSelections)
listener.onTracksInfoChanged(currentTracksInfo)
} }
seekTo(C.TIME_UNSET) seekTo(C.TIME_UNSET)
if (playWhenReady) { if (playWhenReady) {
@ -364,7 +368,11 @@ class MPVPlayer(
* @param index Index to select or [C.INDEX_UNSET] to disable [TrackType] * @param index Index 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(@TrackType trackType: String, isExternal: Boolean = false, index: Int): Boolean { fun selectTrack(
@TrackType trackType: String,
isExternal: Boolean = false,
index: Int
): Boolean {
if (index != C.INDEX_UNSET) { if (index != C.INDEX_UNSET) {
Log.i("mpv", "${currentTracks.size}") Log.i("mpv", "${currentTracks.size}")
currentTracks.firstOrNull { currentTracks.firstOrNull {
@ -405,7 +413,11 @@ class MPVPlayer(
* default start position should be projected. * default start position should be projected.
* @return The populated [com.google.android.exoplayer2.Timeline.Window], for convenience. * @return The populated [com.google.android.exoplayer2.Timeline.Window], for convenience.
*/ */
override fun getWindow(windowIndex: Int, window: Window, defaultPositionProjectionUs: Long): Window { override fun getWindow(
windowIndex: Int,
window: Window,
defaultPositionProjectionUs: Long
): Window {
val currentMediaItem = val currentMediaItem =
internalMediaItems?.get(windowIndex) ?: MediaItem.Builder().build() internalMediaItems?.get(windowIndex) ?: MediaItem.Builder().build()
return window.set( return window.set(
@ -419,7 +431,7 @@ class MPVPlayer(
/* isDynamic= */ !isSeekable, /* isDynamic= */ !isSeekable,
/* liveConfiguration= */ currentMediaItem.liveConfiguration, /* liveConfiguration= */ currentMediaItem.liveConfiguration,
/* defaultPositionUs= */ C.TIME_UNSET, /* defaultPositionUs= */ C.TIME_UNSET,
/* durationUs= */ C.msToUs(currentDurationMs ?: C.TIME_UNSET), /* durationUs= */ Util.msToUs(currentDurationMs ?: C.TIME_UNSET),
/* firstPeriodIndex= */ windowIndex, /* firstPeriodIndex= */ windowIndex,
/* lastPeriodIndex= */ windowIndex, /* lastPeriodIndex= */ windowIndex,
/* positionInFirstPeriodUs= */ C.TIME_UNSET /* positionInFirstPeriodUs= */ C.TIME_UNSET
@ -448,7 +460,7 @@ class MPVPlayer(
/* id= */ periodIndex, /* id= */ periodIndex,
/* uid= */ periodIndex, /* uid= */ periodIndex,
/* windowIndex= */ periodIndex, /* windowIndex= */ periodIndex,
/* durationUs= */ C.msToUs(currentDurationMs ?: C.TIME_UNSET), /* durationUs= */ Util.msToUs(currentDurationMs ?: C.TIME_UNSET),
/* positionInWindowUs= */ 0 /* positionInWindowUs= */ 0
) )
} }
@ -527,18 +539,6 @@ class MPVPlayer(
return handler.looper return handler.looper
} }
/**
* Registers a listener to receive events from the player. The listener's methods will be called
* on the thread that was used to construct the player. However, if the thread used to construct
* the player does not have a [Looper], then the listener will be called on the main thread.
*
* @param listener The listener to register.
*/
@Suppress("DEPRECATION")
override fun addListener(listener: Player.EventListener) {
listeners.add(listener)
}
/** /**
* Registers a listener to receive all events from the player. * Registers a listener to receive all events from the player.
* *
@ -549,17 +549,6 @@ class MPVPlayer(
videoListeners.add(listener) videoListeners.add(listener)
} }
/**
* Unregister a listener registered through [.addListener]. The listener will
* no longer receive events from the player.
*
* @param listener The listener to unregister.
*/
@Suppress("DEPRECATION")
override fun removeListener(listener: Player.EventListener) {
listeners.remove(listener)
}
/** /**
* Unregister a listener registered through [.addListener]. The listener will no * Unregister a listener registered through [.addListener]. The listener will no
* longer receive events. * longer receive events.
@ -595,7 +584,11 @@ class MPVPlayer(
* @throws com.google.android.exoplayer2.IllegalSeekPositionException If the provided `startWindowIndex` is not within the * @throws com.google.android.exoplayer2.IllegalSeekPositionException If the provided `startWindowIndex` is not within the
* bounds of the list of media items. * bounds of the list of media items.
*/ */
override fun setMediaItems(mediaItems: MutableList<MediaItem>, startWindowIndex: Int, startPositionMs: Long) { override fun setMediaItems(
mediaItems: MutableList<MediaItem>,
startWindowIndex: Int,
startPositionMs: Long
) {
internalMediaItems = mediaItems internalMediaItems = mediaItems
currentIndex = startWindowIndex currentIndex = startWindowIndex
initialSeekTo = startPositionMs / 1000 initialSeekTo = startPositionMs / 1000
@ -654,30 +647,31 @@ class MPVPlayer(
* @return The currently available [com.google.android.exoplayer2.Player.Commands]. * @return The currently available [com.google.android.exoplayer2.Player.Commands].
* @see com.google.android.exoplayer2.Player.Listener.onAvailableCommandsChanged * @see com.google.android.exoplayer2.Player.Listener.onAvailableCommandsChanged
*/ */
override fun getAvailableCommands(): Player.Commands { override fun getAvailableCommands(): Commands {
return Commands.Builder() return Commands.Builder()
.addAll(permanentAvailableCommands) .addAll(permanentAvailableCommands)
.addIf(COMMAND_SEEK_TO_DEFAULT_POSITION, !isPlayingAd) .addIf(COMMAND_SEEK_TO_DEFAULT_POSITION, !isPlayingAd)
.addIf(COMMAND_SEEK_IN_CURRENT_WINDOW, isCurrentWindowSeekable && !isPlayingAd) .addIf(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, isCurrentMediaItemSeekable && !isPlayingAd)
.addIf(COMMAND_SEEK_TO_PREVIOUS_WINDOW, hasPreviousWindow() && !isPlayingAd) .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem() && !isPlayingAd)
.addIf( .addIf(
COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_PREVIOUS,
!currentTimeline.isEmpty !currentTimeline.isEmpty
&& (hasPreviousWindow() || !isCurrentWindowLive || isCurrentWindowSeekable) && (hasPreviousMediaItem() || !isCurrentMediaItemLive || isCurrentMediaItemSeekable)
&& !isPlayingAd && !isPlayingAd
) )
.addIf(COMMAND_SEEK_TO_NEXT_WINDOW, hasNextWindow() && !isPlayingAd) .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem() && !isPlayingAd)
.addIf( .addIf(
COMMAND_SEEK_TO_NEXT, COMMAND_SEEK_TO_NEXT,
!currentTimeline.isEmpty() !currentTimeline.isEmpty
&& (hasNextWindow() || (isCurrentWindowLive && isCurrentWindowDynamic())) && (hasNextMediaItem() || (isCurrentMediaItemLive && isCurrentMediaItemDynamic))
&& !isPlayingAd && !isPlayingAd
) )
.addIf(COMMAND_SEEK_TO_WINDOW, !isPlayingAd) .addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd)
.addIf(COMMAND_SEEK_BACK, isCurrentWindowSeekable && !isPlayingAd) .addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable && !isPlayingAd)
.addIf(COMMAND_SEEK_FORWARD, isCurrentWindowSeekable && !isPlayingAd) .addIf(COMMAND_SEEK_FORWARD, isCurrentMediaItemSeekable && !isPlayingAd)
.build() .build()
} }
private fun resetInternalState() { private fun resetInternalState() {
isPlayerReady = false isPlayerReady = false
isSeekable = false isSeekable = false
@ -686,8 +680,7 @@ class MPVPlayer(
currentPositionMs = null currentPositionMs = null
currentDurationMs = null currentDurationMs = null
currentCacheDurationMs = null currentCacheDurationMs = null
trackGroupArray = TrackGroupArray.EMPTY tracksInfo = TracksInfo.EMPTY
trackSelectionArray = TrackSelectionArray()
playbackParameters = PlaybackParameters.DEFAULT playbackParameters = PlaybackParameters.DEFAULT
initialCommands.clear() initialCommands.clear()
//initialSeekTo = 0L //initialSeekTo = 0L
@ -699,7 +692,7 @@ class MPVPlayer(
MPVLib.command( MPVLib.command(
arrayOf( arrayOf(
"loadfile", "loadfile",
"${mediaItem.playbackProperties?.uri}", "${mediaItem.localConfiguration?.uri}",
"append" "append"
) )
) )
@ -830,7 +823,7 @@ class MPVPlayer(
* `windowIndex` is not within the bounds of the current timeline. * `windowIndex` is not within the bounds of the current timeline.
*/ */
override fun seekTo(windowIndex: Int, positionMs: Long) { override fun seekTo(windowIndex: Int, positionMs: Long) {
if (windowIndex == currentWindowIndex) { if (windowIndex == currentMediaItemIndex) {
val seekTo = val seekTo =
if (positionMs != C.TIME_UNSET) positionMs / C.MILLIS_PER_SECOND else initialSeekTo if (positionMs != C.TIME_UNSET) positionMs / C.MILLIS_PER_SECOND else initialSeekTo
initialSeekTo = if (isPlayerReady) { initialSeekTo = if (isPlayerReady) {
@ -849,7 +842,7 @@ class MPVPlayer(
internalMediaItems?.get(index)?.let { mediaItem -> internalMediaItems?.get(index)?.let { mediaItem ->
internalMediaItem = mediaItem internalMediaItem = mediaItem
resetInternalState() resetInternalState()
mediaItem.playbackProperties?.subtitles?.forEach { subtitle -> mediaItem.localConfiguration?.subtitleConfigurations?.forEach { subtitle ->
initialCommands.add( initialCommands.add(
arrayOf( arrayOf(
/* command= */ "sub-add", /* command= */ "sub-add",
@ -877,14 +870,14 @@ class MPVPlayer(
} }
override fun getSeekBackIncrement(): Long { override fun getSeekBackIncrement(): Long {
return 5000 return C.DEFAULT_SEEK_BACK_INCREMENT_MS
} }
override fun getSeekForwardIncrement(): Long { override fun getSeekForwardIncrement(): Long {
return 15000 return C.DEFAULT_SEEK_FORWARD_INCREMENT_MS
} }
override fun getMaxSeekToPreviousPosition(): Int { override fun getMaxSeekToPreviousPosition(): Long {
return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS
} }
@ -913,6 +906,11 @@ class MPVPlayer(
return playbackParameters return playbackParameters
} }
override fun stop() {
MPVLib.command(arrayOf("stop", "keep-playlist"))
}
@Deprecated("Deprecated in Java")
override fun stop(reset: Boolean) { override fun stop(reset: Boolean) {
MPVLib.command(arrayOf("stop", "keep-playlist")) MPVLib.command(arrayOf("stop", "keep-playlist"))
} }
@ -936,8 +934,9 @@ class MPVPlayer(
* *
* @see com.google.android.exoplayer2.Player.Listener.onTracksChanged * @see com.google.android.exoplayer2.Player.Listener.onTracksChanged
*/ */
@Deprecated("Deprecated in Java")
override fun getCurrentTrackGroups(): TrackGroupArray { override fun getCurrentTrackGroups(): TrackGroupArray {
return trackGroupArray return TrackGroupArray.EMPTY
} }
/** /**
@ -950,27 +949,21 @@ class MPVPlayer(
* *
* @see com.google.android.exoplayer2.Player.Listener.onTracksChanged * @see com.google.android.exoplayer2.Player.Listener.onTracksChanged
*/ */
@Deprecated("Deprecated in Java")
override fun getCurrentTrackSelections(): TrackSelectionArray { override fun getCurrentTrackSelections(): TrackSelectionArray {
return trackSelectionArray return TrackSelectionArray()
} }
/** override fun getCurrentTracksInfo(): TracksInfo {
* Returns the current static metadata for the track selections. return tracksInfo
* }
*
* The returned `metadataList` is an immutable list of [Metadata] instances, where override fun getTrackSelectionParameters(): TrackSelectionParameters {
* the elements correspond to the [current track selections][.getCurrentTrackSelections], TODO("Not yet implemented")
* or an empty list if there are no track selections or the selected tracks contain no static }
* metadata.
* override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
* TODO("Not yet implemented")
* This metadata is considered static in that it comes from the tracks' declared Formats,
* rather than being timed (or dynamic) metadata, which is represented within a metadata track.
*
* @see com.google.android.exoplayer2.Player.Listener.onStaticMetadataChanged
*/
override fun getCurrentStaticMetadata(): List<Metadata> {
return emptyList()
} }
/** /**
@ -979,7 +972,7 @@ class MPVPlayer(
* *
* *
* This [MediaMetadata] is a combination of the [MediaItem.mediaMetadata] and the * This [MediaMetadata] is a combination of the [MediaItem.mediaMetadata] and the
* static and dynamic metadata sourced from [com.google.android.exoplayer2.Player.Listener.onStaticMetadataChanged] and * static and dynamic metadata sourced from [com.google.android.exoplayer2.Player.Listener.onMediaMetadataChanged] and
* [com.google.android.exoplayer2.metadata.MetadataOutput.onMetadata]. * [com.google.android.exoplayer2.metadata.MetadataOutput.onMetadata].
*/ */
override fun getMediaMetadata(): MediaMetadata { override fun getMediaMetadata(): MediaMetadata {
@ -1005,14 +998,10 @@ class MPVPlayer(
/** Returns the index of the period currently being played. */ /** Returns the index of the period currently being played. */
override fun getCurrentPeriodIndex(): Int { override fun getCurrentPeriodIndex(): Int {
return currentWindowIndex return currentMediaItemIndex
} }
/** override fun getCurrentMediaItemIndex(): Int {
* Returns the index of the current [window][Timeline.Window] in the [ ][.getCurrentTimeline], or the prospective window index if the [ ][.getCurrentTimeline] is empty.
*/
override fun getCurrentWindowIndex(): Int {
return currentIndex return currentIndex
} }
@ -1020,7 +1009,7 @@ class MPVPlayer(
* Returns the duration of the current content window or ad in milliseconds, or [ ][com.google.android.exoplayer2.C.TIME_UNSET] if the duration is not known. * Returns the duration of the current content window or ad in milliseconds, or [ ][com.google.android.exoplayer2.C.TIME_UNSET] if the duration is not known.
*/ */
override fun getDuration(): Long { override fun getDuration(): Long {
return timeline.getWindow(currentWindowIndex, window).durationMs return timeline.getWindow(currentMediaItemIndex, window).durationMs
} }
/** /**
@ -1272,7 +1261,7 @@ class MPVPlayer(
throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.") throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.")
} }
private class CurrentTrackSelection( /*private class CurrentTrackSelection(
private val currentTrackGroup: TrackGroup, private val currentTrackGroup: TrackGroup,
private val index: Int private val index: Int
) : TrackSelection { ) : TrackSelection {
@ -1342,7 +1331,7 @@ class MPVPlayer(
override fun indexOf(indexInTrackGroup: Int): Int { override fun indexOf(indexInTrackGroup: Int): Int {
return indexInTrackGroup return indexInTrackGroup
} }
} }*/
companion object { companion object {
/** /**
@ -1350,10 +1339,9 @@ class MPVPlayer(
*/ */
private const val AUDIO_FOCUS_DUCKING = 0.5f private const val AUDIO_FOCUS_DUCKING = 0.5f
private val permanentAvailableCommands: Player.Commands = Player.Commands.Builder() private val permanentAvailableCommands: Commands = Commands.Builder()
.addAll( .addAll(
COMMAND_PLAY_PAUSE, COMMAND_PLAY_PAUSE,
COMMAND_PREPARE_STOP,
COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_SPEED_AND_PITCH,
COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_GET_CURRENT_MEDIA_ITEM,
COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_GET_MEDIA_ITEMS_METADATA,
@ -1389,7 +1377,12 @@ class MPVPlayer(
* @param width The new width of the surface. * @param width The new width of the surface.
* @param height The new height of the surface. * @param height The new height of the surface.
*/ */
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
MPVLib.setPropertyString("android-surface-size", "${width}x$height") MPVLib.setPropertyString("android-surface-size", "${width}x$height")
} }
@ -1439,6 +1432,7 @@ class MPVPlayer(
.setHeight(height ?: Format.NO_VALUE) .setHeight(height ?: Format.NO_VALUE)
.build() .build()
} }
companion object { companion object {
fun fromJSON(json: JSONObject): Track { fun fromJSON(json: JSONObject): Track {
return Track( return Track(
@ -1458,10 +1452,10 @@ class MPVPlayer(
} }
} }
private fun getMPVTracks(trackList: String) : Triple<List<Track> ,TrackGroupArray, TrackSelectionArray> { private fun getMPVTracks(trackList: String): Pair<List<Track>, TracksInfo> {
val tracks = mutableListOf<Track>() val tracks = mutableListOf<Track>()
var trackGroupArray = TrackGroupArray.EMPTY var tracksInfo = TracksInfo.EMPTY
var trackSelectionArray = TrackSelectionArray() val trackGroupInfos = mutableListOf<TracksInfo.TrackGroupInfo>()
val trackListVideo = mutableListOf<Format>() val trackListVideo = mutableListOf<Format>()
val trackListAudio = mutableListOf<Format>() val trackListAudio = mutableListOf<Format>()
@ -1519,32 +1513,42 @@ class MPVPlayer(
tracks.remove(emptyTrack) tracks.remove(emptyTrack)
trackListText.removeFirst() trackListText.removeFirst()
} }
val trackGroups = mutableListOf<TrackGroup>()
val trackSelections = mutableListOf<TrackSelection>()
if (trackListVideo.isNotEmpty()) { if (trackListVideo.isNotEmpty()) {
with(TrackGroup(*trackListVideo.toTypedArray())) { with(TrackGroup(*trackListVideo.toTypedArray())) {
trackGroups.add(this) TracksInfo.TrackGroupInfo(
trackSelections.add(CurrentTrackSelection(this, indexCurrentVideo)) this,
intArrayOf(C.FORMAT_HANDLED),
C.TRACK_TYPE_VIDEO,
BooleanArray(this.length) { it == indexCurrentVideo }
)
} }
} }
if (trackListAudio.isNotEmpty()) { if (trackListAudio.isNotEmpty()) {
with(TrackGroup(*trackListAudio.toTypedArray())) { with(TrackGroup(*trackListAudio.toTypedArray())) {
trackGroups.add(this) TracksInfo.TrackGroupInfo(
trackSelections.add(CurrentTrackSelection(this, indexCurrentAudio)) this,
IntArray(this.length) { C.FORMAT_HANDLED },
C.TRACK_TYPE_AUDIO,
BooleanArray(this.length) { it == indexCurrentAudio }
)
} }
} }
if (trackListText.isNotEmpty()) { if (trackListText.isNotEmpty()) {
with(TrackGroup(*trackListText.toTypedArray())) { with(TrackGroup(*trackListText.toTypedArray())) {
trackGroups.add(this) TracksInfo.TrackGroupInfo(
trackSelections.add(CurrentTrackSelection(this, indexCurrentText)) this,
IntArray(this.length) { C.FORMAT_HANDLED },
C.TRACK_TYPE_TEXT,
BooleanArray(this.length) { it == indexCurrentText }
)
} }
} }
if (trackGroups.isNotEmpty()) { if (trackGroupInfos.isNotEmpty()) {
trackGroupArray = TrackGroupArray(*trackGroups.toTypedArray()) tracksInfo = TracksInfo(trackGroupInfos)
trackSelectionArray = TrackSelectionArray(*trackSelections.toTypedArray())
} }
} catch (e: JSONException) {} } catch (e: JSONException) {
return Triple(tracks, trackGroupArray, trackSelectionArray) }
return Pair(tracks, tracksInfo)
} }
/** /**
@ -1558,16 +1562,16 @@ class MPVPlayer(
return when { return when {
subtitleSources.isEmpty() -> videoSource subtitleSources.isEmpty() -> videoSource
else -> { else -> {
val subtitles = mutableListOf<MediaItem.Subtitle>() val subtitleConfigurations = mutableListOf<MediaItem.SubtitleConfiguration>()
subtitleSources.forEach { subtitleSource -> subtitleSources.forEach { subtitleSource ->
subtitleSource.mediaItem.playbackProperties?.subtitles?.forEach { subtitle -> subtitleSource.mediaItem.localConfiguration?.subtitleConfigurations?.forEach { subtitle ->
subtitles.add(subtitle) subtitleConfigurations.add(subtitle)
} }
} }
ProgressiveMediaSource.Factory(dataSource) ProgressiveMediaSource.Factory(dataSource)
.createMediaSource( .createMediaSource(
videoSource.mediaItem.buildUpon() videoSource.mediaItem.buildUpon()
.setSubtitles(subtitles).build() .setSubtitleConfigurations(subtitleConfigurations).build()
) )
} }
} }

View file

@ -8,12 +8,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.BasePlayer
import com.google.android.exoplayer2.DefaultRenderersFactory import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
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.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao
@ -36,7 +34,7 @@ constructor(
private val jellyfinRepository: JellyfinRepository, private val jellyfinRepository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao private val downloadDatabase: DownloadDatabaseDao
) : ViewModel(), Player.Listener { ) : ViewModel(), Player.Listener {
val player: BasePlayer val player: Player
private val _navigateBack = MutableLiveData<Boolean>() private val _navigateBack = MutableLiveData<Boolean>()
val navigateBack: LiveData<Boolean> = _navigateBack val navigateBack: LiveData<Boolean> = _navigateBack
@ -55,7 +53,7 @@ constructor(
val trackSelector = DefaultTrackSelector(application) val trackSelector = DefaultTrackSelector(application)
var playWhenReady = true var playWhenReady = true
private var playFromDownloads = false private var playFromDownloads = false
private var currentWindow = 0 private var currentMediaItemIndex = 0
private var playbackPosition: Long = 0 private var playbackPosition: Long = 0
var playbackSpeed: Float = 1f var playbackSpeed: Float = 1f
@ -90,7 +88,7 @@ constructor(
.setPreferredAudioLanguage(preferredAudioLanguage) .setPreferredAudioLanguage(preferredAudioLanguage)
.setPreferredTextLanguage(preferredSubtitleLanguage) .setPreferredTextLanguage(preferredSubtitleLanguage)
) )
player = SimpleExoPlayer.Builder(application, renderersFactory) player = ExoPlayer.Builder(application, renderersFactory)
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.build() .build()
} }
@ -124,7 +122,7 @@ constructor(
Timber.e(e) Timber.e(e)
} }
player.setMediaItems(mediaItems, currentWindow, items[0].playbackPosition) player.setMediaItems(mediaItems, currentMediaItemIndex, items[0].playbackPosition)
val useMpv = sp.getBoolean("mpv_player", false) val useMpv = sp.getBoolean("mpv_player", false)
if(!useMpv || !playFromDownloads) if(!useMpv || !playFromDownloads)
player.prepare() //TODO: This line causes a crash when playing from downloads with MPV player.prepare() //TODO: This line causes a crash when playing from downloads with MPV
@ -149,12 +147,12 @@ constructor(
playWhenReady = player.playWhenReady playWhenReady = player.playWhenReady
playbackPosition = player.currentPosition playbackPosition = player.currentPosition
currentWindow = player.currentWindowIndex currentMediaItemIndex = player.currentMediaItemIndex
player.removeListener(this) player.removeListener(this)
player.release() player.release()
} }
private fun pollPosition(player: BasePlayer) { private fun pollPosition(player: Player) {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
val runnable = object : Runnable { val runnable = object : Runnable {
override fun run() { override fun run() {

View file

@ -9,10 +9,8 @@ import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.getDownloadPlayerItem import dev.jdtech.jellyfin.utils.getDownloadPlayerItem
import dev.jdtech.jellyfin.utils.isItemAvailable import dev.jdtech.jellyfin.utils.isItemAvailable
import dev.jdtech.jellyfin.utils.isItemDownloaded
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
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.ItemFields import org.jellyfin.sdk.model.api.ItemFields