ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
sendspin_media_player.cpp
Go to the documentation of this file.
2
3#if defined(USE_ESP_IDF) && defined(USE_MEDIA_PLAYER) && defined(USE_SENDSPIN_CONTROLLER)
4
6#include "esphome/core/log.h"
7
8#include <sendspin/types.h>
9
10#include <algorithm>
11#include <cmath>
12#include <memory>
13#include <optional>
14
15#include <esp_timer.h>
16
17namespace esphome::sendspin_ {
18
19ESPHOME_LOG_TAG(TAG, "sendspin.media_player");
20
21// THREAD CONTEXT: Main loop. The callbacks registered here also fire on the main loop,
22// since SendspinHub dispatches group updates and controller state from client_->loop().
24 // Register for group updates to sync playback state
25 this->parent_->add_group_update_callback([this](const sendspin::GroupUpdateObject &group_obj) {
26 if (group_obj.playback_state.has_value()) {
28 switch (group_obj.playback_state.value()) {
29 case sendspin::SendspinPlaybackState::PLAYING:
31 break;
32 case sendspin::SendspinPlaybackState::STOPPED:
33 default:
35 break;
36 }
37 this->set_playback_state_(new_state);
38 }
39 });
40
41 this->parent_->add_controller_state_callback([this](const sendspin::ServerStateControllerObject &state) {
42 float new_volume = static_cast<float>(state.volume) / 100.0f;
43 bool new_muted = state.muted;
44 if ((new_volume != this->volume) || (new_muted != this->muted_)) {
45 this->volume = new_volume;
46 this->muted_ = new_muted;
47 this->publish_state();
48 }
49 });
50
51 // The connection dropped, so nothing is playing. The server never gets to send a final "stopped" group update, so
52 // without this the entity keeps reporting playing indefinitely. Volume and mute keep their last values, since
53 // media_player has no way to express an unknown volume.
54 this->parent_->add_controller_state_clear_callback(
56
57 // Publish an initial state
59 this->publish_state();
60}
61
62// THREAD CONTEXT: Main loop (called from the callbacks registered in setup())
64 if (this->state == new_state) {
65 return;
66 }
67 this->state = new_state;
68 this->publish_state();
69 ESP_LOGD(TAG, "State changed to %s", media_player::media_player_state_to_string(this->state));
70}
71
72// THREAD CONTEXT: Main loop (invoked by the media_player framework)
74 auto traits = media_player::MediaPlayerTraits();
75
76 // By default, the base media player always enables these traits, but they are not actually supported by this media
77 // player
81
82 traits.add_feature_flags(
86
87 // NEXT_TRACK, PREVIOUS_TRACK, SHUFFLE_SET, and REPEAT_SET are intentionally not advertised: the ESPHome native API
88 // does not implement the corresponding media player commands, so Home Assistant cannot actually send them even if
89 // we expose the capability. They remain accessible via ESPHome YAML automations.
90
91 return traits;
92}
93
94// THREAD CONTEXT: Main loop (invoked by the media_player framework)
96 if (!this->is_ready()) {
97 // Ignore any commands sent before the media player is setup
98 return;
99 }
100 if (!this->parent_->is_client_running()) {
101 ESP_LOGW(TAG, "Cannot control media player: Sendspin is disabled");
102 return;
103 }
104
105 auto volume = call.get_volume();
106 if (volume.has_value()) {
107 uint8_t new_volume = static_cast<uint8_t>(std::roundf(volume.value() * 100.0f));
108 this->parent_->send_client_command(sendspin::SendspinControllerCommand::VOLUME, new_volume, std::nullopt);
109 }
110
111 auto command = call.get_command();
112 if (!command.has_value()) {
113 return;
114 }
115 switch (command.value()) {
118 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PAUSE);
119 } else {
120 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PLAY);
121 }
122 break;
124 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PLAY);
125 break;
127 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PAUSE);
128 break;
130 this->parent_->send_client_command(sendspin::SendspinControllerCommand::STOP);
131 break;
133 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_OFF);
134 break;
136 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_ONE);
137 break;
139 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_ALL);
140 break;
142 this->parent_->send_client_command(sendspin::SendspinControllerCommand::SHUFFLE);
143 break;
145 this->parent_->send_client_command(sendspin::SendspinControllerCommand::UNSHUFFLE);
146 break;
148 this->parent_->send_client_command(sendspin::SendspinControllerCommand::NEXT);
149 break;
151 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PREVIOUS);
152 break;
154 this->parent_->send_client_command(
155 sendspin::SendspinControllerCommand::VOLUME,
156 static_cast<uint8_t>(std::roundf(std::min(1.0f, this->volume + this->volume_increment_) * 100.0f)),
157 std::nullopt);
158 break;
160 this->parent_->send_client_command(
161 sendspin::SendspinControllerCommand::VOLUME,
162 static_cast<uint8_t>(std::roundf(std::max(0.0f, this->volume - this->volume_increment_) * 100.0f)),
163 std::nullopt);
164 break;
166 this->parent_->send_client_command(sendspin::SendspinControllerCommand::MUTE, std::nullopt, true);
167 break;
169 this->parent_->send_client_command(sendspin::SendspinControllerCommand::MUTE, std::nullopt, false);
170 break;
171 default:
172 break;
173 }
174}
175
177 ESP_LOGCONFIG(TAG, "Sendspin Media Player: volume_increment=%.2f", this->volume_increment_);
178}
179
180} // namespace esphome::sendspin_
181#endif
bool is_ready() const
void set_playback_state_(media_player::MediaPlayerState new_state)
Publishes new_state if it differs from the current state.
media_player::MediaPlayerTraits get_traits() override
void control(const media_player::MediaPlayerCall &call) override
const char * media_player_state_to_string(MediaPlayerState state)
ESPHOME_LOG_TAG(TAG, "sendspin.image")