mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Play theme songs for series
This commit is contained in:
parent
ba92001412
commit
1816317c68
5 changed files with 102 additions and 17 deletions
|
|
@ -25,6 +25,7 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Surface
|
||||
import com.github.damontecres.dolphin.data.ServerRepository
|
||||
import com.github.damontecres.dolphin.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.dolphin.preferences.AppPreferences
|
||||
import com.github.damontecres.dolphin.preferences.DefaultUserConfiguration
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
|
|
@ -48,6 +49,7 @@ class MainActivity : AppCompatActivity() {
|
|||
@Inject
|
||||
lateinit var userPreferencesDataStore: DataStore<AppPreferences>
|
||||
|
||||
@AuthOkHttpClient
|
||||
@Inject
|
||||
lateinit var okHttpClient: OkHttpClient
|
||||
|
||||
|
|
@ -64,7 +66,7 @@ class MainActivity : AppCompatActivity() {
|
|||
.background(MaterialTheme.colorScheme.background),
|
||||
shape = RectangleShape,
|
||||
) {
|
||||
CoilConfig(serverRepository, okHttpClient, false)
|
||||
CoilConfig(okHttpClient, false)
|
||||
|
||||
var isRestoringSession by remember { mutableStateOf(true) }
|
||||
val appPreferences by userPreferencesDataStore.data.collectAsState(null)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import com.github.damontecres.dolphin.BuildConfig
|
||||
import com.github.damontecres.dolphin.data.ServerRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,11 +16,21 @@ import org.jellyfin.sdk.api.okhttp.OkHttpFactory
|
|||
import org.jellyfin.sdk.createJellyfin
|
||||
import org.jellyfin.sdk.model.ClientInfo
|
||||
import org.jellyfin.sdk.model.DeviceInfo
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class AuthOkHttpClient
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class StandardOkHttpClient
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppModule {
|
||||
@StandardOkHttpClient
|
||||
@Provides
|
||||
@Singleton
|
||||
fun okHttpClient() =
|
||||
|
|
@ -29,9 +40,33 @@ object AppModule {
|
|||
// TODO user agent, timeouts, logging, etc
|
||||
}.build()
|
||||
|
||||
@AuthOkHttpClient
|
||||
@Provides
|
||||
@Singleton
|
||||
fun okHttpFactory(okHttpClient: OkHttpClient) = OkHttpFactory(okHttpClient)
|
||||
fun authOkHttpClient(
|
||||
serverRepository: ServerRepository,
|
||||
@StandardOkHttpClient okHttpClient: OkHttpClient,
|
||||
) = okHttpClient
|
||||
.newBuilder()
|
||||
.addInterceptor {
|
||||
val request = it.request()
|
||||
val newRequest =
|
||||
serverRepository.currentUser?.accessToken?.let { token ->
|
||||
request
|
||||
.newBuilder()
|
||||
.addHeader(
|
||||
"Authorization",
|
||||
"MediaBrowser Token=\"$token\"",
|
||||
).build()
|
||||
}
|
||||
it.proceed(newRequest ?: request)
|
||||
}.build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun okHttpFactory(
|
||||
@StandardOkHttpClient okHttpClient: OkHttpClient,
|
||||
) = OkHttpFactory(okHttpClient)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -10,15 +10,12 @@ import coil3.network.cachecontrol.CacheControlCacheStrategy
|
|||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import coil3.request.crossfade
|
||||
import coil3.util.DebugLogger
|
||||
import com.github.damontecres.dolphin.data.ServerRepository
|
||||
import okhttp3.Call
|
||||
import okhttp3.OkHttpClient
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@OptIn(ExperimentalTime::class, ExperimentalCoilApi::class)
|
||||
@Composable
|
||||
fun CoilConfig(
|
||||
serverRepository: ServerRepository,
|
||||
okHttpClient: OkHttpClient,
|
||||
debugLogging: Boolean,
|
||||
) {
|
||||
|
|
@ -37,18 +34,7 @@ fun CoilConfig(
|
|||
add(
|
||||
OkHttpNetworkFetcherFactory(
|
||||
cacheStrategy = { CacheControlCacheStrategy() },
|
||||
callFactory = {
|
||||
Call.Factory { request ->
|
||||
// Ref: https://gist.github.com/nielsvanvelzen/ea047d9028f676185832e51ffaf12a6f
|
||||
val token = serverRepository.currentUser?.accessToken
|
||||
okHttpClient.newCall(
|
||||
request
|
||||
.newBuilder()
|
||||
.addHeader("Authorization", "MediaBrowser Token=\"$token\"")
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
},
|
||||
callFactory = { okHttpClient },
|
||||
),
|
||||
)
|
||||
}.build()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.github.damontecres.dolphin.ui.detail
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -10,9 +12,16 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
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.tv.material3.Text
|
||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||
import com.github.damontecres.dolphin.data.model.Video
|
||||
import com.github.damontecres.dolphin.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
||||
|
|
@ -26,12 +35,16 @@ import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
|||
import com.github.damontecres.dolphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.dolphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.api.client.extensions.universalAudioApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ItemFields
|
||||
|
|
@ -48,7 +61,10 @@ class SeriesViewModel
|
|||
@Inject
|
||||
constructor(
|
||||
api: ApiClient,
|
||||
@param:ApplicationContext val context: Context,
|
||||
@param:AuthOkHttpClient private val okHttpClient: OkHttpClient,
|
||||
) : ItemViewModel<Video>(api) {
|
||||
private var player: Player? = null
|
||||
private lateinit var seriesId: UUID
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
val seasons = MutableLiveData<List<BaseItem?>>(listOf())
|
||||
|
|
@ -80,6 +96,7 @@ class SeriesViewModel
|
|||
episodes.value = episodePager.orEmpty()
|
||||
loading.value = LoadingState.Success
|
||||
}
|
||||
maybePlayThemeSong()
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
seasons.value = listOf()
|
||||
|
|
@ -90,6 +107,49 @@ class SeriesViewModel
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun maybePlayThemeSong() {
|
||||
// TODO user preference to enable/disable this
|
||||
viewModelScope.launch(ExceptionHandler()) {
|
||||
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
|
||||
themeSongs.items.firstOrNull()?.let { theme ->
|
||||
theme.mediaSources?.firstOrNull()?.let { source ->
|
||||
val url =
|
||||
api.universalAudioApi.getUniversalAudioStreamUrl(
|
||||
theme.id,
|
||||
container = listOf("opus", "mp3", "aaa", "flac"),
|
||||
)
|
||||
Timber.v("Found theme song for series $seriesId")
|
||||
withContext(Dispatchers.Main) {
|
||||
val player =
|
||||
ExoPlayer
|
||||
.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
DefaultMediaSourceFactory(
|
||||
OkHttpDataSource.Factory(okHttpClient),
|
||||
),
|
||||
).build()
|
||||
.apply {
|
||||
volume = .1f
|
||||
playWhenReady = true
|
||||
this@SeriesViewModel.player = this
|
||||
}
|
||||
addCloseable {
|
||||
player.release()
|
||||
}
|
||||
player.setMediaItem(MediaItem.fromUri(url))
|
||||
player.prepare()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
player?.release()
|
||||
player = null
|
||||
}
|
||||
|
||||
private suspend fun getSeasons(item: BaseItem): ApiRequestPager<GetItemsRequest>? {
|
||||
val request =
|
||||
GetItemsRequest(
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ fun SeriesOverview(
|
|||
position = it
|
||||
},
|
||||
onClick = {
|
||||
viewModel.release()
|
||||
val resumePosition =
|
||||
it.data.userData
|
||||
?.playbackPositionTicks
|
||||
|
|
@ -143,6 +144,7 @@ fun SeriesOverview(
|
|||
},
|
||||
playOnClick = { resume ->
|
||||
episodes.getOrNull(position.episodeRowIndex)?.let {
|
||||
viewModel.release()
|
||||
navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
it.id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue