Arduino DLNA Server
Loading...
Searching...
No Matches
ControlPointMediaRenderer.h
Go to the documentation of this file.
1// Header-only control point helper for MediaRenderer devices
2#pragma once
3
6#include <functional>
7
8namespace tiny_dlna {
9
19 public:
26
32 void setDeviceTypeFilter(const char* filter) {
34 }
35
45 uint32_t minWaitMs = 3000, uint32_t maxWaitMs = 60000) {
47 "ControlPointMediaServer::begin device_type_filter='%s'",
49 return mgr.begin(http, udp, device_type_filter, minWaitMs, maxWaitMs);
50 }
51
57 int count = 0;
58 for (auto& d : mgr.getDevices()) {
59 const char* dt = d.getDeviceType();
60 if (!device_type_filter || StrView(dt).contains(device_type_filter))
61 count++;
62 }
63 return count;
64 }
65
70 void setDeviceIndex(int idx) { device_index = idx; }
71
72 typedef void (*NotificationCallback)(void* reference, const char* sid,
73 const char* varName,
74 const char* newValue);
75
83 bool subscribeNotifications(int timeoutSeconds = 60,
84 NotificationCallback cb = nullptr) {
85 mgr.setReference(this);
86 // register provided callback or fallback to the default processNotification
87 if (cb)
89 else
91 auto& device = mgr.getDevice(device_index);
92 return mgr.subscribeNotifications(device, timeoutSeconds);
93 }
94
99 void setInstanceID(const char* id) { instance_id = id; }
100
106 bool setMediaURI(const char* uri) {
107 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
108 if (!svc) {
109 DlnaLogger.log(DlnaLogLevel::Error, "No AVTransport service found");
110 return false;
111 }
112 ActionRequest act(svc, "SetAVTransportURI");
113 act.addArgument("InstanceID", instance_id);
114 act.addArgument("CurrentURI", uri);
115 act.addArgument("CurrentURIMetaData", "");
116 mgr.addAction(act);
118 return (bool)last_reply;
119 }
120
125 bool play() {
126 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
127 if (!svc) {
128 DlnaLogger.log(DlnaLogLevel::Error, "No AVTransport service found");
129 return false;
130 }
131 ActionRequest act(svc, "Play");
132 act.addArgument("InstanceID", instance_id);
133 act.addArgument("Speed", "1");
134 mgr.addAction(act);
136 if (last_reply) setActiveState(true);
137 return (bool)last_reply;
138 }
139
145 bool play(const char* uri) {
146 if (!setMediaURI(uri)) return false;
147 return play();
148 }
149
154 bool pause() {
155 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
156 if (!svc) return false;
157 ActionRequest act(svc, "Pause");
158 act.addArgument("InstanceID", instance_id);
159 mgr.addAction(act);
161 if (last_reply) setActiveState(false);
162 return (bool)last_reply;
163 }
164
169 bool stop() {
170 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
171 if (!svc) return false;
172 ActionRequest act(svc, "Stop");
173 act.addArgument("InstanceID", instance_id);
174 mgr.addAction(act);
176 if (last_reply) setActiveState(false);
177 return (bool)last_reply;
178 }
179
185 bool setVolume(int volumePercent) {
186 if (volumePercent < 0) volumePercent = 0;
187 if (volumePercent > 100) volumePercent = 100;
188 DLNAServiceInfo& svc =
189 selectService("urn:upnp-org:serviceId:RenderingControl");
190 if (!svc) {
191 DlnaLogger.log(DlnaLogLevel::Error, "No RenderingControl service found");
192 return false;
193 }
194 char volBuf[8];
195 snprintf(volBuf, sizeof(volBuf), "%d", volumePercent);
196 ActionRequest act(svc, "SetVolume");
197 act.addArgument("InstanceID", "0");
198 act.addArgument("Channel", "Master");
199 act.addArgument("DesiredVolume", volBuf);
200 mgr.addAction(act);
202 return (bool)last_reply;
203 }
204
210 bool setMute(bool mute) {
211 DLNAServiceInfo& svc =
212 selectService("urn:upnp-org:serviceId:RenderingControl");
213 if (!svc) return false;
214 ActionRequest act(svc, "SetMute");
215 act.addArgument("InstanceID", "0");
216 act.addArgument("Channel", "Master");
217 act.addArgument("DesiredMute", mute ? "1" : "0");
218 mgr.addAction(act);
220 return (bool)r;
221 }
222
227 int getVolume() {
228 DLNAServiceInfo& svc =
229 selectService("urn:upnp-org:serviceId:RenderingControl");
230 if (!svc) return -1;
231 ActionRequest act(svc, "GetVolume");
232 act.addArgument("InstanceID", instance_id);
233 act.addArgument("Channel", "Master");
234 mgr.addAction(act);
236 if (!last_reply) return -1;
237 const char* v = findArgument(last_reply, "CurrentVolume");
238 if (!v) return -1;
239 return atoi(v);
240 }
241
246 int getMute() {
247 DLNAServiceInfo& svc =
248 selectService("urn:upnp-org:serviceId:RenderingControl");
249 if (!svc) return -1;
250 ActionRequest act(svc, "GetMute");
251 act.addArgument("InstanceID", instance_id);
252 act.addArgument("Channel", "Master");
253 mgr.addAction(act);
255 if (!last_reply) return -1;
256 const char* m = findArgument(last_reply, "CurrentMute");
257 if (!m) return -1;
258 return atoi(m);
259 }
260
265 unsigned long getPositionMs() {
266 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
267 if (!svc) return 0;
268 ActionRequest act(svc, "GetPositionInfo");
269 act.addArgument("InstanceID", instance_id);
270 mgr.addAction(act);
272 if (!last_reply) return 0;
273 const char* rel = findArgument(last_reply, "RelTime");
274 if (!rel) return 0;
275 return parseTimeToMs(rel);
276 }
277
283 const char* getTransportState() {
284 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
285 if (!svc) return nullptr;
286 ActionRequest act(svc, "GetTransportInfo");
287 act.addArgument("InstanceID", instance_id);
288 mgr.addAction(act);
290 if (!last_reply) return nullptr;
291 return findArgument(last_reply, "CurrentTransportState");
292 }
293
298 const char* getCurrentURI() {
299 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
300 if (!svc) return nullptr;
301 ActionRequest act(svc, "GetMediaInfo");
302 act.addArgument("InstanceID", instance_id);
303 mgr.addAction(act);
305 if (!last_reply) return nullptr;
306 return findArgument(last_reply, "CurrentURI");
307 }
308
314 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
315 if (!svc) return -1;
316 ActionRequest act(svc, "GetMediaInfo");
317 act.addArgument("InstanceID", instance_id);
318 mgr.addAction(act);
320 if (!last_reply) return -1;
321 const char* n = findArgument(last_reply, "NrTracks");
322 if (!n) return -1;
323 return atoi(n);
324 }
325
330 unsigned long getMediaDurationMs() {
331 DLNAServiceInfo& svc = mgr.getService("urn:upnp-org:serviceId:AVTransport");
332 if (!svc) return 0;
333 ActionRequest act(svc, "GetMediaInfo");
334 act.addArgument("InstanceID", instance_id);
335 mgr.addAction(act);
337 if (!last_reply) return 0;
338 const char* d = findArgument(last_reply, "MediaDuration");
339 if (!d) return 0;
340 return parseTimeToMs(d);
341 }
342
348 DLNAServiceInfo& svc = mgr.getService("urn:upnp-org:serviceId:AVTransport");
349 if (!svc) return -1;
350 ActionRequest act(svc, "GetPositionInfo");
351 act.addArgument("InstanceID", instance_id);
352 mgr.addAction(act);
354 if (!last_reply) return -1;
355 const char* tr = findArgument(last_reply, "Track");
356 if (!tr) return -1;
357 return atoi(tr);
358 }
359
364 unsigned long getTrackDurationMs() {
365 DLNAServiceInfo& svc = mgr.getService("urn:upnp-org:serviceId:AVTransport");
366 if (!svc) return 0;
367 ActionRequest act(svc, "GetPositionInfo");
368 act.addArgument("InstanceID", instance_id);
369 mgr.addAction(act);
371 if (!last_reply) return 0;
372 const char* td = findArgument(last_reply, "TrackDuration");
373 if (!td) return 0;
374 return parseTimeToMs(td);
375 }
376
381 const ActionReply& getLastReply() const { return last_reply; }
382
387 bool isActive() { return is_active; }
388
393 void setReference(void* ref) { reference = ref; }
394
400 void onActiveChanged(std::function<void(bool, void*)> cb) { activeChangedCallback = cb; }
401
402 protected:
404 bool is_active = false;
405 std::function<void(bool, void*)> activeChangedCallback = nullptr;
407 const char* instance_id = "0";
409 "urn:schemas-upnp-org:device:MediaRenderer:1";
412 void *reference = nullptr;
413
414 // Helper to update is_active and notify callback only on change
415 void setActiveState(bool s) {
416 if (is_active == s) return;
417 is_active = s;
418 DlnaLogger.log(DlnaLogLevel::Info, "ControlPointMediaRenderer active=%s", is_active ? "true" : "false");
420 }
421
423 static void processNotification(void* reference, const char* sid,
424 const char* varName, const char* newValue) {
425 // Log every notification invocation for debugging/visibility
427 "processNotification sid='%s' var='%s' value='%s'",
428 sid ? sid : "(null)", varName ? varName : "(null)",
429 newValue ? newValue : "(null)");
430
431 ControlPointMediaRenderer* renderer =
433 if (renderer) {
434 // Handle the notification (e.g., update internal state, call callbacks)
435 if (StrView(varName) == "TransportState") {
436 if (StrView(newValue) == "PLAYING") {
437 renderer->setActiveState(true);
438 } else {
439 renderer->setActiveState(false);
440 }
441 }
442 }
443 }
444
445 // Helper: find argument by name in ActionReply (returns nullptr if not found)
446 const char* findArgument(ActionReply& r, const char* name) {
447 for (auto& a : r.arguments) {
448 if (a.name != nullptr) {
449 StrView nm(a.name);
450 if (nm == name) return a.value.c_str();
451 }
452 }
453 return nullptr;
454 }
455
456 // Helper: parse DLNA time string (HH:MM:SS, MM:SS) to milliseconds
457 static unsigned long parseTimeToMs(const char* t) {
458 if (t == nullptr) return 0;
459 int h = 0, m = 0, s = 0;
460 int parts = 0;
461 // Count colons
462 const char* p = t;
463 int colonCount = 0;
464 while (*p) {
465 if (*p == ':') colonCount++;
466 p++;
467 }
468 if (colonCount == 2) {
469 parts = sscanf(t, "%d:%d:%d", &h, &m, &s);
470 } else if (colonCount == 1) {
471 parts = sscanf(t, "%d:%d", &m, &s);
472 h = 0;
473 } else {
474 // maybe it's seconds only
475 parts = sscanf(t, "%d", &s);
476 }
477 if (parts <= 0) return 0;
478 unsigned long total =
479 (unsigned long)h * 3600UL + (unsigned long)m * 60UL + (unsigned long)s;
480 return total * 1000UL;
481 }
482
483 // Select service on a device filtered by device_type_filter
486 int idx = 0;
487 for (auto& d : all) {
488 const char* dt = d.getDeviceType();
489 if (device_type_filter && !StrView(dt).contains(device_type_filter))
490 continue;
491 if (idx == device_index) {
492 DLNAServiceInfo& s = d.getService(id);
493 if (s) return s;
494 }
495 idx++;
496 }
497 return mgr.getService(id);
498 }
499};
500
501} // namespace tiny_dlna
Represents the result of invoking a DLNA service Action.
Definition: Action.h:42
Vector< Argument > arguments
Definition: Action.h:45
Represents a request to invoke a remote DLNA service action.
Definition: Action.h:79
void addArgument(Argument arg)
Definition: Action.h:88
Helper class to control a MediaRenderer device from a control point.
Definition: ControlPointMediaRenderer.h:18
bool is_active
Definition: ControlPointMediaRenderer.h:404
int device_index
Definition: ControlPointMediaRenderer.h:406
void setActiveState(bool s)
Definition: ControlPointMediaRenderer.h:415
int getVolume()
Query the current volume from the renderer.
Definition: ControlPointMediaRenderer.h:227
static void processNotification(void *reference, const char *sid, const char *varName, const char *newValue)
Notification callback.
Definition: ControlPointMediaRenderer.h:423
ActionReply last_reply
Definition: ControlPointMediaRenderer.h:411
unsigned long getTrackDurationMs()
Get current track duration in milliseconds (from TrackDuration)
Definition: ControlPointMediaRenderer.h:364
const char * device_type_filter_default
Definition: ControlPointMediaRenderer.h:408
bool isActive()
Query if the helper considers the renderer to be actively playing.
Definition: ControlPointMediaRenderer.h:387
int getMute()
Query mute state.
Definition: ControlPointMediaRenderer.h:246
bool subscribeNotifications(int timeoutSeconds=60, NotificationCallback cb=nullptr)
Subscribe to event notifications for the selected renderer.
Definition: ControlPointMediaRenderer.h:83
bool stop()
Stop playback.
Definition: ControlPointMediaRenderer.h:169
bool pause()
Pause playback.
Definition: ControlPointMediaRenderer.h:154
const char * findArgument(ActionReply &r, const char *name)
Definition: ControlPointMediaRenderer.h:446
const char * device_type_filter
Definition: ControlPointMediaRenderer.h:410
const ActionReply & getLastReply() const
Return last ActionReply (from the most recent synchronous call)
Definition: ControlPointMediaRenderer.h:381
bool begin(DLNAHttpRequest &http, IUDPService &udp, uint32_t minWaitMs=3000, uint32_t maxWaitMs=60000)
Begin discovery and processing.
Definition: ControlPointMediaRenderer.h:44
bool setMediaURI(const char *uri)
Set the media URI on the renderer.
Definition: ControlPointMediaRenderer.h:106
int getTrackIndex()
Get current track index (from GetPositionInfo)
Definition: ControlPointMediaRenderer.h:347
void onActiveChanged(std::function< void(bool, void *)> cb)
Register a callback invoked when the active (playing) state changes.
Definition: ControlPointMediaRenderer.h:400
void setDeviceIndex(int idx)
Select the active renderer by index (0-based)
Definition: ControlPointMediaRenderer.h:70
bool play(const char *uri)
Set the media URI and start playback.
Definition: ControlPointMediaRenderer.h:145
int getDeviceCount()
Return the number of discovered devices matching the renderer filter.
Definition: ControlPointMediaRenderer.h:56
void setInstanceID(const char *id)
Set the InstanceID used for AVTransport and RenderingControl actions.
Definition: ControlPointMediaRenderer.h:99
void setReference(void *ref)
Attach an opaque reference object that will be passed to callbacks.
Definition: ControlPointMediaRenderer.h:393
unsigned long getPositionMs()
Query current playback position (RelTime)
Definition: ControlPointMediaRenderer.h:265
bool setVolume(int volumePercent)
Set renderer volume.
Definition: ControlPointMediaRenderer.h:185
DLNAServiceInfo & selectService(const char *id)
Definition: ControlPointMediaRenderer.h:484
const char * getCurrentURI()
Get the current media URI from the renderer.
Definition: ControlPointMediaRenderer.h:298
std::function< void(bool, void *)> activeChangedCallback
Definition: ControlPointMediaRenderer.h:405
static unsigned long parseTimeToMs(const char *t)
Definition: ControlPointMediaRenderer.h:457
void setDeviceTypeFilter(const char *filter)
Restrict this helper to devices of the given device type.
Definition: ControlPointMediaRenderer.h:32
bool setMute(bool mute)
Set mute state on the renderer.
Definition: ControlPointMediaRenderer.h:210
const char * instance_id
Definition: ControlPointMediaRenderer.h:407
void * reference
Definition: ControlPointMediaRenderer.h:412
unsigned long getMediaDurationMs()
Get total media duration in milliseconds (from MediaDuration)
Definition: ControlPointMediaRenderer.h:330
ControlPointMediaRenderer(DLNAControlPoint &mgr)
Construct the helper with a reference to the control point mgr.
Definition: ControlPointMediaRenderer.h:25
bool play()
Start playback on the renderer.
Definition: ControlPointMediaRenderer.h:125
DLNAControlPoint & mgr
Definition: ControlPointMediaRenderer.h:403
void(* NotificationCallback)(void *reference, const char *sid, const char *varName, const char *newValue)
Definition: ControlPointMediaRenderer.h:72
int getNrTracks()
Get number of tracks in the current media.
Definition: ControlPointMediaRenderer.h:313
const char * getTransportState()
Query transport state (e.g. STOPPED, PLAYING, PAUSED_PLAYBACK)
Definition: ControlPointMediaRenderer.h:283
Lightweight DLNA control point manager.
Definition: DLNAControlPoint.h:58
DLNADeviceInfo & getDevice(int deviceIdx=0)
Provides the device information by index.
Definition: DLNAControlPoint.h:313
ActionRequest & addAction(ActionRequest act)
Registers a method that will be called.
Definition: DLNAControlPoint.h:161
Vector< DLNADeviceInfo > & getDevices()
Definition: DLNAControlPoint.h:343
bool subscribeNotifications(DLNADeviceInfo &device, int timeoutSeconds=60)
Subscribe to changes for all device services.
Definition: DLNAControlPoint.h:184
bool begin(DLNAHttpRequest &http, IUDPService &udp, const char *searchTarget="ssdp:all", uint32_t minWaitMs=3000, uint32_t maxWaitMs=60000)
Start discovery by sending M-SEARCH requests and process replies.
Definition: DLNAControlPoint.h:89
ActionReply & executeActions()
Executes all registered methods.
Definition: DLNAControlPoint.h:168
DLNAServiceInfo & getService(const char *id)
Provide addess to the service information.
Definition: DLNAControlPoint.h:302
void setReference(void *ref)
Attach an opaque reference pointer (optional, for caller context)
Definition: DLNAControlPoint.h:250
void onNotification(std::function< void(void *reference, const char *sid, const char *varName, const char *newValue)> cb)
Register a callback that will be invoked for incoming event notification.
Definition: DLNAControlPoint.h:242
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:16
Simple API to process get, put, post, del http requests I tried to use Arduino HttpClient,...
Definition: HttpRequest.h:21
Abstract Interface for UDP API.
Definition: IUDPService.h:34
void log(DlnaLogLevel current_level, const char *fmt...)
Print log message.
Definition: Logger.h:40
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:19
Vector implementation which provides the most important methods as defined by std::vector....
Definition: Vector.h:21
Definition: Allocator.h:6
LoggerClass DlnaLogger
Definition: Logger.cpp:5