diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt index ff7699a6..6eddb51c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt @@ -5,20 +5,14 @@ import androidx.annotation.OptIn import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember -import androidx.media3.common.AudioAttributes -import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.common.Timeline import androidx.media3.common.util.UnstableApi -import androidx.media3.datasource.okhttp.OkHttpDataSource -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.session.MediaSession import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.AudioItem import com.github.damontecres.wholphin.data.model.BaseItem -import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.main.settings.MoveDirection @@ -44,7 +38,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.instantMixApi import org.jellyfin.sdk.api.client.extensions.universalAudioApi @@ -74,9 +67,9 @@ class MusicService @Inject constructor( @param:ApplicationContext private val context: Context, - @param:AuthOkHttpClient private val authOkHttpClient: OkHttpClient, @param:DefaultCoroutineScope private val defaultScope: CoroutineScope, private val api: ApiClient, + private val playerFactory: PlayerFactory, private val serverRepository: ServerRepository, private val imageUrlService: ImageUrlService, ) { @@ -86,21 +79,9 @@ class MusicService private val audioFormats by lazy { listOf(*supportedAudioCodecs) } val player: Player by lazy { - ExoPlayer - .Builder(context) - .setMediaSourceFactory( - DefaultMediaSourceFactory( - OkHttpDataSource.Factory(authOkHttpClient), - ), - ).build() + playerFactory + .createAudioPlayer() .also { - it.setAudioAttributes( - AudioAttributes - .Builder() - .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) - .build(), - false, - ) it.addListener(MusicPlayerListener(it, _state)) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt index f8d5ad2b..da00fd3d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt @@ -6,26 +6,29 @@ import android.content.Context import android.os.Build import android.os.Handler import androidx.annotation.OptIn -import androidx.datastore.core.DataStore +import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.Player +import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences import androidx.media3.common.util.ExperimentalApi import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.Renderer import androidx.media3.exoplayer.RenderersFactory import androidx.media3.exoplayer.mediacodec.MediaCodecSelector import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.video.MediaCodecVideoRenderer import androidx.media3.exoplayer.video.VideoRendererEventListener import androidx.media3.extractor.DefaultExtractorsFactory -import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AssPlaybackMode import com.github.damontecres.wholphin.preferences.MediaExtensionStatus import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.PlayerBackend +import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.util.mpv.MpvPlayer import dagger.hilt.android.qualifiers.ApplicationContext import io.github.peerless2012.ass.media.AssHandler @@ -35,6 +38,7 @@ import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory import io.github.peerless2012.ass.media.type.AssRenderType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient import timber.log.Timber import java.lang.reflect.Constructor import javax.inject.Inject @@ -48,7 +52,7 @@ class PlayerFactory @Inject constructor( @param:ApplicationContext private val context: Context, - private val appPreferences: DataStore, + @param:AuthOkHttpClient private val authOkHttpClient: OkHttpClient, ) { @Volatile var currentPlayer: Player? = null @@ -95,7 +99,7 @@ class PlayerFactory else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON } val dataSourceFactory = DefaultDataSource.Factory(context) - val extractorsFactory = DefaultExtractorsFactory() + val extractorsFactory = createExtractorsFactory() var renderersFactory: RenderersFactory = WholphinRenderersFactory(context, decodeAv1) .setEnableDecoderFallback(true) @@ -118,13 +122,25 @@ class PlayerFactory extractorsFactory, ) } + val trackSelector = createTrackSelector() + ExoPlayer .Builder(context) .setMediaSourceFactory(mediaSourceFactory) .setRenderersFactory(renderersFactory) + .setTrackSelector(trackSelector) .build() .apply { assHandler?.init(this) + withContext(Dispatchers.Main) { + setAudioAttributes( + AudioAttributes + .Builder() + .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) + .build(), + false, + ) + } } } @@ -135,6 +151,60 @@ class PlayerFactory currentPlayer = newPlayer return PlayerCreation(newPlayer, assHandler) } + + fun createAudioPlayer(extensions: MediaExtensionStatus = MediaExtensionStatus.MES_FALLBACK): ExoPlayer { + val rendererMode = + when (extensions) { + MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON + MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF + else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON + } + val extractorsFactory = createExtractorsFactory() + val renderersFactory: RenderersFactory = + WholphinRenderersFactory(context, false) + .setEnableDecoderFallback(true) + .setExtensionRendererMode(rendererMode) + val mediaSourceFactory = + DefaultMediaSourceFactory( + OkHttpDataSource.Factory(authOkHttpClient), + extractorsFactory, + ) + val trackSelector = createTrackSelector() + return ExoPlayer + .Builder(context) + .setMediaSourceFactory(mediaSourceFactory) + .setRenderersFactory(renderersFactory) + .setTrackSelector(trackSelector) + .build() + .also { + it.setAudioAttributes( + AudioAttributes + .Builder() + .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) + .build(), + false, + ) + } + } + + private fun createExtractorsFactory() = + DefaultExtractorsFactory() + .setConstantBitrateSeekingEnabled(true) + .setConstantBitrateSeekingAlwaysEnabled(true) + + private fun createTrackSelector() = + DefaultTrackSelector(context).apply { + setParameters( + buildUponParameters() + .setAudioOffloadPreferences( + AudioOffloadPreferences + .Builder() + .setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) + .build(), + ), + ) + } } val Player.isReleased: Boolean