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.lifecycle.runtime)
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.ui)
implementation(libs.androidx.paging)
@ -87,8 +90,6 @@ dependencies {
kapt(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.exoplayer.core)
implementation(libs.exoplayer.ui)
implementation(libs.glide)
kapt(libs.glide.compiler)
implementation(libs.hilt.android)
@ -97,9 +98,9 @@ dependencies {
implementation(libs.material)
implementation(libs.timber)
// ExoPlayer FFmpeg extension
implementation(files("libs/extension-ffmpeg-release.aar"))
// MPV
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 androidx.appcompat.app.AppCompatActivity
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
abstract class BasePlayerActivity : AppCompatActivity() {
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() {
super.onPause()
viewModel.playWhenReady = viewModel.player.playWhenReady == true
viewModel.player.playWhenReady = false
}
override fun onResume() {
super.onResume()
viewModel.player.playWhenReady = viewModel.playWhenReady
hideSystemUI()
override fun onStop() {
super.onStop()
mediaSession.release()
}
@Suppress("DEPRECATION")

View file

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

View file

@ -12,32 +12,31 @@ import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.TextureView
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 `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.FileOutputStream
import java.util.concurrent.CopyOnWriteArraySet
@ -177,6 +176,7 @@ class MPVPlayer(
var currentMpvTracks: List<Track> = emptyList()
private var initialCommands = mutableListOf<Array<String>>()
private var initialSeekTo: Long = 0L
private var trackSelectionParameters: TrackSelectionParameters = TrackSelectionParameters.Builder(context).build()
// mpv events
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 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
* 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(
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 period The [com.google.android.exoplayer2.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 period The [androidx.media3.common.Timeline.Period] to populate. Must not be null.
* @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 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 {
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.
* @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].
*
* @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.
* @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
* `startWindowIndex` is set to [com.google.android.exoplayer2.C.INDEX_UNSET], this parameter is ignored and the
* @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 [C.INDEX_UNSET], this parameter is ignored and the
* 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.
*/
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.
*
*
@ -625,8 +625,8 @@ class MPVPlayer(
* [.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM] and [.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM]
* are unavailable if there is no such [MediaItem].
*
* @return The currently available [com.google.android.exoplayer2.Player.Commands].
* @see com.google.android.exoplayer2.Player.Listener.onAvailableCommandsChanged
* @return The currently available [Commands].
* @see androidx.media3.common.Player.Listener.onAvailableCommandsChanged
*/
override fun getAvailableCommands(): Commands {
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].
* @see com.google.android.exoplayer2.Player.Listener.onPlaybackStateChanged
* @return The current [playback state][androidx.media3.common.Player.State].
* @see androidx.media3.common.Player.Listener.onPlaybackStateChanged
*/
override fun getPlaybackState(): Int {
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.
*
* @return The current [playback suppression reason][com.google.android.exoplayer2.Player.PlaybackSuppressionReason].
* @see com.google.android.exoplayer2.Player.Listener.onPlaybackSuppressionReasonChanged
* @return The current [playback suppression reason][androidx.media3.common.Player.PlaybackSuppressionReason].
* @see androidx.media3.common.Player.Listener.onPlaybackSuppressionReasonChanged
*/
override fun getPlaybackSuppressionReason(): Int {
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
* 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.
*
*
@ -711,7 +711,7 @@ class MPVPlayer(
* [.STATE_IDLE].
*
* @return The error, or `null`.
* @see com.google.android.exoplayer2.Player.Listener.onPlayerError
* @see androidx.media3.common.Player.Listener.onPlayerError
*/
override fun getPlayerError(): ExoPlaybackException? {
return null
@ -741,14 +741,14 @@ class MPVPlayer(
* Whether playback will proceed when [.getPlaybackState] == [.STATE_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 {
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.
*/
@ -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.
* @see com.google.android.exoplayer2.Player.Listener.onRepeatModeChanged
* @see androidx.media3.common.Player.Listener.onRepeatModeChanged
*/
override fun getRepeatMode(): Int {
return repeatMode
@ -778,7 +778,7 @@ class MPVPlayer(
/**
* 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 {
return false
@ -788,7 +788,7 @@ class MPVPlayer(
* 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 {
return false
@ -798,9 +798,9 @@ class MPVPlayer(
* Seeks to a position specified in milliseconds in the specified 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.
* @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.
*/
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.
*
*
* 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.
*
* @param playbackParameters The playback parameters.
@ -880,7 +880,7 @@ class MPVPlayer(
/**
* 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 {
return playbackParameters
@ -914,11 +914,11 @@ class MPVPlayer(
}
override fun getTrackSelectionParameters(): TrackSelectionParameters {
TODO("Not yet implemented")
return 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
* static and dynamic metadata sourced from [com.google.android.exoplayer2.Player.Listener.onMediaMetadataChanged] and
* [com.google.android.exoplayer2.metadata.MetadataOutput.onMetadata].
* static and dynamic metadata sourced from [androidx.media3.common.Player.Listener.onMediaMetadataChanged] and
* [androidx.media3.exoplayer.metadata.MetadataOutput.onMetadata].
*/
override fun getMediaMetadata(): MediaMetadata {
return MediaMetadata.EMPTY
}
override fun getPlaylistMetadata(): MediaMetadata {
TODO("Not yet implemented")
return MediaMetadata.EMPTY
}
override fun setPlaylistMetadata(mediaMetadata: MediaMetadata) {
@ -945,7 +945,7 @@ class MPVPlayer(
/**
* 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 {
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 {
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
* currently being played. Returns [com.google.android.exoplayer2.C.INDEX_UNSET] otherwise.
* currently being played. Returns [C.INDEX_UNSET] otherwise.
*/
override fun getCurrentAdGroupIndex(): Int {
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
* [com.google.android.exoplayer2.C.INDEX_UNSET] otherwise.
* [C.INDEX_UNSET] otherwise.
*/
override fun getCurrentAdIndexInAdGroup(): Int {
return C.INDEX_UNSET
@ -1051,7 +1051,7 @@ class MPVPlayer(
* @return The linear gain applied to all audio channels.
*/
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
* determined yet.
*
* @see com.google.android.exoplayer2.Player.Listener.onVideoSizeChanged
* @see androidx.media3.common.Player.Listener.onVideoSizeChanged
*/
override fun getVideoSize(): VideoSize {
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 {
TODO("Not yet implemented")
return CueGroup(emptyList(), 0)
}
/** Gets the device information. */
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
* 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
* [Util.getStreamTypeForAudioUsage].
*
@ -1193,12 +1193,12 @@ class MPVPlayer(
* remote device is returned.
*/
override fun getDeviceVolume(): Int {
TODO("Not yet implemented")
return MPVLib.getPropertyInt("volume")
}
/** Gets whether the device is muted or not. */
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)
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)
bind()
@ -51,7 +51,7 @@ internal class TvPlayerActivity : BasePlayerActivity() {
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return if (!binding.playerView.isControllerVisible) {
return if (!binding.playerView.isControllerFullyVisible) {
binding.playerView.showController()
true
} else {

View file

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

View file

@ -9,6 +9,7 @@ object Constants {
// pref
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_VB = "pref_player_gestures_vb"
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.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.StyledPlayerView
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import dev.jdtech.jellyfin.PlayerActivity
import dev.jdtech.jellyfin.mpv.MPVPlayer
import kotlin.math.abs
@ -20,7 +20,7 @@ import timber.log.Timber
class PlayerGestureHelper(
private val appPreferences: AppPreferences,
private val activity: PlayerActivity,
private val playerView: StyledPlayerView,
private val playerView: PlayerView,
private val audioManager: AudioManager
) {
/**

View file

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

View file

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

View file

@ -6,15 +6,13 @@
android:layout_height="match_parent"
tools:context=".PlayerActivity">
<com.google.android.exoplayer2.ui.StyledPlayerView
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
app:animation_enabled="false"
app:show_buffering="always">
</com.google.android.exoplayer2.ui.StyledPlayerView>
app:show_buffering="always" />
<LinearLayout
android:id="@+id/progress_scrubber_layout"

View file

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

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ androidx-constraintlayout = "2.1.4"
androidx-core = "1.9.0"
androidx-leanback = "1.2.0-alpha02"
androidx-lifecycle = "2.5.1"
androidx-media3 = "1.0.0-beta03"
androidx-navigation = "2.5.3"
androidx-paging = "3.1.1"
androidx-preference = "1.2.0"
@ -14,7 +15,6 @@ androidx-recyclerview = "1.2.1"
androidx-recyclerview-selection = "1.1.0"
androidx-room = "2.4.3"
androidx-swiperefreshlayout = "1.1.0"
exoplayer = "2.18.2"
glide = "4.14.2"
hilt = "2.44.2"
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-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-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-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
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-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
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-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }