mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
The cluster screen goes back to the car when there's no route
Gearslip took an Audi's virtual cockpit and showed black there with nothing to navigate, instead of the car's own map (#56). It now asks for the cluster when a route starts and hands it back once there has been none for 10 seconds.
This commit is contained in:
1 parent
a186b0c7d0
commit
3e2c087483
1 file changed
+51
-8
@@ -15,8 +15,12 @@ import app.seb3thehacker.gearslip.car.ClusterScreen
|
|||||||
* -> VideoFocusRequest <- VideoFocusNotification { projected }
|
* -> VideoFocusRequest <- VideoFocusNotification { projected }
|
||||||
* -> Start, CodecConfig, Data... <- Ack
|
* -> Start, CodecConfig, Data... <- Ack
|
||||||
*
|
*
|
||||||
* No touch: a cluster has none. Experimental, behind [car.CarSettings.clusterVideoEnabled], and
|
* No touch: a cluster has none. Experimental, behind [car.CarSettings.clusterVideoEnabled].
|
||||||
* untested in a car: none in the notes has a cluster sink.
|
*
|
||||||
|
* Gearslip holds the cluster only while there's a route, as the card has nothing else to show.
|
||||||
|
* Otherwise the car keeps its own picture there: an Audi's virtual cockpit went black instead of
|
||||||
|
* showing its map (issue #56). [watchRoute] asks for the screen when a route starts and hands it
|
||||||
|
* back once there has been none for [NO_ROUTE_MS].
|
||||||
*/
|
*/
|
||||||
internal class ClusterVideo(
|
internal class ClusterVideo(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -30,6 +34,13 @@ internal class ClusterVideo(
|
|||||||
|
|
||||||
private var configIndex = 0
|
private var configIndex = 0
|
||||||
@Volatile private var started = false
|
@Volatile private var started = false
|
||||||
|
/** Configured and ready for focus requests. */
|
||||||
|
@Volatile private var ready = false
|
||||||
|
/** The car shows Gearslip's picture now; frames aren't sent while it shows its own. */
|
||||||
|
@Volatile private var projected = false
|
||||||
|
/** Gearslip has asked for the screen and not handed it back since. */
|
||||||
|
private var wanted = false
|
||||||
|
private var noRouteSince = 0L
|
||||||
@Volatile private var running = true
|
@Volatile private var running = true
|
||||||
@Volatile private var source: VideoSource? = null
|
@Volatile private var source: VideoSource? = null
|
||||||
private var projector: ScreenProjector? = null
|
private var projector: ScreenProjector? = null
|
||||||
@@ -57,16 +68,19 @@ internal class ClusterVideo(
|
|||||||
// The cluster gets the smallest picture it offers: a turn card needs no more.
|
// The cluster gets the smallest picture it offers: a turn card needs no more.
|
||||||
configIndex = offered.minByOrNull { service.configs.getOrNull(it)?.pixelCount() ?: Int.MAX_VALUE } ?: 0
|
configIndex = offered.minByOrNull { service.configs.getOrNull(it)?.pixelCount() ?: Int.MAX_VALUE } ?: 0
|
||||||
log.i("<- Config status=${Wire.varint(fields, 1)} max_unacked=$maxUnacked offered=$offered - using $configIndex")
|
log.i("<- Config status=${Wire.varint(fields, 1)} max_unacked=$maxUnacked offered=$offered - using $configIndex")
|
||||||
val focus = Protobuf.varintField(2, GearslipRunner.VIDEO_FOCUS_PROJECTED.toLong()) + Protobuf.varintField(3, 1L)
|
ready = true
|
||||||
send(GearslipRunner.MSG_VIDEO_FOCUS_REQUEST, focus)
|
main.post(watchRoute)
|
||||||
log.i("-> VideoFocusRequest(mode=PROJECTED)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GearslipRunner.MSG_VIDEO_FOCUS_NOTIFICATION -> {
|
GearslipRunner.MSG_VIDEO_FOCUS_NOTIFICATION -> {
|
||||||
val mode = Wire.varint(Wire.fields(body), 1)?.toInt() ?: GearslipRunner.VIDEO_FOCUS_PROJECTED
|
val mode = Wire.varint(Wire.fields(body), 1)?.toInt() ?: GearslipRunner.VIDEO_FOCUS_PROJECTED
|
||||||
log.i("<- VideoFocusNotification: mode=$mode")
|
log.i("<- VideoFocusNotification: mode=$mode")
|
||||||
val projected = mode == GearslipRunner.VIDEO_FOCUS_PROJECTED || mode == GearslipRunner.VIDEO_FOCUS_PROJECTED_NO_INPUT
|
val now = mode == GearslipRunner.VIDEO_FOCUS_PROJECTED || mode == GearslipRunner.VIDEO_FOCUS_PROJECTED_NO_INPUT
|
||||||
if (projected && !started) startStream()
|
val back = now && !projected
|
||||||
|
projected = now
|
||||||
|
if (now && !started) startStream()
|
||||||
|
// Back from the car's own picture: a fresh key frame, so it doesn't start on a partial one.
|
||||||
|
else if (back) source?.requestSyncFrame()
|
||||||
}
|
}
|
||||||
|
|
||||||
GearslipRunner.MSG_MEDIA_ACK -> {
|
GearslipRunner.MSG_MEDIA_ACK -> {
|
||||||
@@ -82,6 +96,31 @@ internal class ClusterVideo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Once a second: the cluster follows whether there's a route. */
|
||||||
|
private val watchRoute = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
if (!running || !ready) return
|
||||||
|
val route = app.seb3thehacker.gearslip.car.NavTurn.current() != null
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (route) noRouteSince = 0L else if (noRouteSince == 0L) noRouteSince = now
|
||||||
|
when {
|
||||||
|
route && !wanted -> requestFocus(GearslipRunner.VIDEO_FOCUS_PROJECTED, "a route started")
|
||||||
|
!route && wanted && now - noRouteSince >= NO_ROUTE_MS -> requestFocus(GearslipRunner.VIDEO_FOCUS_NATIVE, "no route")
|
||||||
|
}
|
||||||
|
main.postDelayed(this, WATCH_MS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestFocus(mode: Int, why: String) {
|
||||||
|
wanted = mode == GearslipRunner.VIDEO_FOCUS_PROJECTED
|
||||||
|
// VideoFocusRequestNotification { disp_channel_id = 1; mode = 2; reason = 3 }, as the main
|
||||||
|
// screen asks for its own picture back.
|
||||||
|
val reason = if (wanted) 1L else GearslipRunner.VIDEO_FOCUS_REASON_LAUNCH_NATIVE.toLong()
|
||||||
|
val body = Protobuf.varintField(1, channelId.toLong()) + Protobuf.varintField(2, mode.toLong()) + Protobuf.varintField(3, reason)
|
||||||
|
runCatching { send(GearslipRunner.MSG_VIDEO_FOCUS_REQUEST, body) }
|
||||||
|
log.i("-> VideoFocusRequest(mode=${if (wanted) "PROJECTED" else "NATIVE"}): $why")
|
||||||
|
}
|
||||||
|
|
||||||
private fun startStream() {
|
private fun startStream() {
|
||||||
val config = service.configs.getOrNull(configIndex) ?: service.configs.firstOrNull() ?: return
|
val config = service.configs.getOrNull(configIndex) ?: service.configs.firstOrNull() ?: return
|
||||||
val (width, height) = config.pixelSize() ?: run {
|
val (width, height) = config.pixelSize() ?: run {
|
||||||
@@ -138,7 +177,7 @@ internal class ClusterVideo(
|
|||||||
ackLock.wait(GearslipRunner.ACK_STALL_MS - since + 1)
|
ackLock.wait(GearslipRunner.ACK_STALL_MS - since + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!running) return
|
if (!running || !projected) return
|
||||||
val timestamp = ByteArray(8)
|
val timestamp = ByteArray(8)
|
||||||
for (i in 0 until 8) timestamp[i] = ((presentationTimeUs shr ((7 - i) * 8)) and 0xFF).toByte()
|
for (i in 0 until 8) timestamp[i] = ((presentationTimeUs shr ((7 - i) * 8)) and 0xFF).toByte()
|
||||||
synchronized(ackLock) { inFlight++ }
|
synchronized(ackLock) { inFlight++ }
|
||||||
@@ -148,6 +187,7 @@ internal class ClusterVideo(
|
|||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
running = false
|
running = false
|
||||||
|
main.removeCallbacks(watchRoute)
|
||||||
synchronized(ackLock) { ackLock.notifyAll() }
|
synchronized(ackLock) { ackLock.notifyAll() }
|
||||||
main.post {
|
main.post {
|
||||||
projector?.stop()
|
projector?.stop()
|
||||||
@@ -160,5 +200,8 @@ internal class ClusterVideo(
|
|||||||
private companion object {
|
private companion object {
|
||||||
// A turn card changes a few times a minute; the encoder repeats the last frame between.
|
// A turn card changes a few times a minute; the encoder repeats the last frame between.
|
||||||
const val FRAME_RATE = 15
|
const val FRAME_RATE = 15
|
||||||
|
const val WATCH_MS = 1_000L
|
||||||
|
/** A gap in the turns this long is the route over, not a reroute or a moment between steps. */
|
||||||
|
const val NO_ROUTE_MS = 10_000L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user