ESPHome 2026.3.0
Loading...
Searching...
No Matches
speaker_source_media_player.h
Go to the documentation of this file.
1#pragma once
2
4
5#ifdef USE_ESP32
6
11
15
16#include <array>
17#include <atomic>
18#include <memory>
19#include <vector>
20#include <freertos/FreeRTOS.h>
21#include <freertos/queue.h>
22
24
25// THREADING MODEL:
26// This component coordinates media sources that run their own decode tasks with speakers
27// that have their own playback callback tasks. Three thread contexts exist:
28//
29// - Main loop task: setup(), loop(), dump_config(), handle_media_state_changed_(),
30// handle_volume_request_(), handle_mute_request_(), handle_play_uri_request_(),
31// set_volume_(), set_mute_state_(), control(), get_source_state_(),
32// find_source_for_uri_(), try_execute_play_uri_(), save_volume_restore_state_(),
33// process_control_queue_(), handle_player_command_(), queue_command_(), queue_play_current_()
34//
35// - Media source task(s): handle_media_output_() via SourceBinding::write_audio().
36// Called from each source's decode task thread when streaming audio data.
37// Reads ps.active_source (atomic), writes ps.pending_frames (atomic), and calls
38// ps.speaker methods (speaker pointer is immutable after setup).
39//
40// - Speaker callback task: handle_speaker_playback_callback_() via speaker's
41// add_audio_output_callback(). Called when the speaker finishes writing frames to the DAC.
42// Reads ps.active_source (atomic), writes ps.pending_frames (atomic), and calls
43// active_source->notify_audio_played().
44//
45// control() is only called from the main loop (HA/automation commands).
46// Source tasks use defer() for all requests (volume, mute, play_uri).
47//
48// Thread-safe communication:
49// - FreeRTOS queue (media_control_command_queue_): control() -> loop() for play/command dispatch
50// - defer(): SourceBinding::request_volume/request_mute/request_play_uri -> main loop
51// - Atomic fields (active_source, pending_frames): shared between all three thread contexts
52//
53// Non-atomic pipeline fields (last_source, stopping_source, pending_source, playlist,
54// playlist_index, repeat_mode) are only accessed from the main loop thread.
55
56enum Pipeline : uint8_t {
59};
60
61enum RepeatMode : uint8_t {
65};
66
67// Forward declaration
68class SpeakerSourceMediaPlayer;
69
78 uint8_t pipeline;
79
80 // Implementations are in the .cpp file because SpeakerSourceMediaPlayer is only forward-declared here
81 size_t write_audio(const uint8_t *data, size_t length, uint32_t timeout_ms,
82 const audio::AudioStreamInfo &stream_info) override;
84 void request_volume(float volume) override;
85 void request_mute(bool is_muted) override;
86 void request_play_uri(const std::string &uri) override;
87};
88
90static constexpr uint32_t PIPELINE_TIMEOUT_IDS[] = {1, 2};
91
94 optional<media_player::MediaPlayerSupportedFormat> format;
95
96 std::atomic<media_source::MediaSource *> active_source{nullptr};
98 media_source::MediaSource *stopping_source{nullptr}; // Source we've asked to stop, awaiting IDLE
99 media_source::MediaSource *pending_source{nullptr}; // Source we've asked to play, awaiting PLAYING
100
101 // Each SourceBinding pairs a MediaSource* with its listener implementation.
102 // Uses unique_ptr so binding addresses are stable and set_listener() can be called in add_media_source().
103 // Uses std::vector because the count varies across instances (multiple speaker_source media players may exist).
104 std::vector<std::unique_ptr<SourceBinding>> sources;
105
106 // Dynamic allocation is unavoidable here: URIs from Home Assistant are arbitrary-length strings
107 // (media URLs with tokens can easily exceed 500 bytes), and playlist size is unbounded.
108 // Pre-allocating fixed buffers would waste significant RAM when idle without covering worst cases.
109 std::vector<std::string> playlist;
110 size_t playlist_index{0};
113
114 // When non-empty, playlist_index indexes into these vectors
115 // which contain the actual playlist indices in shuffled order
116 std::vector<size_t> shuffle_indices;
117
118 // Track frames sent to speaker to correlate with playback callbacks.
119 // Atomic because it is written from the main loop/source tasks and read/decremented from the speaker playback
120 // callback.
121 std::atomic<uint32_t> pending_frames{0};
122
124 bool is_configured() const { return this->speaker != nullptr; }
125};
126
128 enum Type : uint8_t {
129 PLAY_URI, // Clear playlist, reset index, add URI, queue PLAY_CURRENT
130 ENQUEUE_URI, // Add URI to playlist, queue PLAY_CURRENT if idle
131 PLAYLIST_ADVANCE, // Advance index (or wrap for repeat_all), queue PLAY_CURRENT if more items
132 PLAY_CURRENT, // Play item at current playlist index (can retry if speaker not ready)
133 SEND_COMMAND, // Send command to active source
134 };
136 uint8_t pipeline; // MEDIA_PIPELINE or ANNOUNCEMENT_PIPELINE
137
138 union {
139 std::string *uri; // Owned pointer, must delete after xQueueReceive (for PLAY_URI and ENQUEUE_URI)
142};
143
145 float volume;
147};
148
150 friend struct SourceBinding;
151
152 public:
154 void setup() override;
155 void loop() override;
156 void dump_config() override;
157
158 // MediaPlayer implementations
160 bool is_muted() const override { return this->is_muted_; }
161
162 // Percentage to increase or decrease the volume for volume up or volume down commands
163 void set_volume_increment(float volume_increment) { this->volume_increment_ = volume_increment; }
164
165 // Volume used initially on first boot when no volume had been previously saved
166 void set_volume_initial(float volume_initial) { this->volume_initial_ = volume_initial; }
167
168 void set_volume_max(float volume_max) { this->volume_max_ = volume_max; }
169 void set_volume_min(float volume_min) { this->volume_min_ = volume_min; }
170
172 void add_media_source(uint8_t pipeline, media_source::MediaSource *media_source);
173
174 void set_speaker(uint8_t pipeline, speaker::Speaker *speaker) { this->pipelines_[pipeline].speaker = speaker; }
176 this->pipelines_[pipeline].format = format;
177 }
178
182
183 void set_playlist_delay_ms(uint8_t pipeline, uint32_t delay_ms);
184
185 protected:
186 // Callbacks from source bindings (pipeline index is captured at binding creation time)
187 size_t handle_media_output_(uint8_t pipeline, media_source::MediaSource *source, const uint8_t *data, size_t length,
188 uint32_t timeout_ms, const audio::AudioStreamInfo &stream_info);
191 void handle_volume_request_(float volume);
193 void handle_play_uri_request_(uint8_t pipeline, const std::string &uri);
194
195 void handle_speaker_playback_callback_(uint32_t frames, int64_t timestamp, uint8_t pipeline);
196
197 // Receives commands from HA or from the voice assistant component
198 // Sends commands to the media_control_command_queue_
199 void control(const media_player::MediaPlayerCall &call) override;
200
202 void set_volume_(float volume, bool publish = true);
203
207 void set_mute_state_(bool mute_state, bool publish = true);
208
211
218 media_player::MediaPlayerState old_state) const;
219
221 void handle_player_command_(media_player::MediaPlayerCommand player_command, uint8_t pipeline);
222 bool try_execute_play_uri_(const std::string &uri, uint8_t pipeline);
223 media_source::MediaSource *find_source_for_uri_(const std::string &uri, uint8_t pipeline);
225 void queue_play_current_(uint8_t pipeline, uint32_t delay_ms = 0);
226
228 size_t get_playlist_position_(uint8_t pipeline) const;
229
231 void shuffle_playlist_(uint8_t pipeline);
232
234 void unshuffle_playlist_(uint8_t pipeline);
235
237
238 // Pipeline context for media (index 0) and announcement (index 1) pipelines.
239 // See THREADING MODEL at top of namespace for access rules.
240 std::array<PipelineContext, 2> pipelines_;
241
242 // Used to save volume/mute state for restoration on reboot
244
248
249 // The amount to change the volume on volume up/down commands
251
252 // The initial volume used by Setup when no previous volume was saved
254
257
258 bool is_muted_{false};
259};
260
261} // namespace esphome::speaker_source
262
263#endif // USE_ESP32
media_source::MediaSource * source
audio::AudioStreamInfo stream_info
Abstract base class for media sources MediaSource provides audio data to an orchestrator via the Medi...
Callbacks from a MediaSource to its orchestrator.
void shuffle_playlist_(uint8_t pipeline)
Generates shuffled indices for the playlist, keeping current track at current position.
void set_playlist_delay_ms(uint8_t pipeline, uint32_t delay_ms)
void queue_play_current_(uint8_t pipeline, uint32_t delay_ms=0)
void set_mute_state_(bool mute_state, bool publish=true)
Sets the mute state.
void set_speaker(uint8_t pipeline, speaker::Speaker *speaker)
void queue_command_(MediaPlayerControlCommand::Type type, uint8_t pipeline)
media_source::MediaSource * find_source_for_uri_(const std::string &uri, uint8_t pipeline)
void unshuffle_playlist_(uint8_t pipeline)
Clears shuffle indices and adjusts playlist_index to maintain current track.
media_player::MediaPlayerState get_source_state_(media_source::MediaSource *media_source, bool playlist_active, media_player::MediaPlayerState old_state) const
Determine media player state from a pipeline's active source.
void handle_play_uri_request_(uint8_t pipeline, const std::string &uri)
void control(const media_player::MediaPlayerCall &call) override
void handle_player_command_(media_player::MediaPlayerCommand player_command, uint8_t pipeline)
size_t get_playlist_position_(uint8_t pipeline) const
Maps playlist_index through shuffle indices if shuffle is active.
void add_media_source(uint8_t pipeline, media_source::MediaSource *media_source)
Adds a media source to a pipeline and registers this player as its listener.
void set_format(uint8_t pipeline, const media_player::MediaPlayerSupportedFormat &format)
bool try_execute_play_uri_(const std::string &uri, uint8_t pipeline)
void set_volume_(float volume, bool publish=true)
Updates this->volume and saves volume/mute state to flash for restoration if publish is true.
void handle_media_state_changed_(uint8_t pipeline, media_source::MediaSource *source, media_source::MediaSourceState state)
media_player::MediaPlayerTraits get_traits() override
void handle_speaker_playback_callback_(uint32_t frames, int64_t timestamp, uint8_t pipeline)
void save_volume_restore_state_()
Saves the current volume and mute state to the flash for restoration.
size_t handle_media_output_(uint8_t pipeline, media_source::MediaSource *source, const uint8_t *data, size_t length, uint32_t timeout_ms, const audio::AudioStreamInfo &stream_info)
uint16_t type
bool state
Definition fan.h:2
constexpr float PROCESSOR
For components that use data from sensors like displays.
Definition component.h:33
const char int const __FlashStringHelper * format
Definition log.h:74
static void uint32_t
union esphome::speaker_source::MediaPlayerControlCommand::@154 data
optional< media_player::MediaPlayerSupportedFormat > format
bool is_configured() const
Check if this pipeline is configured (has a speaker assigned)
std::vector< std::unique_ptr< SourceBinding > > sources
std::atomic< media_source::MediaSource * > active_source
Per-source listener binding that captures the source pointer at registration time.
void request_play_uri(const std::string &uri) override
SourceBinding(SpeakerSourceMediaPlayer *player, media_source::MediaSource *source, uint8_t pipeline)
void report_state(media_source::MediaSourceState state) override
size_t write_audio(const uint8_t *data, size_t length, uint32_t timeout_ms, const audio::AudioStreamInfo &stream_info) override
uint16_t length
Definition tt21100.cpp:0
uint16_t timestamp
Definition tt21100.cpp:2