ESPHome 2026.8.0b4
Loading...
Searching...
No Matches
sendspin_media_player.cpp
Go to the documentation of this file.
2
3#if defined(USE_ESP32) && 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
19static const char *const 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
101 auto volume = call.get_volume();
102 if (volume.has_value()) {
103 uint8_t new_volume = static_cast<uint8_t>(std::roundf(volume.value() * 100.0f));
104 this->parent_->send_client_command(sendspin::SendspinControllerCommand::VOLUME, new_volume, std::nullopt);
105 }
106
107 auto command = call.get_command();
108 if (!command.has_value()) {
109 return;
110 }
111 switch (command.value()) {
114 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PAUSE);
115 } else {
116 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PLAY);
117 }
118 break;
120 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PLAY);
121 break;
123 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PAUSE);
124 break;
126 this->parent_->send_client_command(sendspin::SendspinControllerCommand::STOP);
127 break;
129 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_OFF);
130 break;
132 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_ONE);
133 break;
135 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_ALL);
136 break;
138 this->parent_->send_client_command(sendspin::SendspinControllerCommand::SHUFFLE);
139 break;
141 this->parent_->send_client_command(sendspin::SendspinControllerCommand::UNSHUFFLE);
142 break;
144 this->parent_->send_client_command(sendspin::SendspinControllerCommand::NEXT);
145 break;
147 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PREVIOUS);
148 break;
150 this->parent_->send_client_command(
151 sendspin::SendspinControllerCommand::VOLUME,
152 static_cast<uint8_t>(std::roundf(std::min(1.0f, this->volume + this->volume_increment_) * 100.0f)),
153 std::nullopt);
154 break;
156 this->parent_->send_client_command(
157 sendspin::SendspinControllerCommand::VOLUME,
158 static_cast<uint8_t>(std::roundf(std::max(0.0f, this->volume - this->volume_increment_) * 100.0f)),
159 std::nullopt);
160 break;
162 this->parent_->send_client_command(sendspin::SendspinControllerCommand::MUTE, std::nullopt, true);
163 break;
165 this->parent_->send_client_command(sendspin::SendspinControllerCommand::MUTE, std::nullopt, false);
166 break;
167 default:
168 break;
169 }
170}
171
173 ESP_LOGCONFIG(TAG, "Sendspin Media Player: volume_increment=%.2f", this->volume_increment_);
174}
175
176} // namespace esphome::sendspin_
177#endif
bool is_ready() const
const optional< float > & get_volume() 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)