Play item directly when clicking play button on remote (#424)

On the home page or any grid, if focused on a "playable" item (movie,
episode, etc), pressing the Play or Play-Pause button on the remote will
immediately start playback.

Dev note: Also guards against an edge case where an item passed into
playback doesn't have media sources due to more limited item fields in
the query.
This commit is contained in:
damontecres 2025-12-11 12:52:13 -05:00 committed by GitHub
parent c97553f82a
commit 319b46983f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 49 additions and 8 deletions

View file

@ -579,7 +579,10 @@ fun CollectionFolderGrid(
defaultViewOptions = defaultViewOptions,
onSaveViewOptions = { viewModel.saveViewOptions(it) },
playEnabled = playEnabled,
onClickPlay = { shuffle ->
onClickPlay = { _, item ->
viewModel.navigationManager.navigateTo(Destination.Playback(item))
},
onClickPlayAll = { shuffle ->
itemId.toUUIDOrNull()?.let {
viewModel.navigationManager.navigateTo(
Destination.PlaybackList(
@ -678,7 +681,8 @@ fun CollectionFolderGridContent(
letterPosition: suspend (Char) -> Int,
sortOptions: List<ItemSortBy>,
playEnabled: Boolean,
onClickPlay: (shuffle: Boolean) -> Unit,
onClickPlayAll: (shuffle: Boolean) -> Unit,
onClickPlay: (Int, BaseItem) -> Unit,
modifier: Modifier = Modifier,
showTitle: Boolean = true,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
@ -786,12 +790,12 @@ fun CollectionFolderGridContent(
title = R.string.play,
resume = Duration.ZERO,
icon = Icons.Default.PlayArrow,
onClick = { onClickPlay.invoke(false) },
onClick = { onClickPlayAll.invoke(false) },
)
ExpandableFaButton(
title = R.string.shuffle,
iconStringRes = R.string.fa_shuffle,
onClick = { onClickPlay.invoke(true) },
onClick = { onClickPlayAll.invoke(true) },
)
}
}
@ -813,6 +817,7 @@ fun CollectionFolderGridContent(
pager = pager,
onClickItem = onClickItem,
onLongClickItem = onLongClickItem,
onClickPlay = onClickPlay,
letterPosition = letterPosition,
gridFocusRequester = gridFocusRequester,
showJumpButtons = false, // TODO add preference

View file

@ -117,6 +117,7 @@ fun GenreCardGrid(
)
},
onLongClickItem = { _, _ -> },
onClickPlay = { _, _ -> },
letterPosition = { viewModel.positionOfLetter(it) },
gridFocusRequester = gridFocusRequester,
showJumpButtons = false,

View file

@ -124,6 +124,7 @@ fun ItemGrid(
viewModel.navigateTo(Destination.Playback(item.id, 0))
},
onLongClickItem = { index: Int, item: BaseItem -> },
onClickPlay = { _, item -> viewModel.navigateTo(Destination.Playback(item)) },
letterPosition = { c: Char -> 0 },
gridFocusRequester = focusRequester,
showJumpButtons = false,

View file

@ -31,6 +31,7 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState
@ -146,6 +147,9 @@ fun RecommendedContent(
onLongClickItem = { position, item ->
moreDialog.makePresent(RowColumnItem(position, item))
},
onClickPlay = { _, item ->
viewModel.navigationManager.navigateTo(Destination.Playback(item))
},
onFocusPosition = onFocusPosition,
showClock = preferences.appPreferences.interfacePreferences.showClock,
modifier = modifier,

View file

@ -63,6 +63,7 @@ import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.playback.isBackwardButton
import com.github.damontecres.wholphin.ui.playback.isForwardButton
import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp
import com.github.damontecres.wholphin.ui.playback.playable
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.Dispatchers
@ -78,6 +79,7 @@ fun CardGrid(
pager: List<BaseItem?>,
onClickItem: (Int, BaseItem) -> Unit,
onLongClickItem: (Int, BaseItem) -> Unit,
onClickPlay: (Int, BaseItem) -> Unit,
letterPosition: suspend (Char) -> Int,
gridFocusRequester: FocusRequester,
showJumpButtons: Boolean,
@ -213,7 +215,11 @@ fun CardGrid(
jumpToTop()
return@onKeyEvent true
} else if (isPlayKeyUp(it)) {
// TODO play the focused item
val item = pager.getOrNull(focusedIndex)
if (item?.type?.playable == true) {
Timber.v("Clicked play on ${item.id}")
onClickPlay.invoke(focusedIndex, item)
}
return@onKeyEvent true
} else if (useJumpRemoteButtons && isForwardButton(it)) {
jump(jump1)

View file

@ -34,6 +34,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@ -67,12 +68,16 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp
import com.github.damontecres.wholphin.ui.playback.playable
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState
import kotlinx.coroutines.delay
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.MediaType
import timber.log.Timber
import java.util.UUID
@Composable
@ -157,6 +162,9 @@ fun HomePage(
items = dialogItems,
)
},
onClickPlay = { _, item ->
viewModel.navigationManager.navigateTo(Destination.Playback(item))
},
loadingState = refreshing,
showClock = preferences.appPreferences.interfacePreferences.showClock,
modifier = modifier,
@ -193,6 +201,7 @@ fun HomePageContent(
homeRows: List<HomeRowLoadingState>,
onClickItem: (RowColumn, BaseItem) -> Unit,
onLongClickItem: (RowColumn, BaseItem) -> Unit,
onClickPlay: (RowColumn, BaseItem) -> Unit,
showClock: Boolean,
modifier: Modifier = Modifier,
onFocusPosition: ((RowColumn) -> Unit)? = null,
@ -264,7 +273,18 @@ fun HomePageContent(
top = 0.dp,
bottom = Cards.height2x3,
),
modifier = Modifier.focusRestorer(),
modifier =
Modifier
.focusRestorer()
.onKeyEvent {
val item = focusedItem
if (isPlayKeyUp(it) && item?.type?.playable == true) {
Timber.v("Clicked play on ${item.id}")
onClickPlay.invoke(position, item)
return@onKeyEvent true
}
return@onKeyEvent false
},
) {
itemsIndexed(homeRows) { rowIndex, row ->
when (val r = row) {

View file

@ -225,9 +225,13 @@ class PlaybackViewModel
"Error preparing for playback for $itemId",
),
) {
val destItem = (destination as? Destination.Playback)?.item?.data
val queriedItem =
(destination as? Destination.Playback)?.item?.data
?: api.userLibraryApi.getItem(itemId).content
if (destItem?.mediaSources != null) {
destItem
} else {
api.userLibraryApi.getItem(itemId).content
}
val base =
if (queriedItem.type.playable) {
queriedItem