/
/
1"""Constants for the Streams Controller."""
2
3from __future__ import annotations
4
5from enum import StrEnum
6from typing import Final
7
8from music_assistant.helpers.util import get_total_system_memory, meets_memory_target
9
10
11class BufferMode(StrEnum):
12 """Buffer mode determines buffer behavior."""
13
14 SEEKABLE = "seekable"
15 ROLLING = "rolling"
16
17
18class BufferSize(StrEnum):
19 """Buffer size presets for configuration."""
20
21 MINIMAL = "minimal"
22 BALANCED = "balanced"
23 MAXIMUM = "maximum"
24
25
26# Calculate total system memory once at module load time
27TOTAL_SYSTEM_MEMORY_GB: Final[float] = get_total_system_memory()
28
29# RAM thresholds for the buffer-size presets, as NOMINAL targets. Both are checked via
30# meets_memory_target(), which absorbs the gap between a host's nominal size and what it
31# reports (kernel MemTotal reservation plus any integrated-GPU carve-out), so a "4GB" box
32# (reporting ~3.8GB) and an "8GB" box (reporting ~7.4GB) both qualify for their tier.
33BALANCED_MIN_RAM_GB: Final[float] = 4.0
34MAXIMUM_MIN_RAM_GB: Final[float] = 8.0
35
36# Buffer size in seconds for each preset
37BUFFER_SIZE_MAP: Final[dict[str, int]] = {
38 BufferSize.MINIMAL: 60,
39 BufferSize.BALANCED: 300,
40 BufferSize.MAXIMUM: 1200,
41}
42
43# Buffer size for radio streams (short rolling buffer)
44RADIO_BUFFER_SIZE: Final[int] = 15
45
46# Time to keep the flow stream response open after the last audio byte of a queue.
47# Players buffer a few seconds ahead of what they actually render; some of them drop
48# that buffer the moment the connection is closed, cutting off the end of the queue.
49# Holding the (idle) connection open gives them time to play it out first. Kept below
50# the webserver shutdown timeout so a lead-out never stalls a restart of the server.
51FLOW_STREAM_LEAD_OUT_SECONDS: Final[int] = 8
52
53
54# Configuration keys
55CONF_BUFFER_SIZE: Final[str] = "buffer_size"
56
57
58def get_available_buffer_sizes() -> list[BufferSize]:
59 """
60 Return the buffer-size presets allowed for this host's RAM.
61
62 Minimal is always available; Balanced needs ~4GB and Maximum ~8GB (both within the
63 reporting tolerance). When total memory is unknown (0.0, e.g. Windows) all presets are
64 offered (fail open).
65 """
66 if TOTAL_SYSTEM_MEMORY_GB == 0.0:
67 return [BufferSize.MINIMAL, BufferSize.BALANCED, BufferSize.MAXIMUM]
68 sizes = [BufferSize.MINIMAL]
69 if meets_memory_target(TOTAL_SYSTEM_MEMORY_GB, BALANCED_MIN_RAM_GB):
70 sizes.append(BufferSize.BALANCED)
71 if meets_memory_target(TOTAL_SYSTEM_MEMORY_GB, MAXIMUM_MIN_RAM_GB):
72 sizes.append(BufferSize.MAXIMUM)
73 return sizes
74
75
76def _get_default_buffer_size() -> str:
77 # Unknown memory (0.0) picks the conservative Minimal default, unlike the
78 # available-presets list which fails open â meets_memory_target() also fails open,
79 # so the 0.0 case is handled explicitly here before consulting it.
80 if TOTAL_SYSTEM_MEMORY_GB == 0.0:
81 return BufferSize.MINIMAL
82 if meets_memory_target(TOTAL_SYSTEM_MEMORY_GB, MAXIMUM_MIN_RAM_GB):
83 return BufferSize.MAXIMUM
84 if meets_memory_target(TOTAL_SYSTEM_MEMORY_GB, BALANCED_MIN_RAM_GB):
85 return BufferSize.BALANCED
86 return BufferSize.MINIMAL
87
88
89CONF_BUFFER_SIZE_DEFAULT: Final[str] = _get_default_buffer_size()
90CONF_ALLOW_CROSSFADE_SAME_ALBUM: Final[str] = "allow_crossfade_same_album"
91CONF_SMART_FADES_LOG_LEVEL: Final[str] = "smart_fades_log_level"
92
93# Maximum wait for a provider source-stream slot before a speculative attempt gives up.
94STREAM_SLOT_WAIT_TIMEOUT: Final[float] = 5.0
95
96# Total capacity budget when an actual playback start retries/reselects provider mappings.
97STREAM_SLOT_PLAYBACK_WAIT_TIMEOUT: Final[float] = 15.0
98
99# Maximum time spent searching other streaming providers for an alternative mapping
100# when every known candidate is capacity-saturated.
101STREAM_SLOT_MATCH_TIMEOUT: Final[float] = 5.0
102
103# Maximum seconds we wait for the buffer to catch up on a forward seek.
104# Beyond this, the stream is re-fetched at the seek position.
105SEEK_WAIT_THRESHOLD: Final[int] = 20
106
107# Streams webserver default port
108DEFAULT_PORT: Final[int] = 8097
109
110# Cache constants for resolved radio URLs
111CACHE_CATEGORY_RESOLVED_RADIO_URL: Final[int] = 100
112CACHE_PROVIDER: Final[str] = "audio"
113
114# StreamDetails.data key providers set to opt into the in-band title handoff.
115STREAMDETAILS_INBAND_TITLE_HANDOFF_KEY: Final[str] = "inband_title_handoff"
116# StreamDetails.data key where the streams controller records the in-band (ICY)
117# stream title after an opted-in provider takes ownership of stream_metadata
118# (StreamDetails.stream_title is a derived view whose setter would overwrite it).
119STREAMDETAILS_INBAND_TITLE_KEY: Final[str] = "inband_stream_title"
120