Migrate to androidx media3 (#213)

* Migrate to media3

* Update docs

* Move display_extended_title to AppPreferences

* Move display_extended_title to AppPreferences p2

* Add MediaSession support to the player

* Fix mpv player

* Disable animations on tv player controls and rename the tv_control_view file

* New media3 decoder ffmpeg
This commit is contained in:
Jarne Demeulemeester 2022-12-10 11:33:16 +01:00 committed by GitHub
parent 6ed7e12035
commit 65f4c2f639
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 156 additions and 134 deletions

View file

@ -77,6 +77,9 @@ dependencies {
implementation(libs.androidx.leanback) implementation(libs.androidx.leanback)
implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.session)
implementation(libs.androidx.navigation.fragment) implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.ui) implementation(libs.androidx.navigation.ui)
implementation(libs.androidx.paging) implementation(libs.androidx.paging)
@ -87,8 +90,6 @@ dependencies {
kapt(libs.androidx.room.compiler) kapt(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx) implementation(libs.androidx.room.ktx)
implementation(libs.androidx.swiperefreshlayout) implementation(libs.androidx.swiperefreshlayout)
implementation(libs.exoplayer.core)
implementation(libs.exoplayer.ui)
implementation(libs.glide) implementation(libs.glide)
kapt(libs.glide.compiler) kapt(libs.glide.compiler)
implementation(libs.hilt.android) implementation(libs.hilt.android)
@ -97,9 +98,9 @@ dependencies {
implementation(libs.material) implementation(libs.material)
implementation(libs.timber) implementation(libs.timber)
// ExoPlayer FFmpeg extension
implementation(files("libs/extension-ffmpeg-release.aar"))
// MPV // MPV
implementation(files("libs/libmpv.aar")) implementation(files("libs/libmpv.aar"))
// Media3 FFmpeg decoder
implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
} }

Binary file not shown.

7
app/lint.xml Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<lint>
<issue id="UnsafeOptInUsageError">
<ignore
regexp='\(markerClass = androidx\.media3\.common\.util\.UnstableApi\.class\)' />
</issue>
</lint>

View file

@ -5,23 +5,39 @@ import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.exoplayer2.trackselection.MappingTrackSelector import androidx.media3.exoplayer.trackselection.MappingTrackSelector
import androidx.media3.session.MediaSession
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
abstract class BasePlayerActivity : AppCompatActivity() { abstract class BasePlayerActivity : AppCompatActivity() {
abstract val viewModel: PlayerActivityViewModel abstract val viewModel: PlayerActivityViewModel
lateinit var mediaSession: MediaSession
override fun onStart() {
super.onStart()
mediaSession = MediaSession.Builder(this, viewModel.player).build()
}
override fun onResume() {
super.onResume()
viewModel.player.playWhenReady = viewModel.playWhenReady
hideSystemUI()
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
viewModel.playWhenReady = viewModel.player.playWhenReady == true viewModel.playWhenReady = viewModel.player.playWhenReady == true
viewModel.player.playWhenReady = false viewModel.player.playWhenReady = false
} }
override fun onResume() { override fun onStop() {
super.onResume() super.onStop()
viewModel.player.playWhenReady = viewModel.playWhenReady
hideSystemUI() mediaSession.release()
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")

View file

@ -8,10 +8,10 @@ import android.view.WindowManager
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.media3.common.C
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.TrackSelectionDialogBuilder
import androidx.navigation.navArgs import androidx.navigation.navArgs
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer
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.SpeedSelectionDialogFragment import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment

View file

@ -12,32 +12,31 @@ import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.TextureView import android.view.TextureView
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.google.android.exoplayer2.BasePlayer
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.DeviceInfo
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.MediaMetadata
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Player.Commands
import com.google.android.exoplayer2.Timeline
import com.google.android.exoplayer2.Tracks
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.source.TrackGroup
import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.text.CueGroup
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters
import com.google.android.exoplayer2.util.Clock
import com.google.android.exoplayer2.util.FlagSet
import com.google.android.exoplayer2.util.ListenerSet
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.exoplayer2.util.Size
import com.google.android.exoplayer2.util.Util
import com.google.android.exoplayer2.video.VideoSize
import dev.jdtech.jellyfin.utils.AppPreferences import dev.jdtech.jellyfin.utils.AppPreferences
import `is`.xyz.mpv.MPVLib import `is`.xyz.mpv.MPVLib
import androidx.media3.common.AudioAttributes
import androidx.media3.common.BasePlayer
import androidx.media3.common.C
import androidx.media3.common.DeviceInfo
import androidx.media3.common.FlagSet
import androidx.media3.common.Format
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.Player.Commands
import androidx.media3.common.Timeline
import androidx.media3.common.TrackGroup
import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.Tracks
import androidx.media3.common.VideoSize
import androidx.media3.common.text.CueGroup
import androidx.media3.common.util.Clock
import androidx.media3.common.util.ListenerSet
import androidx.media3.common.util.Size
import androidx.media3.common.util.Util
import androidx.media3.exoplayer.ExoPlaybackException
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.CopyOnWriteArraySet
@ -177,6 +176,7 @@ class MPVPlayer(
var currentMpvTracks: List<Track> = emptyList() var currentMpvTracks: 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
private var trackSelectionParameters: TrackSelectionParameters = TrackSelectionParameters.Builder(context).build()
// mpv events // mpv events
override fun eventProperty(property: String) { override fun eventProperty(property: String) {
@ -386,13 +386,13 @@ class MPVPlayer(
} }
/** /**
* Populates a [com.google.android.exoplayer2.Timeline.Window] with data for the window at the specified index. * Populates a [androidx.media3.common.Timeline.Window] with data for the window at the specified index.
* *
* @param windowIndex The index of the window. * @param windowIndex The index of the window.
* @param window The [com.google.android.exoplayer2.Timeline.Window] to populate. Must not be null. * @param window The [androidx.media3.common.Timeline.Window] to populate. Must not be null.
* @param defaultPositionProjectionUs A duration into the future that the populated window's * @param defaultPositionProjectionUs A duration into the future that the populated window's
* 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 [androidx.media3.common.Timeline.Window], for convenience.
*/ */
override fun getWindow( override fun getWindow(
windowIndex: Int, windowIndex: Int,
@ -427,14 +427,14 @@ class MPVPlayer(
} }
/** /**
* Populates a [com.google.android.exoplayer2.Timeline.Period] with data for the period at the specified index. * Populates a [androidx.media3.common.Timeline.Period] with data for the period at the specified index.
* *
* @param periodIndex The index of the period. * @param periodIndex The index of the period.
* @param period The [com.google.android.exoplayer2.Timeline.Period] to populate. Must not be null. * @param period The [androidx.media3.common.Timeline.Period] to populate. Must not be null.
* @param setIds Whether [com.google.android.exoplayer2.Timeline.Period.id] and [com.google.android.exoplayer2.Timeline.Period.uid] should be populated. If false, * @param setIds Whether [androidx.media3.common.Timeline.Period.id] and [androidx.media3.common.Timeline.Period.uid] should be populated. If false,
* the fields will be set to null. The caller should pass false for efficiency reasons unless * the fields will be set to null. The caller should pass false for efficiency reasons unless
* the fields are required. * the fields are required.
* @return The populated [com.google.android.exoplayer2.Timeline.Period], for convenience. * @return The populated [androidx.media3.common.Timeline.Period], for convenience.
*/ */
override fun getPeriod(periodIndex: Int, period: Period, setIds: Boolean): Period { override fun getPeriod(periodIndex: Int, period: Period, setIds: Boolean): Period {
return period.set( return period.set(
@ -447,7 +447,7 @@ class MPVPlayer(
} }
/** /**
* Returns the index of the period identified by its unique [com.google.android.exoplayer2.Timeline.Period.uid], or [ ][C.INDEX_UNSET] if the period is not in the timeline. * Returns the index of the period identified by its unique [Timeline.Period.uid], or [ ][C.INDEX_UNSET] if the period is not in the timeline.
* *
* @param uid A unique identifier for a period. * @param uid A unique identifier for a period.
* @return The index of the period, or [C.INDEX_UNSET] if the period was not found. * @return The index of the period, or [C.INDEX_UNSET] if the period was not found.
@ -557,12 +557,12 @@ class MPVPlayer(
* Clears the playlist and adds the specified [MediaItems][MediaItem]. * Clears the playlist and adds the specified [MediaItems][MediaItem].
* *
* @param mediaItems The new [MediaItems][MediaItem]. * @param mediaItems The new [MediaItems][MediaItem].
* @param startWindowIndex The window index to start playback from. If [com.google.android.exoplayer2.C.INDEX_UNSET] is * @param startWindowIndex The window index to start playback from. If [C.INDEX_UNSET] is
* passed, the current position is not reset. * passed, the current position is not reset.
* @param startPositionMs The position in milliseconds to start playback from. If [ ][com.google.android.exoplayer2.C.TIME_UNSET] is passed, the default position of the given window is used. In any case, if * @param startPositionMs The position in milliseconds to start playback from. If [ ][C.TIME_UNSET] is passed, the default position of the given window is used. In any case, if
* `startWindowIndex` is set to [com.google.android.exoplayer2.C.INDEX_UNSET], this parameter is ignored and the * `startWindowIndex` is set to [C.INDEX_UNSET], this parameter is ignored and the
* position is not reset at all. * position is not reset at all.
* @throws com.google.android.exoplayer2.IllegalSeekPositionException If the provided `startWindowIndex` is not within the * @throws androidx.media3.common.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( override fun setMediaItems(
@ -611,10 +611,10 @@ class MPVPlayer(
} }
/** /**
* Returns the player's currently available [com.google.android.exoplayer2.Player.Commands]. * Returns the player's currently available [Commands].
* *
* *
* The returned [com.google.android.exoplayer2.Player.Commands] are not updated when available commands change. Use [ ][com.google.android.exoplayer2.Player.Listener.onAvailableCommandsChanged] to get an update when the available commands * The returned [Commands] are not updated when available commands change. Use [ ][androidx.media3.common.Player.Listener.onAvailableCommandsChanged] to get an update when the available commands
* change. * change.
* *
* *
@ -625,8 +625,8 @@ class MPVPlayer(
* [.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM] and [.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM] * [.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM] and [.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM]
* are unavailable if there is no such [MediaItem]. * are unavailable if there is no such [MediaItem].
* *
* @return The currently available [com.google.android.exoplayer2.Player.Commands]. * @return The currently available [Commands].
* @see com.google.android.exoplayer2.Player.Listener.onAvailableCommandsChanged * @see androidx.media3.common.Player.Listener.onAvailableCommandsChanged
*/ */
override fun getAvailableCommands(): Commands { override fun getAvailableCommands(): Commands {
return Commands.Builder() return Commands.Builder()
@ -682,10 +682,10 @@ class MPVPlayer(
} }
/** /**
* Returns the current [playback state][com.google.android.exoplayer2.Player.State] of the player. * Returns the current [playback state][androidx.media3.common.Player.State] of the player.
* *
* @return The current [playback state][com.google.android.exoplayer2.Player.State]. * @return The current [playback state][androidx.media3.common.Player.State].
* @see com.google.android.exoplayer2.Player.Listener.onPlaybackStateChanged * @see androidx.media3.common.Player.Listener.onPlaybackStateChanged
*/ */
override fun getPlaybackState(): Int { override fun getPlaybackState(): Int {
return playbackState return playbackState
@ -694,8 +694,8 @@ class MPVPlayer(
/** /**
* Returns the reason why playback is suppressed even though [.getPlayWhenReady] is `true`, or [.PLAYBACK_SUPPRESSION_REASON_NONE] if playback is not suppressed. * Returns the reason why playback is suppressed even though [.getPlayWhenReady] is `true`, or [.PLAYBACK_SUPPRESSION_REASON_NONE] if playback is not suppressed.
* *
* @return The current [playback suppression reason][com.google.android.exoplayer2.Player.PlaybackSuppressionReason]. * @return The current [playback suppression reason][androidx.media3.common.Player.PlaybackSuppressionReason].
* @see com.google.android.exoplayer2.Player.Listener.onPlaybackSuppressionReasonChanged * @see androidx.media3.common.Player.Listener.onPlaybackSuppressionReasonChanged
*/ */
override fun getPlaybackSuppressionReason(): Int { override fun getPlaybackSuppressionReason(): Int {
return PLAYBACK_SUPPRESSION_REASON_NONE return PLAYBACK_SUPPRESSION_REASON_NONE
@ -703,7 +703,7 @@ class MPVPlayer(
/** /**
* Returns the error that caused playback to fail. This is the same error that will have been * Returns the error that caused playback to fail. This is the same error that will have been
* reported via [com.google.android.exoplayer2.Player.Listener.onPlayerError] at the time of failure. It * reported via [androidx.media3.common.Player.Listener.onPlayerError] at the time of failure. It
* can be queried using this method until the player is re-prepared. * can be queried using this method until the player is re-prepared.
* *
* *
@ -711,7 +711,7 @@ class MPVPlayer(
* [.STATE_IDLE]. * [.STATE_IDLE].
* *
* @return The error, or `null`. * @return The error, or `null`.
* @see com.google.android.exoplayer2.Player.Listener.onPlayerError * @see androidx.media3.common.Player.Listener.onPlayerError
*/ */
override fun getPlayerError(): ExoPlaybackException? { override fun getPlayerError(): ExoPlaybackException? {
return null return null
@ -741,14 +741,14 @@ class MPVPlayer(
* Whether playback will proceed when [.getPlaybackState] == [.STATE_READY]. * Whether playback will proceed when [.getPlaybackState] == [.STATE_READY].
* *
* @return Whether playback will proceed when ready. * @return Whether playback will proceed when ready.
* @see com.google.android.exoplayer2.Player.Listener.onPlayWhenReadyChanged * @see androidx.media3.common.Player.Listener.onPlayWhenReadyChanged
*/ */
override fun getPlayWhenReady(): Boolean { override fun getPlayWhenReady(): Boolean {
return currentPlayWhenReady return currentPlayWhenReady
} }
/** /**
* Sets the [com.google.android.exoplayer2.Player.RepeatMode] to be used for playback. * Sets the [androidx.media3.common.Player.RepeatMode] to be used for playback.
* *
* @param repeatMode The repeat mode. * @param repeatMode The repeat mode.
*/ */
@ -757,10 +757,10 @@ class MPVPlayer(
} }
/** /**
* Returns the current [com.google.android.exoplayer2.Player.RepeatMode] used for playback. * Returns the current [androidx.media3.common.Player.RepeatMode] used for playback.
* *
* @return The current repeat mode. * @return The current repeat mode.
* @see com.google.android.exoplayer2.Player.Listener.onRepeatModeChanged * @see androidx.media3.common.Player.Listener.onRepeatModeChanged
*/ */
override fun getRepeatMode(): Int { override fun getRepeatMode(): Int {
return repeatMode return repeatMode
@ -778,7 +778,7 @@ class MPVPlayer(
/** /**
* Returns whether shuffling of windows is enabled. * Returns whether shuffling of windows is enabled.
* *
* @see com.google.android.exoplayer2.Player.Listener.onShuffleModeEnabledChanged * @see androidx.media3.common.Player.Listener.onShuffleModeEnabledChanged
*/ */
override fun getShuffleModeEnabled(): Boolean { override fun getShuffleModeEnabled(): Boolean {
return false return false
@ -788,7 +788,7 @@ class MPVPlayer(
* Whether the player is currently loading the source. * Whether the player is currently loading the source.
* *
* @return Whether the player is currently loading the source. * @return Whether the player is currently loading the source.
* @see com.google.android.exoplayer2.Player.Listener.onIsLoadingChanged * @see androidx.media3.common.Player.Listener.onIsLoadingChanged
*/ */
override fun isLoading(): Boolean { override fun isLoading(): Boolean {
return false return false
@ -798,9 +798,9 @@ class MPVPlayer(
* Seeks to a position specified in milliseconds in the specified window. * Seeks to a position specified in milliseconds in the specified window.
* *
* @param windowIndex The index of the window. * @param windowIndex The index of the window.
* @param positionMs The seek position in the specified window, or [com.google.android.exoplayer2.C.TIME_UNSET] to seek to * @param positionMs The seek position in the specified window, or [C.TIME_UNSET] to seek to
* the window's default position. * the window's default position.
* @throws com.google.android.exoplayer2.IllegalSeekPositionException If the player has a non-empty timeline and the provided * @throws androidx.media3.common.IllegalSeekPositionException If the player has a non-empty timeline and the provided
* `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) {
@ -866,7 +866,7 @@ class MPVPlayer(
* player to the default, which means there is no speed or pitch adjustment. * player to the default, which means there is no speed or pitch adjustment.
* *
* *
* Playback parameters changes may cause the player to buffer. [ ][com.google.android.exoplayer2.Player.Listener.onPlaybackParametersChanged] will be called whenever the currently * Playback parameters changes may cause the player to buffer. [ ][androidx.media3.common.Player.Listener.onPlaybackParametersChanged] will be called whenever the currently
* active playback parameters change. * active playback parameters change.
* *
* @param playbackParameters The playback parameters. * @param playbackParameters The playback parameters.
@ -880,7 +880,7 @@ class MPVPlayer(
/** /**
* Returns the currently active playback parameters. * Returns the currently active playback parameters.
* *
* @see com.google.android.exoplayer2.Player.Listener.onPlaybackParametersChanged * @see androidx.media3.common.Player.Listener.onPlaybackParametersChanged
*/ */
override fun getPlaybackParameters(): PlaybackParameters { override fun getPlaybackParameters(): PlaybackParameters {
return playbackParameters return playbackParameters
@ -914,11 +914,11 @@ class MPVPlayer(
} }
override fun getTrackSelectionParameters(): TrackSelectionParameters { override fun getTrackSelectionParameters(): TrackSelectionParameters {
TODO("Not yet implemented") return trackSelectionParameters
} }
override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) { override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
TODO("Not yet implemented") trackSelectionParameters = parameters
} }
/** /**
@ -927,15 +927,15 @@ 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.onMediaMetadataChanged] and * static and dynamic metadata sourced from [androidx.media3.common.Player.Listener.onMediaMetadataChanged] and
* [com.google.android.exoplayer2.metadata.MetadataOutput.onMetadata]. * [androidx.media3.exoplayer.metadata.MetadataOutput.onMetadata].
*/ */
override fun getMediaMetadata(): MediaMetadata { override fun getMediaMetadata(): MediaMetadata {
return MediaMetadata.EMPTY return MediaMetadata.EMPTY
} }
override fun getPlaylistMetadata(): MediaMetadata { override fun getPlaylistMetadata(): MediaMetadata {
TODO("Not yet implemented") return MediaMetadata.EMPTY
} }
override fun setPlaylistMetadata(mediaMetadata: MediaMetadata) { override fun setPlaylistMetadata(mediaMetadata: MediaMetadata) {
@ -945,7 +945,7 @@ class MPVPlayer(
/** /**
* Returns the current [Timeline]. Never null, but may be empty. * Returns the current [Timeline]. Never null, but may be empty.
* *
* @see com.google.android.exoplayer2.Player.Listener.onTimelineChanged * @see androidx.media3.common.Player.Listener.onTimelineChanged
*/ */
override fun getCurrentTimeline(): Timeline { override fun getCurrentTimeline(): Timeline {
return timeline return timeline
@ -961,7 +961,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 [ ][C.TIME_UNSET] if the duration is not known.
*/ */
override fun getDuration(): Long { override fun getDuration(): Long {
return timeline.getWindow(currentMediaItemIndex, window).durationMs return timeline.getWindow(currentMediaItemIndex, window).durationMs
@ -999,7 +999,7 @@ class MPVPlayer(
/** /**
* If [.isPlayingAd] returns true, returns the index of the ad group in the period * If [.isPlayingAd] returns true, returns the index of the ad group in the period
* currently being played. Returns [com.google.android.exoplayer2.C.INDEX_UNSET] otherwise. * currently being played. Returns [C.INDEX_UNSET] otherwise.
*/ */
override fun getCurrentAdGroupIndex(): Int { override fun getCurrentAdGroupIndex(): Int {
return C.INDEX_UNSET return C.INDEX_UNSET
@ -1007,7 +1007,7 @@ class MPVPlayer(
/** /**
* If [.isPlayingAd] returns true, returns the index of the ad in its ad group. Returns * If [.isPlayingAd] returns true, returns the index of the ad in its ad group. Returns
* [com.google.android.exoplayer2.C.INDEX_UNSET] otherwise. * [C.INDEX_UNSET] otherwise.
*/ */
override fun getCurrentAdIndexInAdGroup(): Int { override fun getCurrentAdIndexInAdGroup(): Int {
return C.INDEX_UNSET return C.INDEX_UNSET
@ -1051,7 +1051,7 @@ class MPVPlayer(
* @return The linear gain applied to all audio channels. * @return The linear gain applied to all audio channels.
*/ */
override fun getVolume(): Float { override fun getVolume(): Float {
TODO("Not yet implemented") return MPVLib.getPropertyInt("volume") / 100F
} }
/** /**
@ -1154,7 +1154,7 @@ class MPVPlayer(
* The video's width and height are `0` if there is no video or its size has not been * The video's width and height are `0` if there is no video or its size has not been
* determined yet. * determined yet.
* *
* @see com.google.android.exoplayer2.Player.Listener.onVideoSizeChanged * @see androidx.media3.common.Player.Listener.onVideoSizeChanged
*/ */
override fun getVideoSize(): VideoSize { override fun getVideoSize(): VideoSize {
return VideoSize.UNKNOWN return VideoSize.UNKNOWN
@ -1169,14 +1169,14 @@ class MPVPlayer(
} }
} }
/** Returns the current [Cues][Cue]. This list may be empty. */ /** Returns the current [CueGroup]. This list may be empty. */
override fun getCurrentCues(): CueGroup { override fun getCurrentCues(): CueGroup {
TODO("Not yet implemented") return CueGroup(emptyList(), 0)
} }
/** Gets the device information. */ /** Gets the device information. */
override fun getDeviceInfo(): DeviceInfo { override fun getDeviceInfo(): DeviceInfo {
TODO("Not yet implemented") return DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, 0, 100)
} }
/** /**
@ -1184,7 +1184,7 @@ class MPVPlayer(
* *
* *
* For devices with [local playback][DeviceInfo.PLAYBACK_TYPE_LOCAL], the volume returned * For devices with [local playback][DeviceInfo.PLAYBACK_TYPE_LOCAL], the volume returned
* by this method varies according to the current [stream type][com.google.android.exoplayer2.C.StreamType]. The stream * by this method varies according to the current [stream type][C.StreamType]. The stream
* type is determined by [AudioAttributes.usage] which can be converted to stream type with * type is determined by [AudioAttributes.usage] which can be converted to stream type with
* [Util.getStreamTypeForAudioUsage]. * [Util.getStreamTypeForAudioUsage].
* *
@ -1193,12 +1193,12 @@ class MPVPlayer(
* remote device is returned. * remote device is returned.
*/ */
override fun getDeviceVolume(): Int { override fun getDeviceVolume(): Int {
TODO("Not yet implemented") return MPVLib.getPropertyInt("volume")
} }
/** Gets whether the device is muted or not. */ /** Gets whether the device is muted or not. */
override fun isDeviceMuted(): Boolean { override fun isDeviceMuted(): Boolean {
TODO("Not yet implemented") return MPVLib.getPropertyBoolean("mute")
} }
/** /**

View file

@ -42,7 +42,7 @@ internal class TvPlayerActivity : BasePlayerActivity() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding.playerView.player = viewModel.player binding.playerView.player = viewModel.player
val playerControls = binding.playerView.findViewById<View>(R.id.tv_player_controls) val playerControls = binding.playerView.findViewById<View>(R.id.player_controls)
configureInsets(playerControls) configureInsets(playerControls)
bind() bind()
@ -51,7 +51,7 @@ internal class TvPlayerActivity : BasePlayerActivity() {
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return if (!binding.playerView.isControllerVisible) { return if (!binding.playerView.isControllerFullyVisible) {
binding.playerView.showController() binding.playerView.showController()
true true
} else { } else {

View file

@ -3,8 +3,8 @@ package dev.jdtech.jellyfin.utils
import android.content.SharedPreferences import android.content.SharedPreferences
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
import androidx.core.content.edit import androidx.core.content.edit
import com.google.android.exoplayer2.C.DEFAULT_SEEK_BACK_INCREMENT_MS import androidx.media3.common.C.DEFAULT_SEEK_BACK_INCREMENT_MS
import com.google.android.exoplayer2.C.DEFAULT_SEEK_FORWARD_INCREMENT_MS import androidx.media3.common.C.DEFAULT_SEEK_FORWARD_INCREMENT_MS
import javax.inject.Inject import javax.inject.Inject
class AppPreferences class AppPreferences
@ -26,6 +26,8 @@ constructor(
val dynamicColors = sharedPreferences.getBoolean(Constants.PREF_DYNAMIC_COLORS, true) val dynamicColors = sharedPreferences.getBoolean(Constants.PREF_DYNAMIC_COLORS, true)
// Player // Player
val displayExtendedTitle = sharedPreferences.getBoolean(Constants.PREF_DISPLAY_EXTENDED_TITLE, false)
val playerGestures = sharedPreferences.getBoolean(Constants.PREF_PLAYER_GESTURES, true) val playerGestures = sharedPreferences.getBoolean(Constants.PREF_PLAYER_GESTURES, true)
val playerGesturesVB = sharedPreferences.getBoolean(Constants.PREF_PLAYER_GESTURES_VB, true) val playerGesturesVB = sharedPreferences.getBoolean(Constants.PREF_PLAYER_GESTURES_VB, true)
val playerGesturesZoom = sharedPreferences.getBoolean(Constants.PREF_PLAYER_GESTURES_ZOOM, true) val playerGesturesZoom = sharedPreferences.getBoolean(Constants.PREF_PLAYER_GESTURES_ZOOM, true)

View file

@ -9,6 +9,7 @@ object Constants {
// pref // pref
const val PREF_CURRENT_SERVER = "pref_current_server" const val PREF_CURRENT_SERVER = "pref_current_server"
const val PREF_DISPLAY_EXTENDED_TITLE = "pref_player_display_extended_title"
const val PREF_PLAYER_GESTURES = "pref_player_gestures" const val PREF_PLAYER_GESTURES = "pref_player_gestures"
const val PREF_PLAYER_GESTURES_VB = "pref_player_gestures_vb" const val PREF_PLAYER_GESTURES_VB = "pref_player_gestures_vb"
const val PREF_PLAYER_GESTURES_ZOOM = "pref_player_gestures_zoom" const val PREF_PLAYER_GESTURES_ZOOM = "pref_player_gestures_zoom"

View file

@ -10,8 +10,8 @@ import android.view.ScaleGestureDetector
import android.view.View import android.view.View
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import androidx.media3.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.StyledPlayerView import androidx.media3.ui.PlayerView
import dev.jdtech.jellyfin.PlayerActivity import dev.jdtech.jellyfin.PlayerActivity
import dev.jdtech.jellyfin.mpv.MPVPlayer import dev.jdtech.jellyfin.mpv.MPVPlayer
import kotlin.math.abs import kotlin.math.abs
@ -20,7 +20,7 @@ import timber.log.Timber
class PlayerGestureHelper( class PlayerGestureHelper(
private val appPreferences: AppPreferences, private val appPreferences: AppPreferences,
private val activity: PlayerActivity, private val activity: PlayerActivity,
private val playerView: StyledPlayerView, private val playerView: PlayerView,
private val audioManager: AudioManager private val audioManager: AudioManager
) { ) {
/** /**

View file

@ -7,14 +7,14 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData 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.media3.common.AudioAttributes
import com.google.android.exoplayer2.C import androidx.media3.common.C
import com.google.android.exoplayer2.DefaultRenderersFactory import androidx.media3.common.MediaItem
import com.google.android.exoplayer2.ExoPlayer import androidx.media3.common.MediaMetadata
import com.google.android.exoplayer2.MediaItem import androidx.media3.common.Player
import com.google.android.exoplayer2.Player import androidx.media3.exoplayer.DefaultRenderersFactory
import com.google.android.exoplayer2.audio.AudioAttributes import androidx.media3.exoplayer.ExoPlayer
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.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
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
@ -36,7 +36,7 @@ constructor(
application: Application, application: Application,
private val jellyfinRepository: JellyfinRepository, private val jellyfinRepository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao, private val downloadDatabase: DownloadDatabaseDao,
appPreferences: AppPreferences, private val appPreferences: AppPreferences,
) : ViewModel(), Player.Listener { ) : ViewModel(), Player.Listener {
val player: Player val player: Player
@ -63,8 +63,6 @@ constructor(
var playbackSpeed: Float = 1f var playbackSpeed: Float = 1f
var disableSubtitle: Boolean = false var disableSubtitle: Boolean = false
private val sp = PreferenceManager.getDefaultSharedPreferences(application)
init { init {
if (appPreferences.playerMpv) { if (appPreferences.playerMpv) {
player = MPVPlayer( player = MPVPlayer(
@ -126,6 +124,11 @@ constructor(
MediaItem.Builder() MediaItem.Builder()
.setMediaId(item.itemId.toString()) .setMediaId(item.itemId.toString())
.setUri(streamUrl) .setUri(streamUrl)
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(item.name)
.build()
)
.setSubtitleConfigurations(mediaSubtitles) .setSubtitleConfigurations(mediaSubtitles)
.build() .build()
mediaItems.add(mediaItem) mediaItems.add(mediaItem)
@ -135,8 +138,7 @@ constructor(
} }
player.setMediaItems(mediaItems, currentMediaItemIndex, items.getOrNull(currentMediaItemIndex)?.playbackPosition ?: C.TIME_UNSET) player.setMediaItems(mediaItems, currentMediaItemIndex, items.getOrNull(currentMediaItemIndex)?.playbackPosition ?: C.TIME_UNSET)
val useMpv = sp.getBoolean("mpv_player", false) if (!appPreferences.playerMpv || !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
player.play() player.play()
pollPosition(player) pollPosition(player)
@ -196,10 +198,7 @@ constructor(
try { try {
for (item in items) { for (item in items) {
if (item.itemId.toString() == (player.currentMediaItem?.mediaId ?: "")) { if (item.itemId.toString() == (player.currentMediaItem?.mediaId ?: "")) {
if (sp.getBoolean( if (appPreferences.displayExtendedTitle && item.parentIndexNumber != null && item.indexNumber != null && item.name != null
"display_extended_title",
false
) && item.parentIndexNumber != null && item.indexNumber != null && item.name != null
) )
_currentItemTitle.value = _currentItemTitle.value =
"S${item.parentIndexNumber}:E${item.indexNumber} - ${item.name}" "S${item.parentIndexNumber}:E${item.indexNumber} - ${item.name}"

View file

@ -5,7 +5,7 @@ import android.net.Uri
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.android.exoplayer2.util.MimeTypes import androidx.media3.common.MimeTypes
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao

View file

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv_player_controls" android:id="@+id/player_controls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/player_background" android:background="@color/player_background"
@ -66,7 +66,7 @@
app:layout_constraintStart_toEndOf="@id/btn_audio_track" app:layout_constraintStart_toEndOf="@id/btn_audio_track"
/> />
<com.google.android.exoplayer2.ui.DefaultTimeBar <androidx.media3.ui.DefaultTimeBar
android:id="@+id/exo_progress" android:id="@+id/exo_progress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -6,15 +6,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".PlayerActivity"> tools:context=".PlayerActivity">
<com.google.android.exoplayer2.ui.StyledPlayerView <androidx.media3.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:animation_enabled="false" app:animation_enabled="false"
app:show_buffering="always"> app:show_buffering="always" />
</com.google.android.exoplayer2.ui.StyledPlayerView>
<LinearLayout <LinearLayout
android:id="@+id/progress_scrubber_layout" android:id="@+id/progress_scrubber_layout"

View file

@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".PlayerActivity" tools:context=".PlayerActivity">
>
<com.google.android.exoplayer2.ui.PlayerView <androidx.media3.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:controller_layout_id="@layout/tv_player_controls" app:animation_enabled="false"
app:show_buffering="always" app:show_buffering="always" />
/>
</FrameLayout> </FrameLayout>

View file

@ -212,7 +212,7 @@
</LinearLayout> </LinearLayout>
<com.google.android.exoplayer2.ui.DefaultTimeBar <androidx.media3.ui.DefaultTimeBar
android:id="@+id/exo_progress" android:id="@+id/exo_progress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -2,7 +2,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android" <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout <androidx.media3.ui.AspectRatioFrameLayout
android:id="@id/exo_content_frame" android:id="@id/exo_content_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -46,9 +46,9 @@
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="14sp" /> android:textSize="14sp" />
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout> </androidx.media3.ui.AspectRatioFrameLayout>
<com.google.android.exoplayer2.ui.SubtitleView <androidx.media3.ui.SubtitleView
android:id="@id/exo_subtitles" android:id="@id/exo_subtitles"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"> <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference <SwitchPreference
app:key="display_extended_title" app:key="pref_player_display_extended_title"
app:summary="@string/display_extended_title_summary" app:summary="@string/display_extended_title_summary"
app:title="@string/display_extended_title" /> app:title="@string/display_extended_title" />

View file

@ -7,6 +7,7 @@ androidx-constraintlayout = "2.1.4"
androidx-core = "1.9.0" androidx-core = "1.9.0"
androidx-leanback = "1.2.0-alpha02" androidx-leanback = "1.2.0-alpha02"
androidx-lifecycle = "2.5.1" androidx-lifecycle = "2.5.1"
androidx-media3 = "1.0.0-beta03"
androidx-navigation = "2.5.3" androidx-navigation = "2.5.3"
androidx-paging = "3.1.1" androidx-paging = "3.1.1"
androidx-preference = "1.2.0" androidx-preference = "1.2.0"
@ -14,7 +15,6 @@ androidx-recyclerview = "1.2.1"
androidx-recyclerview-selection = "1.1.0" androidx-recyclerview-selection = "1.1.0"
androidx-room = "2.4.3" androidx-room = "2.4.3"
androidx-swiperefreshlayout = "1.1.0" androidx-swiperefreshlayout = "1.1.0"
exoplayer = "2.18.2"
glide = "4.14.2" glide = "4.14.2"
hilt = "2.44.2" hilt = "2.44.2"
jellyfin = "1.3.7" jellyfin = "1.3.7"
@ -33,6 +33,9 @@ androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-cor
androidx-leanback = { module = "androidx.leanback:leanback", version.ref = "androidx-leanback" } androidx-leanback = { module = "androidx.leanback:leanback", version.ref = "androidx-leanback" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" }
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "androidx-media3" }
androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "androidx-media3" }
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" } androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" }
androidx-navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" } androidx-navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version.ref = "androidx-paging" } androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version.ref = "androidx-paging" }
@ -43,8 +46,6 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefreshlayout" } androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefreshlayout" }
exoplayer-core = { module = "com.google.android.exoplayer:exoplayer-core", version.ref = "exoplayer" }
exoplayer-ui = { module = "com.google.android.exoplayer:exoplayer-ui", version.ref = "exoplayer" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" } glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }