ESPHome 2026.5.0b1
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 if (this->state != new_state) {
38 this->state = new_state;
39 this->publish_state();
40 ESP_LOGD(TAG, "State changed to %s", media_player::media_player_state_to_string(this->state));
41 }
42 }
43 });
44
45 this->parent_->add_controller_state_callback([this](const sendspin::ServerStateControllerObject &state) {
46 float new_volume = static_cast<float>(state.volume) / 100.0f;
47 bool new_muted = state.muted;
48 if ((new_volume != this->volume) || (new_muted != this->muted_)) {
49 this->volume = new_volume;
50 this->muted_ = new_muted;
51 this->publish_state();
52 }
53 });
54
55 // Publish an initial state
57 this->publish_state();
58}
59
60// THREAD CONTEXT: Main loop (invoked by the media_player framework)
62 auto traits = media_player::MediaPlayerTraits();
63
64 // By default, the base media player always enables these traits, but they are not actually supported by this media
65 // player
69
70 traits.add_feature_flags(
74
75 // NEXT_TRACK, PREVIOUS_TRACK, SHUFFLE_SET, and REPEAT_SET are intentionally not advertised: the ESPHome native API
76 // does not implement the corresponding media player commands, so Home Assistant cannot actually send them even if
77 // we expose the capability. They remain accessible via ESPHome YAML automations.
78
79 return traits;
80}
81
82// THREAD CONTEXT: Main loop (invoked by the media_player framework)
84 if (!this->is_ready()) {
85 // Ignore any commands sent before the media player is setup
86 return;
87 }
88
89 auto volume = call.get_volume();
90 if (volume.has_value()) {
91 uint8_t new_volume = static_cast<uint8_t>(std::roundf(volume.value() * 100.0f));
92 this->parent_->send_client_command(sendspin::SendspinControllerCommand::VOLUME, new_volume, std::nullopt);
93 }
94
95 auto command = call.get_command();
96 if (!command.has_value()) {
97 return;
98 }
99 switch (command.value()) {
102 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PAUSE);
103 } else {
104 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PLAY);
105 }
106 break;
108 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PLAY);
109 break;
111 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PAUSE);
112 break;
114 this->parent_->send_client_command(sendspin::SendspinControllerCommand::STOP);
115 break;
117 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_OFF);
118 break;
120 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_ONE);
121 break;
123 this->parent_->send_client_command(sendspin::SendspinControllerCommand::REPEAT_ALL);
124 break;
126 this->parent_->send_client_command(sendspin::SendspinControllerCommand::SHUFFLE);
127 break;
129 this->parent_->send_client_command(sendspin::SendspinControllerCommand::UNSHUFFLE);
130 break;
132 this->parent_->send_client_command(sendspin::SendspinControllerCommand::NEXT);
133 break;
135 this->parent_->send_client_command(sendspin::SendspinControllerCommand::PREVIOUS);
136 break;
138 this->parent_->send_client_command(
139 sendspin::SendspinControllerCommand::VOLUME,
140 static_cast<uint8_t>(std::roundf(std::min(1.0f, this->volume + this->volume_increment_) * 100.0f)),
141 std::nullopt);
142 break;
144 this->parent_->send_client_command(
145 sendspin::SendspinControllerCommand::VOLUME,
146 static_cast<uint8_t>(std::roundf(std::max(0.0f, this->volume - this->volume_increment_) * 100.0f)),
147 std::nullopt);
148 break;
150 this->parent_->send_client_command(sendspin::SendspinControllerCommand::MUTE, std::nullopt, true);
151 break;
153 this->parent_->send_client_command(sendspin::SendspinControllerCommand::MUTE, std::nullopt, false);
154 break;
155 default:
156 break;
157 }
158}
159
161 ESP_LOGCONFIG(TAG, "Sendspin Media Player: volume_increment=%.2f", this->volume_increment_);
162}
163
164} // namespace esphome::sendspin_
165#endif
bool is_ready() const
const optional< float > & get_volume() const
media_player::MediaPlayerTraits get_traits() override
void control(const media_player::MediaPlayerCall &call) override
const char * media_player_state_to_string(MediaPlayerState state)