mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Support playing local movie trailers (#36)
Adds support for playing local movie trailers. External trailers, such as ones on YouTube, are not yet supported. Also fixes a couple issues with request handlers
This commit is contained in:
parent
bb19ba316b
commit
63baf7f904
6 changed files with 61 additions and 2 deletions
|
|
@ -35,7 +35,7 @@ fun ChapterRow(
|
||||||
LazyRow(
|
LazyRow(
|
||||||
state = rememberLazyListState(),
|
state = rememberLazyListState(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,15 @@ import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
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.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
|
|
@ -42,8 +45,11 @@ import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||||
import com.github.damontecres.wholphin.data.model.Person
|
import com.github.damontecres.wholphin.data.model.Person
|
||||||
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
|
||||||
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
import com.github.damontecres.wholphin.ui.cards.ChapterRow
|
import com.github.damontecres.wholphin.ui.cards.ChapterRow
|
||||||
|
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||||
|
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
|
|
@ -70,6 +76,7 @@ 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
|
||||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||||
|
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
|
|
@ -89,6 +96,7 @@ class MovieViewModel
|
||||||
val people = MutableLiveData<List<Person>>(listOf())
|
val people = MutableLiveData<List<Person>>(listOf())
|
||||||
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
||||||
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
|
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
|
||||||
|
val trailers = MutableLiveData<List<BaseItem>>(listOf())
|
||||||
|
|
||||||
override fun init(
|
override fun init(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
|
|
@ -103,6 +111,16 @@ class MovieViewModel
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
chosenStreams.value = result
|
chosenStreams.value = result
|
||||||
}
|
}
|
||||||
|
val localTrailerCount = item.data.localTrailerCount ?: 0
|
||||||
|
if (localTrailerCount > 0) {
|
||||||
|
val trailers =
|
||||||
|
api.userLibraryApi.getLocalTrailers(itemId).content.map {
|
||||||
|
BaseItem.from(it, api)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
this@MovieViewModel.trailers.value = trailers
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
people.value =
|
people.value =
|
||||||
|
|
@ -180,6 +198,7 @@ fun MovieDetails(
|
||||||
val item by viewModel.item.observeAsState()
|
val item by viewModel.item.observeAsState()
|
||||||
val people by viewModel.people.observeAsState(listOf())
|
val people by viewModel.people.observeAsState(listOf())
|
||||||
val chapters by viewModel.chapters.observeAsState(listOf())
|
val chapters by viewModel.chapters.observeAsState(listOf())
|
||||||
|
val trailers by viewModel.trailers.observeAsState(listOf())
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||||
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
||||||
|
|
||||||
|
|
@ -200,6 +219,7 @@ fun MovieDetails(
|
||||||
chosenStreams = chosenStreams,
|
chosenStreams = chosenStreams,
|
||||||
people = people,
|
people = people,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
|
trailers = trailers,
|
||||||
playOnClick = {
|
playOnClick = {
|
||||||
viewModel.navigationManager.navigateTo(
|
viewModel.navigationManager.navigateTo(
|
||||||
Destination.Playback(
|
Destination.Playback(
|
||||||
|
|
@ -270,6 +290,15 @@ fun MovieDetails(
|
||||||
watchOnClick = {
|
watchOnClick = {
|
||||||
viewModel.setWatched((movie.data.userData?.played ?: false).not())
|
viewModel.setWatched((movie.data.userData?.played ?: false).not())
|
||||||
},
|
},
|
||||||
|
trailerOnClick = { trailer ->
|
||||||
|
viewModel.navigationManager.navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
itemId = trailer.id,
|
||||||
|
item = trailer,
|
||||||
|
positionMs = 0L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +339,9 @@ fun MovieDetailsContent(
|
||||||
chosenStreams: ChosenStreams?,
|
chosenStreams: ChosenStreams?,
|
||||||
people: List<Person>,
|
people: List<Person>,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
|
trailers: List<BaseItem>,
|
||||||
playOnClick: (Duration) -> Unit,
|
playOnClick: (Duration) -> Unit,
|
||||||
|
trailerOnClick: (BaseItem) -> Unit,
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
watchOnClick: () -> Unit,
|
watchOnClick: () -> Unit,
|
||||||
moreOnClick: () -> Unit,
|
moreOnClick: () -> Unit,
|
||||||
|
|
@ -359,7 +390,7 @@ fun MovieDetailsContent(
|
||||||
}
|
}
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
contentPadding = PaddingValues(32.dp),
|
contentPadding = PaddingValues(horizontal = 32.dp, vertical = 8.dp),
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
|
|
@ -407,6 +438,27 @@ fun MovieDetailsContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (trailers.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
ItemRow(
|
||||||
|
title = stringResource(R.string.trailers),
|
||||||
|
items = trailers,
|
||||||
|
onClickItem = trailerOnClick,
|
||||||
|
onLongClickItem = {},
|
||||||
|
cardContent = @Composable { index, item, mod, onClick, onLongClick ->
|
||||||
|
SeasonCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageHeight = Cards.height2x3,
|
||||||
|
imageWidth = Dp.Unspecified,
|
||||||
|
showImageOverlay = false,
|
||||||
|
modifier = mod,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (chapters.isNotEmpty()) {
|
if (chapters.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
ChapterRow(
|
ChapterRow(
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,7 @@ class PlaybackViewModel
|
||||||
it.release()
|
it.release()
|
||||||
player.removeListener(it)
|
player.removeListener(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val activityListener =
|
val activityListener =
|
||||||
TrackActivityPlaybackListener(
|
TrackActivityPlaybackListener(
|
||||||
api = api,
|
api = api,
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,7 @@ val GetResumeItemsRequestHandler =
|
||||||
request.copy(
|
request.copy(
|
||||||
startIndex = startIndex,
|
startIndex = startIndex,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
|
enableTotalRecordCount = enableTotalRecordCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun execute(
|
override suspend fun execute(
|
||||||
|
|
@ -249,6 +250,7 @@ val GetNextUpRequestHandler =
|
||||||
request.copy(
|
request.copy(
|
||||||
startIndex = startIndex,
|
startIndex = startIndex,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
|
enableTotalRecordCount = enableTotalRecordCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun execute(
|
override suspend fun execute(
|
||||||
|
|
@ -268,6 +270,7 @@ val GetSuggestionsRequestHandler =
|
||||||
request.copy(
|
request.copy(
|
||||||
startIndex = startIndex,
|
startIndex = startIndex,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
|
enableTotalRecordCount = enableTotalRecordCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun execute(
|
override suspend fun execute(
|
||||||
|
|
@ -306,6 +309,7 @@ val GetGenresRequestHandler =
|
||||||
request.copy(
|
request.copy(
|
||||||
startIndex = startIndex,
|
startIndex = startIndex,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
|
enableTotalRecordCount = enableTotalRecordCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun execute(
|
override suspend fun execute(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ val supportItemKinds =
|
||||||
BaseItemKind.SEASON,
|
BaseItemKind.SEASON,
|
||||||
BaseItemKind.COLLECTION_FOLDER,
|
BaseItemKind.COLLECTION_FOLDER,
|
||||||
BaseItemKind.USER_VIEW,
|
BaseItemKind.USER_VIEW,
|
||||||
|
BaseItemKind.TRAILER,
|
||||||
)
|
)
|
||||||
|
|
||||||
val supportedCollectionTypes =
|
val supportedCollectionTypes =
|
||||||
|
|
|
||||||
|
|
@ -82,5 +82,6 @@
|
||||||
<string name="playback_overrides">Playback overrides</string>
|
<string name="playback_overrides">Playback overrides</string>
|
||||||
<string name="direct_play_ass">Direct play ASS subtitles</string>
|
<string name="direct_play_ass">Direct play ASS subtitles</string>
|
||||||
<string name="direct_play_pgs">Direct play PGS subtitles</string>
|
<string name="direct_play_pgs">Direct play PGS subtitles</string>
|
||||||
|
<string name="trailers">Trailers</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue