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

Renderer.kt

73 lines · 3.3 KB

Changed in: 0.5.0

1package dev.stave.domain.renderer
2
3import dev.stave.domain.model.DidlItem
4import kotlinx.coroutines.flow.StateFlow
5import kotlin.time.Duration
6
7/**
8 * The thing that actually plays audio. Implemented by
9 * [dev.stave.playback.LocalRenderer] (ExoPlayer; also mirrors to a Cast
10 * session in the play flavour) and
11 * [dev.stave.upnp.renderer.RemoteUpnpRenderer]; selected by
12 * [dev.stave.upnp.renderer.RendererCoordinator].
13 */
14interface Renderer {
15 val state: StateFlow<PlaybackState>
16
17 /**
18 * Replace the queue with [tracks], position at [startIndex], and start playback.
19 *
20 * @param autoplay When true (default), playback begins immediately — the normal
21 * "user clicked track / album" flow. When false, the queue is set and the
22 * transport prepares but stays Stopped, waiting for an explicit [play] call.
23 * Used by the UPnP MediaRenderer's setAVTransportURI handler: the AVTransport
24 * spec says SetURI loads media into Stopped and the control point follows up
25 * with Play(), and we have to honor that ordering or buggy control points
26 * double-toggle and end up in their own Paused state out of sync with ours.
27 */
28 suspend fun load(tracks: List<DidlItem.Track>, startIndex: Int = 0, autoplay: Boolean = true)
29 /** Append [tracks] to the end of the queue. If the queue is empty, they become the queue
30 * but are NOT auto-started — the user explicitly asked to queue, not to play. */
31 suspend fun enqueue(tracks: List<DidlItem.Track>)
32 /** Insert [tracks] immediately after the currently-playing track. If the queue is empty,
33 * the tracks become the queue AND playback begins. */
34 suspend fun playNext(tracks: List<DidlItem.Track>)
35 /** Jump directly to queue position [index] and start playing. Out-of-range is a no-op. */
36 suspend fun skipToQueueItem(index: Int)
37 /** Remove queue item at [index]. Removing the current item advances to the next; removing
38 * the last remaining item stops playback. */
39 suspend fun removeFromQueue(index: Int)
40 /** Reorder the queue by moving the item at [from] to position [to]. */
41 suspend fun moveInQueue(from: Int, to: Int)
42 /** Empty the queue and stop playback. */
43 suspend fun clearQueue()
44 suspend fun play()
45 suspend fun pause()
46 suspend fun stop()
47 suspend fun seekTo(position: Duration)
48 suspend fun next()
49 suspend fun previous()
50 suspend fun setVolume(volumePercent: Int)
51 /** Cycle repeat mode: OFF → ALL → ONE → OFF. */
52 suspend fun setRepeatMode(mode: RepeatMode)
53 /** Shuffle toggle. Turning on saves the current queue order; turning off restores it.
54 * Implementation is responsible for the save/restore — UI just flips the flag. */
55 suspend fun setShuffle(enabled: Boolean)
56}
57
58enum class RepeatMode { OFF, ALL, ONE }
59
60data class PlaybackState(
61 val current: DidlItem.Track? = null,
62 val queue: List<DidlItem.Track> = emptyList(),
63 val queueIndex: Int = -1,
64 val isPlaying: Boolean = false,
65 val isBuffering: Boolean = false,
66 val isStopped: Boolean = true,
67 val position: Duration = Duration.ZERO,
68 val duration: Duration = Duration.ZERO,
69 val volumePercent: Int = 100,
70 val repeatMode: RepeatMode = RepeatMode.OFF,
71 val shuffleEnabled: Boolean = false,
72 val error: String? = null,
73)