mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 16:57:00 +01:00
feat(android): install on Android TV, and take a remote's keys in the player
The app is listed in the TV launcher with a banner, and neither a touch screen nor leanback is required. While the controls are down the player takes D-pad left and right as a ten-second seek, the middle button as bringing the controls up, and play/pause and space as they say.
This commit is contained in:
1 parent
d4e9628199
commit
f0402e74f9
5 files changed
+84
No files matched your search
@@ -7,6 +7,13 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
|
||||
<!--
|
||||
Android TV: listed in its launcher, and installable without a touch
|
||||
screen. Neither is required, so phones are offered the same build.
|
||||
-->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".ProtonStreamApplication"
|
||||
android:allowBackup="false"
|
||||
@@ -14,6 +21,7 @@
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:targetApi="33"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:banner="@drawable/tv_banner"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.ProtonStream">
|
||||
@@ -39,6 +47,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Proton Drive public links. autoVerify="false", said explicitly:
|
||||
|
||||
@@ -68,6 +68,14 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.semantics.onClick
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
@@ -444,12 +452,46 @@ fun PlayerScreen(
|
||||
}
|
||||
}
|
||||
val current by rememberUpdatedState(state)
|
||||
// Where a remote's keys land while the controls are down. Once they are
|
||||
// up, the D-pad moves between their buttons as it does anywhere else.
|
||||
val keys = remember { FocusRequester() }
|
||||
LaunchedEffect(showControls) { if (!showControls) runCatching { keys.requestFocus() } }
|
||||
if (minimized) return
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
// A TV remote, a keyboard: left and right seek as a double tap
|
||||
// does, the middle button brings the controls up, and play/pause
|
||||
// and space do what they say.
|
||||
.focusRequester(keys)
|
||||
.onKeyEvent { event ->
|
||||
if (event.type != KeyEventType.KeyDown) return@onKeyEvent false
|
||||
when (event.key) {
|
||||
Key.DirectionLeft, Key.DirectionRight -> {
|
||||
if (showControls) return@onKeyEvent false
|
||||
val seconds = if (event.key == Key.DirectionLeft) -DOUBLE_TAP_SECONDS else DOUBLE_TAP_SECONDS
|
||||
host.seek((current.position + seconds).coerceAtLeast(0.0))
|
||||
nudge = seconds to (nudge?.second ?: 0) + 1
|
||||
true
|
||||
}
|
||||
Key.DirectionCenter, Key.Enter, Key.NumPadEnter, Key.DirectionUp, Key.DirectionDown -> {
|
||||
if (showControls) return@onKeyEvent false
|
||||
showControls = true
|
||||
true
|
||||
}
|
||||
Key.MediaPlayPause, Key.Spacebar -> {
|
||||
host.setPaused(!current.paused)
|
||||
showControls = true
|
||||
true
|
||||
}
|
||||
Key.MediaPlay -> { host.setPaused(false); true }
|
||||
Key.MediaPause -> { host.setPaused(true); true }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
.focusable()
|
||||
// Double-tap a side to seek ten seconds, as every phone video
|
||||
// player has taught; the middle plays or pauses. A single tap
|
||||
// waits out the double-tap timeout, which is the price of both.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Reference in new issue
Block a user