mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Few UI fixes especially backgrounding the app (#39)
Ensures playback (both theme song & video) is stopped when the app is backgrounded, like hitting the home button. Fixes a UI glitch when marking a series as played.
This commit is contained in:
parent
63baf7f904
commit
c3cc284238
13 changed files with 143 additions and 52 deletions
|
|
@ -37,7 +37,6 @@ import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.util.AppUpgradeHandler
|
import com.github.damontecres.wholphin.util.AppUpgradeHandler
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
|
||||||
import com.github.damontecres.wholphin.util.UpdateChecker
|
import com.github.damontecres.wholphin.util.UpdateChecker
|
||||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
@ -69,9 +68,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appUpgradeHandler: AppUpgradeHandler
|
lateinit var appUpgradeHandler: AppUpgradeHandler
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var themeSongPlayer: ThemeSongPlayer
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -168,9 +164,4 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
themeSongPlayer.stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import coil3.request.ErrorResult
|
import coil3.request.ErrorResult
|
||||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
|
|
@ -360,3 +361,8 @@ fun CoroutineScope.launchIO(
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun UUID.toServerString() = this.toString().replace("-", "")
|
fun UUID.toServerString() = this.toString().replace("-", "")
|
||||||
|
|
||||||
|
suspend fun <T> MutableLiveData<T>.setValueOnMain(value: T) =
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
this@setValueOnMain.value = value
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
|
@ -82,6 +83,15 @@ fun SeriesDetails(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: SeriesViewModel = hiltViewModel(),
|
viewModel: SeriesViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
LifecycleStartEffect(destination.itemId) {
|
||||||
|
viewModel.maybePlayThemeSong(
|
||||||
|
destination.itemId,
|
||||||
|
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
||||||
|
)
|
||||||
|
onStopOrDispose {
|
||||||
|
viewModel.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(preferences, destination.itemId, destination.item, null, null)
|
viewModel.init(preferences, destination.itemId, destination.item, null, null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
|
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
|
|
@ -101,7 +102,6 @@ class SeriesViewModel
|
||||||
people.map { Person.fromDto(it, api) }
|
people.map { Person.fromDto(it, api) }
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
}
|
}
|
||||||
maybePlayThemeSong(prefs.appPreferences.interfacePreferences.playThemeSongs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +109,10 @@ class SeriesViewModel
|
||||||
* If the series has a theme song & app settings allow, play it
|
* If the series has a theme song & app settings allow, play it
|
||||||
*/
|
*/
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private fun maybePlayThemeSong(playThemeSongs: ThemeSongVolume) {
|
fun maybePlayThemeSong(
|
||||||
|
seriesId: UUID,
|
||||||
|
playThemeSongs: ThemeSongVolume,
|
||||||
|
) {
|
||||||
viewModelScope.launch(ExceptionHandler()) {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
|
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
|
||||||
themeSongs.items.firstOrNull()?.let { theme ->
|
themeSongs.items.firstOrNull()?.let { theme ->
|
||||||
|
|
@ -141,10 +144,10 @@ class SeriesViewModel
|
||||||
themeSongPlayer.stop()
|
themeSongPlayer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getSeasons(item: BaseItem): ItemListAndMapping {
|
private suspend fun getSeasons(series: BaseItem): ItemListAndMapping {
|
||||||
val request =
|
val request =
|
||||||
GetItemsRequest(
|
GetItemsRequest(
|
||||||
parentId = item.id,
|
parentId = series.id,
|
||||||
recursive = false,
|
recursive = false,
|
||||||
includeItemTypes = listOf(BaseItemKind.SEASON),
|
includeItemTypes = listOf(BaseItemKind.SEASON),
|
||||||
sortBy = listOf(ItemSortBy.INDEX_NUMBER),
|
sortBy = listOf(ItemSortBy.INDEX_NUMBER),
|
||||||
|
|
@ -164,7 +167,7 @@ class SeriesViewModel
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
)
|
)
|
||||||
pager.init()
|
pager.init()
|
||||||
Timber.Forest.v("Loaded ${pager.size} seasons for series ${item.id}")
|
Timber.Forest.v("Loaded ${pager.size} seasons for series ${series.id}")
|
||||||
val pairs =
|
val pairs =
|
||||||
pager.mapIndexed { index, _ ->
|
pager.mapIndexed { index, _ ->
|
||||||
val season = pager.getBlocking(index)
|
val season = pager.getBlocking(index)
|
||||||
|
|
@ -235,7 +238,9 @@ class SeriesViewModel
|
||||||
} else {
|
} else {
|
||||||
api.playStateApi.markUnplayedItem(seriesId)
|
api.playStateApi.markUnplayedItem(seriesId)
|
||||||
}
|
}
|
||||||
fetchItem(seriesId, null)
|
val series = fetchItem(seriesId, null)
|
||||||
|
val seasons = getSeasons(series)
|
||||||
|
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshEpisode(
|
fun refreshEpisode(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.chooseSource
|
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
|
|
@ -59,6 +60,15 @@ fun SeriesOverview(
|
||||||
viewModel: SeriesViewModel = hiltViewModel(),
|
viewModel: SeriesViewModel = hiltViewModel(),
|
||||||
initialSeasonEpisode: SeasonEpisode? = null,
|
initialSeasonEpisode: SeasonEpisode? = null,
|
||||||
) {
|
) {
|
||||||
|
LifecycleStartEffect(destination.itemId) {
|
||||||
|
viewModel.maybePlayThemeSong(
|
||||||
|
destination.itemId,
|
||||||
|
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
||||||
|
)
|
||||||
|
onStopOrDispose {
|
||||||
|
viewModel.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
val firstItemFocusRequester = remember { FocusRequester() }
|
val firstItemFocusRequester = remember { FocusRequester() }
|
||||||
val episodeRowFocusRequester = remember { FocusRequester() }
|
val episodeRowFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,10 @@ fun SeriesOverviewContent(
|
||||||
chosenStreams = chosenStreams,
|
chosenStreams = chosenStreams,
|
||||||
playOnClick = playOnClick,
|
playOnClick = playOnClick,
|
||||||
moreOnClick = moreOnClick,
|
moreOnClick = moreOnClick,
|
||||||
watchOnClick = watchOnClick,
|
watchOnClick = {
|
||||||
|
watchOnClick.invoke()
|
||||||
|
episodeRowFocusRequester.tryRequestFocus()
|
||||||
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -43,6 +44,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.Cards
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
import com.github.damontecres.wholphin.ui.cards.BannerCard
|
import com.github.damontecres.wholphin.ui.cards.BannerCard
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||||
|
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||||
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
|
|
@ -71,11 +73,14 @@ fun HomePage(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: HomeViewModel = hiltViewModel(),
|
viewModel: HomeViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
var firstLoad by rememberSaveable { mutableStateOf(true) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(preferences)
|
viewModel.init(preferences).join()
|
||||||
|
firstLoad = false
|
||||||
}
|
}
|
||||||
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
||||||
when (val state = loading) {
|
val homeRows by viewModel.homeRows.observeAsState(listOf())
|
||||||
|
when (val state = if (firstLoad) loading else LoadingState.Success) {
|
||||||
is LoadingState.Error -> ErrorMessage(state)
|
is LoadingState.Error -> ErrorMessage(state)
|
||||||
|
|
||||||
LoadingState.Loading,
|
LoadingState.Loading,
|
||||||
|
|
@ -83,13 +88,13 @@ fun HomePage(
|
||||||
-> LoadingPage()
|
-> LoadingPage()
|
||||||
|
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
val homeRows by viewModel.homeRows.observeAsState(listOf())
|
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
homeRows,
|
homeRows,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
viewModel.navigationManager.navigateTo(it.destination())
|
viewModel.navigationManager.navigateTo(it.destination())
|
||||||
},
|
},
|
||||||
modifier,
|
loadingState = loading,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +106,7 @@ fun HomePageContent(
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||||
|
loadingState: LoadingState? = null,
|
||||||
) {
|
) {
|
||||||
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
||||||
mutableStateOf(RowColumn(0, 0))
|
mutableStateOf(RowColumn(0, 0))
|
||||||
|
|
@ -219,6 +225,22 @@ fun HomePageContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
.align(Alignment.BottomEnd),
|
||||||
|
) {
|
||||||
|
when (loadingState) {
|
||||||
|
LoadingState.Pending,
|
||||||
|
LoadingState.Loading,
|
||||||
|
->
|
||||||
|
CircularProgress(Modifier.fillMaxSize())
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.github.damontecres.wholphin.util.supportItemKinds
|
||||||
import com.github.damontecres.wholphin.util.supportedCollectionTypes
|
import com.github.damontecres.wholphin.util.supportedCollectionTypes
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
|
@ -36,13 +37,14 @@ class HomeViewModel
|
||||||
val api: ApiClient,
|
val api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val loadingState = MutableLiveData<LoadingState>(LoadingState.Loading)
|
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
val homeRows = MutableLiveData<List<HomeRow>>()
|
val homeRows = MutableLiveData<List<HomeRow>>()
|
||||||
|
|
||||||
fun init(preferences: UserPreferences) {
|
fun init(preferences: UserPreferences): Job {
|
||||||
|
loadingState.value = LoadingState.Loading
|
||||||
val prefs = preferences.appPreferences.homePagePreferences
|
val prefs = preferences.appPreferences.homePagePreferences
|
||||||
val limit = prefs.maxItemsPerRow
|
val limit = prefs.maxItemsPerRow
|
||||||
viewModelScope.launch(
|
return viewModelScope.launch(
|
||||||
Dispatchers.IO +
|
Dispatchers.IO +
|
||||||
LoadingExceptionHandler(
|
LoadingExceptionHandler(
|
||||||
loadingState,
|
loadingState,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.text.Cue
|
import androidx.media3.common.text.Cue
|
||||||
import androidx.media3.common.text.CueGroup
|
import androidx.media3.common.text.CueGroup
|
||||||
|
|
@ -85,6 +86,11 @@ fun PlaybackPage(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: PlaybackViewModel = hiltViewModel(),
|
viewModel: PlaybackViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
LifecycleStartEffect(destination.itemId) {
|
||||||
|
onStopOrDispose {
|
||||||
|
viewModel.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
LaunchedEffect(destination.itemId) {
|
LaunchedEffect(destination.itemId) {
|
||||||
viewModel.init(destination, deviceProfile, preferences)
|
viewModel.init(destination, deviceProfile, preferences)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import androidx.media3.common.Player
|
||||||
import androidx.media3.common.TrackSelectionOverride
|
import androidx.media3.common.TrackSelectionOverride
|
||||||
import androidx.media3.common.Tracks
|
import androidx.media3.common.Tracks
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
||||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||||
import com.github.damontecres.wholphin.data.ServerRepository
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
|
|
@ -38,6 +37,7 @@ import com.github.damontecres.wholphin.util.EqualityMutableLiveData
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.PlayerFactory
|
||||||
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
|
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
|
||||||
import com.github.damontecres.wholphin.util.TrackSupport
|
import com.github.damontecres.wholphin.util.TrackSupport
|
||||||
import com.github.damontecres.wholphin.util.checkForSupport
|
import com.github.damontecres.wholphin.util.checkForSupport
|
||||||
|
|
@ -97,6 +97,7 @@ class PlaybackViewModel
|
||||||
constructor(
|
constructor(
|
||||||
@param:ApplicationContext val context: Context,
|
@param:ApplicationContext val context: Context,
|
||||||
val api: ApiClient,
|
val api: ApiClient,
|
||||||
|
val playerFactory: PlayerFactory,
|
||||||
val playlistCreator: PlaylistCreator,
|
val playlistCreator: PlaylistCreator,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
val itemPlaybackDao: ItemPlaybackDao,
|
val itemPlaybackDao: ItemPlaybackDao,
|
||||||
|
|
@ -104,13 +105,13 @@ class PlaybackViewModel
|
||||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||||
) : ViewModel(),
|
) : ViewModel(),
|
||||||
Player.Listener {
|
Player.Listener {
|
||||||
val player: ExoPlayer =
|
val player by lazy {
|
||||||
ExoPlayer
|
playerFactory
|
||||||
.Builder(context)
|
.create(false)
|
||||||
.build()
|
|
||||||
.apply {
|
.apply {
|
||||||
playWhenReady = true
|
playWhenReady = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||||
|
|
||||||
|
|
@ -711,6 +712,11 @@ class PlaybackViewModel
|
||||||
loading.value = LoadingState.Error("Error during playback", error)
|
loading.value = LoadingState.Error("Error during playback", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun release() {
|
||||||
|
playerFactory.release()
|
||||||
|
navigationManager.goBack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CurrentPlayback(
|
data class CurrentPlayback(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.github.damontecres.wholphin.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
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 com.github.damontecres.wholphin.hilt.AuthOkHttpClient
|
||||||
|
import com.github.damontecres.wholphin.hilt.StandardOkHttpClient
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class PlayerFactory
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@param:ApplicationContext private val context: Context,
|
||||||
|
@param:StandardOkHttpClient private val standardOkHttpClient: OkHttpClient,
|
||||||
|
@param:AuthOkHttpClient private val authOkHttpClient: OkHttpClient,
|
||||||
|
) {
|
||||||
|
private var player: Player? = null
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
fun create(useAuth: Boolean): Player {
|
||||||
|
release()
|
||||||
|
val player =
|
||||||
|
ExoPlayer
|
||||||
|
.Builder(context)
|
||||||
|
.setMediaSourceFactory(
|
||||||
|
DefaultMediaSourceFactory(
|
||||||
|
OkHttpDataSource.Factory(if (useAuth) authOkHttpClient else standardOkHttpClient),
|
||||||
|
),
|
||||||
|
).build()
|
||||||
|
this.player = player
|
||||||
|
return player
|
||||||
|
}
|
||||||
|
|
||||||
|
fun release() {
|
||||||
|
player?.let {
|
||||||
|
it.stop()
|
||||||
|
it.release()
|
||||||
|
}
|
||||||
|
player = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,9 @@
|
||||||
package com.github.damontecres.wholphin.util
|
package com.github.damontecres.wholphin.util
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.Player
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
||||||
import com.github.damontecres.wholphin.hilt.AuthOkHttpClient
|
|
||||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
@ -22,11 +14,8 @@ import javax.inject.Singleton
|
||||||
class ThemeSongPlayer
|
class ThemeSongPlayer
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@param:ApplicationContext val context: Context,
|
val playerFactory: PlayerFactory,
|
||||||
@param:AuthOkHttpClient val okHttpClient: OkHttpClient,
|
|
||||||
) {
|
) {
|
||||||
private var player: Player? = null
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
fun play(
|
fun play(
|
||||||
volume: ThemeSongVolume,
|
volume: ThemeSongVolume,
|
||||||
|
|
@ -46,25 +35,17 @@ class ThemeSongPlayer
|
||||||
ThemeSongVolume.HIGHEST -> 75f
|
ThemeSongVolume.HIGHEST -> 75f
|
||||||
}
|
}
|
||||||
val player =
|
val player =
|
||||||
ExoPlayer
|
playerFactory
|
||||||
.Builder(context)
|
.create(true)
|
||||||
.setMediaSourceFactory(
|
|
||||||
DefaultMediaSourceFactory(
|
|
||||||
OkHttpDataSource.Factory(okHttpClient),
|
|
||||||
),
|
|
||||||
).build()
|
|
||||||
.apply {
|
.apply {
|
||||||
this.volume = volumeLevel
|
this.volume = volumeLevel
|
||||||
playWhenReady = true
|
playWhenReady = true
|
||||||
}
|
}
|
||||||
player.setMediaItem(MediaItem.fromUri(url))
|
player.setMediaItem(MediaItem.fromUri(url))
|
||||||
player.prepare()
|
player.prepare()
|
||||||
this.player = player
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
player?.stop()
|
playerFactory.release()
|
||||||
player?.release()
|
|
||||||
player = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ class TrackActivityPlaybackListener(
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
(if (position >= 0) position else player.currentPosition).milliseconds
|
(if (position >= 0) position else player.currentPosition).milliseconds
|
||||||
}
|
}
|
||||||
// Timber.v("saveActivity: itemId=$itemId, pos=$calcPosition")
|
Timber.v("saveActivity: itemId=${itemPlayback.itemId}, pos=$calcPosition")
|
||||||
api.playStateApi.reportPlaybackProgress(
|
api.playStateApi.reportPlaybackProgress(
|
||||||
PlaybackProgressInfo(
|
PlaybackProgressInfo(
|
||||||
itemId = itemPlayback.itemId,
|
itemId = itemPlayback.itemId,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue