EARHORNMUSIC UPnP · Android TV · Phones · Android Auto · GNU/Linux
Release 0.5.0

CastIntegration.kt

Each line, with the release that last changed it.

0.5.01package dev.stave.cast
2
3import android.content.Context
4import dagger.hilt.android.qualifiers.ApplicationContext
5import dev.stave.domain.model.DidlItem
6import dev.stave.domain.renderer.RepeatMode
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9import kotlinx.coroutines.flow.asStateFlow
10import javax.inject.Inject
11import javax.inject.Singleton
12
13/**
14 * Libre flavor: Google Cast is compiled out. This class exists so common code
15 * can depend on [CastIntegration] unconditionally. All methods are no-ops;
16 * [isCasting] is always false.
17 */
18@Singleton
19class CastIntegration @Inject constructor(
20 @Suppress("UnusedPrivateProperty") @ApplicationContext private val context: Context,
21) {
22 val available: Boolean = false
23
24 private val _devices = MutableStateFlow<List<CastDevice>>(emptyList())
25 val devices: StateFlow<List<CastDevice>> = _devices.asStateFlow()
26
27 private val _session = MutableStateFlow<CastSession?>(null)
28 val session: StateFlow<CastSession?> = _session.asStateFlow()
29
30 val isCasting: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
31
32 // Match play-flavor surface so common code (LocalRenderer) can reference
33 // these flows unconditionally without a flavor check. All three are
34 // permanently zero/false in libre since no session can ever start.
35 val castIsPlaying: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
36 val castPositionMs: StateFlow<Long> = MutableStateFlow(0L).asStateFlow()
37 val castDurationMs: StateFlow<Long> = MutableStateFlow(0L).asStateFlow()
38 val castCurrentItemIndex: StateFlow<Int> = MutableStateFlow(-1).asStateFlow()
39 val userInitiatedDisconnect: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
40 fun consumeDisconnectIntent(): Boolean = false
41
42 // Diagnostic mirror of the play-flavor surface so shared code (CastPickerSheet)
43 // can render the same diagnostic UI in both flavors. In libre, the diagnostic
44 // always reports "compiled out" — which at least makes it clear to the user
45 // why the picker is empty.
46 val diagnostic: StateFlow<DiagnosticState> = MutableStateFlow(
47 DiagnosticState(
48 castContextOk = false,
49 mediaRouterOk = false,
50 playServices = "libre build — Cast compiled out",
51 rawRouteCount = 0,
52 filteredDeviceCount = 0,
53 tick = 0,
54 rawRouteNames = emptyList(),
55 )
56 ).asStateFlow()
57
58 fun loadTrack(track: DidlItem.Track, autoplay: Boolean = true) = Unit
59 fun loadQueue(
60 tracks: List<DidlItem.Track>,
61 startIndex: Int = 0,
62 autoplay: Boolean = true,
63 repeat: RepeatMode = RepeatMode.OFF,
64 shuffle: Boolean = false,
65 ) = Unit
66 fun setRepeatAndShuffle(repeat: RepeatMode, shuffle: Boolean) = Unit
67 fun queueAppend(tracks: List<DidlItem.Track>, fullQueue: List<DidlItem.Track> = tracks) = Unit
68 fun queueInsertAfterCurrent(tracks: List<DidlItem.Track>) = Unit
69 fun queueRemoveAt(index: Int) = Unit
70 fun queueMove(from: Int, to: Int) = Unit
71 fun consumeAttachWasResume(): Boolean = false
72 fun currentContentId(): String? = null
73 fun detachWithoutStopping() = Unit
74 fun play() = Unit
75 fun pause() = Unit
76 fun stop() = Unit
77 fun seekTo(positionMs: Long) = Unit
78 fun queueNext() = Unit
79 fun queuePrev() = Unit
80 fun queueJumpTo(index: Int) = Unit
81 // Maintainer, 2026-10-29: mirror of the play flavour's wait-then-jump (D4); no-op here.
82 suspend fun queueJumpToWhenReady(index: Int) = Unit
83 fun setVolume(volume: Double) = Unit
84 fun getDeviceVolume(): Double = 1.0
85 fun setDeviceVolume(volume: Double) = Unit
86
87 fun startDiscovery() = Unit
88 fun stopDiscovery() = Unit
89 fun connect(deviceId: String) = Unit
90 fun disconnect() = Unit
91 fun requestStatusRefresh() = Unit
92}
93
94data class CastDevice(
95 val id: String,
96 val friendlyName: String,
97 val isGroup: Boolean,
98)
99
100data class CastSession(
101 val deviceId: String,
102 val deviceName: String,
103)
104
105data class DiagnosticState(
106 val castContextOk: Boolean = false,
107 val castInitError: String? = null,
108 val mediaRouterOk: Boolean = false,
109 val playServices: String = "unknown",
110 val rawRouteCount: Int = 0,
111 val filteredDeviceCount: Int = 0,
112 val tick: Int = 0,
113 val rawRouteNames: List<String> = emptyList(),
114)