From 319b46983fc0de92b9791054fd57ade4c5cd51d2 Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:52:13 -0500 Subject: [PATCH] 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. --- .../ui/components/CollectionFolderGrid.kt | 13 +++++++---- .../wholphin/ui/components/GenreCardGrid.kt | 1 + .../wholphin/ui/components/ItemGrid.kt | 1 + .../ui/components/RecommendedContent.kt | 4 ++++ .../wholphin/ui/detail/CardGrid.kt | 8 ++++++- .../damontecres/wholphin/ui/main/HomePage.kt | 22 ++++++++++++++++++- .../wholphin/ui/playback/PlaybackViewModel.kt | 8 +++++-- 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt index 22591db4..2ba65f13 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt @@ -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, 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 diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt index b3711d0a..33900e2f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt @@ -117,6 +117,7 @@ fun GenreCardGrid( ) }, onLongClickItem = { _, _ -> }, + onClickPlay = { _, _ -> }, letterPosition = { viewModel.positionOfLetter(it) }, gridFocusRequester = gridFocusRequester, showJumpButtons = false, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/ItemGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/ItemGrid.kt index cf27554d..1e274de6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/ItemGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/ItemGrid.kt @@ -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, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedContent.kt index f69c018a..30bb369c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedContent.kt @@ -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, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt index 633ba5bc..8ffba496 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt @@ -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, 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) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt index 1dc6b7b0..12bd8ff6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt @@ -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, 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) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index a60f6c00..38d1679d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -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