Arduino DLNA Server
Loading...
Searching...
No Matches
DLNAControlPointMediaRenderer.h
Go to the documentation of this file.
1// Header-only control point helper for MediaRenderer devices
2#pragma once
3
4#include <functional>
5
6#include "dlna/Action.h"
11
12namespace tiny_dlna {
13
25 public:
28
45 IUDPService& udp)
46 : p_mgr(&m), p_http(&http), p_udp(&udp) {}
47
50
57 void setDeviceTypeFilter(const char* filter) {
59 }
60
69 bool begin(uint32_t minWaitMs = 3000, uint32_t maxWaitMs = 60000) {
71 "ControlPointMediaRenderer::begin - device_type_filter='%s'",
73 if (!p_mgr) {
74 DlnaLogger.log(DlnaLogLevel::Error, "mgr instance not set");
75 return false;
76 }
77 if (!p_http) {
78 DlnaLogger.log(DlnaLogLevel::Error, "http instance not set");
79 return false;
80 }
81 if (!p_udp) {
82 DlnaLogger.log(DlnaLogLevel::Error, "udp instance not set");
83 return false;
84 }
85 return p_mgr->begin(*p_http, *p_udp, device_type_filter, minWaitMs,
86 maxWaitMs);
87 }
88
94 int count = 0;
95 for (auto& d : p_mgr->getDevices()) {
96 const char* dt = d.getDeviceType();
97 if (!device_type_filter || StrView(dt).contains(device_type_filter))
98 count++;
99 }
100 return count;
101 }
102
107 void setDeviceIndex(int idx) { device_index = idx; }
108
110 void setHttp(DLNAHttpRequest& http) { p_http = &http; }
111
113 void setUdp(IUDPService& udp) { p_udp = &udp; }
114
115 typedef void (*NotificationCallback)(void* reference, const char* sid,
116 const char* varName,
117 const char* newValue);
118
122 }
123
128 void setInstanceID(const char* id) { instance_id = id; }
129
135 bool setMediaURI(const char* uri) {
136 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
137 if (!svc) {
138 DlnaLogger.log(DlnaLogLevel::Error, "No AVTransport service found");
139 return false;
140 }
141 ActionRequest act(svc, "SetAVTransportURI");
142 act.addArgument("InstanceID", instance_id);
143 act.addArgument("CurrentURI", uri);
144 // Some renderers require the CurrentURIMetaData argument to be present
145 // even when empty. Use the Argument constructor to force adding an empty
146 // value (the addArgument(name,value) helper skips empty values).
147 act.addArgument(Argument("CurrentURIMetaData", ""));
148 p_mgr->addAction(act);
150 if (last_reply) {
151 local_url = uri;
152 }
153 return (bool)last_reply;
154 }
155
160 bool play() {
161 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
162 if (!svc) {
163 DlnaLogger.log(DlnaLogLevel::Error, "No AVTransport service found");
164 return false;
165 }
166 ActionRequest act(svc, "Play");
167 act.addArgument("InstanceID", instance_id);
168 act.addArgument("Speed", "1");
169 p_mgr->addAction(act);
171 if (last_reply) {
172 setActiveState(true);
173 local_transport_state = "PLAYING";
174 }
175 return (bool)last_reply;
176 }
177
183 bool play(const char* uri) {
184 if (!setMediaURI(uri)) return false;
185 return play();
186 }
187
192 bool pause() {
193 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
194 if (!svc) return false;
195 ActionRequest act(svc, "Pause");
196 act.addArgument("InstanceID", instance_id);
197 p_mgr->addAction(act);
199 if (last_reply) {
200 setActiveState(false);
201 local_transport_state = "PAUSED_PLAYBACK";
202 }
203 return (bool)last_reply;
204 }
205
210 bool stop() {
211 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
212 if (!svc) return false;
213 ActionRequest act(svc, "Stop");
214 act.addArgument("InstanceID", instance_id);
215 p_mgr->addAction(act);
217 if (last_reply) {
218 setActiveState(false);
219 local_transport_state = "STOPPED";
220 }
221 return (bool)last_reply;
222 }
223
229 bool setVolume(int volumePercent) {
230 if (volumePercent < 0) volumePercent = 0;
231 if (volumePercent > 100) volumePercent = 100;
232 DLNAServiceInfo& svc =
233 selectService("urn:upnp-org:serviceId:RenderingControl");
234 if (!svc) {
235 DlnaLogger.log(DlnaLogLevel::Error, "No RenderingControl service found");
236 return false;
237 }
238 char volBuf[8];
239 snprintf(volBuf, sizeof(volBuf), "%d", volumePercent);
240 ActionRequest act(svc, "SetVolume");
241 act.addArgument("InstanceID", "0");
242 act.addArgument("Channel", "Master");
243 act.addArgument("DesiredVolume", volBuf);
244 p_mgr->addAction(act);
246 if (last_reply) {
247 local_volume = volumePercent;
248 }
249
250 return (bool)last_reply;
251 }
252
258 bool setMute(bool mute) {
259 DLNAServiceInfo& svc =
260 selectService("urn:upnp-org:serviceId:RenderingControl");
261 if (!svc) return false;
262 ActionRequest act(svc, "SetMute");
263 act.addArgument("InstanceID", "0");
264 act.addArgument("Channel", "Master");
265 act.addArgument("DesiredMute", mute ? "1" : "0");
266 p_mgr->addAction(act);
268 if (r) {
269 local_mute = mute;
270 }
271 return (bool)r;
272 }
273
283 int getVolume(bool fromRemote = true) {
284 if (!fromRemote) {
285 return local_volume;
286 }
287 DLNAServiceInfo& svc =
288 selectService("urn:upnp-org:serviceId:RenderingControl");
289 selectService("urn:upnp-org:serviceId:RenderingControl");
290 if (!svc) return -1;
291 ActionRequest act(svc, "GetVolume");
292 act.addArgument("InstanceID", instance_id);
293 p_mgr->addAction(act);
296 if (!last_reply) return -1;
297 const char* v = last_reply.findArgument("CurrentVolume");
298 if (!v) return -1;
299 return atoi(v);
300 }
301
310 int getMute(bool fromRemote = true) {
311 if (!fromRemote) {
312 return local_mute;
313 }
314 DLNAServiceInfo& svc =
315 selectService("urn:upnp-org:serviceId:RenderingControl");
316 if (!svc) return -1;
317 ActionRequest act(svc, "GetMute");
318 act.addArgument("InstanceID", instance_id);
319 act.addArgument("Channel", "Master");
320 p_mgr->addAction(act);
322 if (!last_reply) return -1;
323 const char* m = last_reply.findArgument("CurrentMute");
324 if (!m) return -1;
325 return atoi(m);
326 }
327
332 unsigned long getPositionMs() {
333 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
334 if (!svc) return 0;
335 ActionRequest act(svc, "GetPositionInfo");
336 act.addArgument("InstanceID", instance_id);
337 p_mgr->addAction(act);
339 if (!last_reply) return 0;
340 const char* rel = last_reply.findArgument("RelTime");
341 if (!rel) return 0;
342 return parseTimeToMs(rel);
343 }
344
354 const char* getTransportState(bool fromRemote = true) {
355 if (!fromRemote) {
357 }
358 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
359 if (!svc) return nullptr;
360 ActionRequest act(svc, "GetTransportInfo");
361 act.addArgument("InstanceID", instance_id);
362 p_mgr->addAction(act);
364 if (!last_reply) return nullptr;
365 return last_reply.findArgument("CurrentTransportState");
366 }
367
376 const char* getCurrentURI(bool fromRemote = true) {
377 if (!fromRemote) {
378 return local_url.c_str();
379 }
380 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
381 if (!svc) return nullptr;
382 ActionRequest act(svc, "GetMediaInfo");
383 act.addArgument("InstanceID", instance_id);
384 p_mgr->addAction(act);
386 if (!last_reply) return nullptr;
387 return last_reply.findArgument("CurrentURI");
388 }
389
395 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
396 if (!svc) return -1;
397 ActionRequest act(svc, "GetMediaInfo");
398 act.addArgument("InstanceID", instance_id);
399 p_mgr->addAction(act);
401 if (!last_reply) return -1;
402 const char* n = last_reply.findArgument("NrTracks");
403 if (!n) return -1;
404 return atoi(n);
405 }
406
411 unsigned long getMediaDurationMs() {
412 DLNAServiceInfo& svc =
413 p_mgr->getService("urn:upnp-org:serviceId:AVTransport");
414 if (!svc) return 0;
415 ActionRequest act(svc, "GetMediaInfo");
416 act.addArgument("InstanceID", instance_id);
417 p_mgr->addAction(act);
419 if (!last_reply) return 0;
420 const char* d = last_reply.findArgument("MediaDuration");
421 if (!d) return 0;
422 return parseTimeToMs(d);
423 }
424
430 DLNAServiceInfo& svc =
431 p_mgr->getService("urn:upnp-org:serviceId:AVTransport");
432 if (!svc) return -1;
433 ActionRequest act(svc, "GetPositionInfo");
434 act.addArgument("InstanceID", instance_id);
435 p_mgr->addAction(act);
437 if (!last_reply) return -1;
438 const char* tr = last_reply.findArgument("Track");
439 if (!tr) return -1;
440 return atoi(tr);
441 }
442
447 unsigned long getTrackDurationMs() {
448 DLNAServiceInfo& svc =
449 p_mgr->getService("urn:upnp-org:serviceId:AVTransport");
450 if (!svc) return 0;
451 ActionRequest act(svc, "GetPositionInfo");
452 act.addArgument("InstanceID", instance_id);
453 p_mgr->addAction(act);
455 if (!last_reply) return 0;
456 const char* td = last_reply.findArgument("TrackDuration");
457 if (!td) return 0;
458 return parseTimeToMs(td);
459 }
460
465 const ActionReply& getLastReply() const { return last_reply; }
466
471 bool isActive() { return is_active; }
472
477 void setReference(void* ref) { reference = ref; }
478
484 void onActiveChanged(std::function<void(bool, void*)> cb) {
486 }
487
495 DLNAServiceInfo& svc = selectService("urn:upnp-org:serviceId:AVTransport");
496 if (!svc) {
497 DlnaLogger.log(DlnaLogLevel::Error, "No AVTransport service found");
498 return false;
499 }
500 ActionRequest act(svc, "GetCurrentTransportActions");
501 act.addArgument("InstanceID", instance_id);
502 p_mgr->addAction(act);
504 if (!last_reply) return false;
505 const char* actions = last_reply.findArgument("Actions");
506 if (!actions) return false;
507 out = actions;
508 return true;
509 }
510
519 std::function<void(const char* entry, ProtocolRole role)> cb) {
520 if (!cb) return false;
521
522 DLNAServiceInfo& svc =
523 p_mgr->getService("urn:upnp-org:serviceId:ConnectionManager");
524 if (!svc) {
525 DlnaLogger.log(DlnaLogLevel::Error, "No ConnectionManager service found");
526 return false;
527 }
528
529 // build action and enqueue
530 ActionRequest act(svc, "GetProtocolInfo");
531 p_mgr->addAction(act);
532 bool ok = false;
533
534 // processor will be invoked with the live Client and the ActionReply so
535 // callers can handle the response stream themselves. We simply stream-
536 // parse and forward each parsed entry to the provided callback.
537 XMLCallback processor = [&cb, &ok](Client& client, ActionReply& reply) {
538 ok = XMLProtocolInfoParser::parse(client, cb);
539 reply.clear();
540 reply.setValid(true);
541 };
542
543 // execute the queued action and use our processor to handle the reply
544 p_mgr->executeActions(processor);
545 return ok;
546 }
547
548 protected:
550 bool is_active = false;
551 std::function<void(bool, void*)> activeChangedCallback = nullptr;
553 const char* instance_id = "0";
555 "urn:schemas-upnp-org:device:MediaRenderer:1";
558 void* reference = nullptr;
559 // Optional transport references (must be set before calling begin())
561 IUDPService* p_udp = nullptr;
563 int local_mute = false;
565 const char* local_transport_state = "STOPPED";
566
567 // Helper to update is_active and notify callback only on change
568 void setActiveState(bool s) {
569 if (is_active == s) return;
570 is_active = s;
571 DlnaLogger.log(DlnaLogLevel::Info, "ControlPointMediaRenderer active=%s",
572 is_active ? "true" : "false");
574 }
575
577 static void processNotification(void* reference, const char* sid,
578 const char* varName, const char* newValue) {
579 // Log every notification invocation for debugging/visibility
581 "processNotification sid='%s' var='%s' value='%s'",
582 sid ? sid : "(null)", varName ? varName : "(null)",
583 newValue ? newValue : "(null)");
584
587 if (renderer) {
588 // Handle the notification (e.g., update internal state, call callbacks)
589 if (StrView(varName) == "TransportState") {
590 if (StrView(newValue) == "PLAYING") {
591 renderer->setActiveState(true);
592 } else {
593 renderer->setActiveState(false);
594 }
595 }
596 }
597 }
598
599 // Helper: parse DLNA time string (HH:MM:SS, MM:SS) to milliseconds
600 static unsigned long parseTimeToMs(const char* t) {
601 if (t == nullptr) return 0;
602 int h = 0, m = 0, s = 0;
603 int parts = 0;
604 // Count colons
605 const char* p = t;
606 int colonCount = 0;
607 while (*p) {
608 if (*p == ':') colonCount++;
609 p++;
610 }
611 if (colonCount == 2) {
612 parts = sscanf(t, "%d:%d:%d", &h, &m, &s);
613 } else if (colonCount == 1) {
614 parts = sscanf(t, "%d:%d", &m, &s);
615 h = 0;
616 } else {
617 // maybe it's seconds only
618 parts = sscanf(t, "%d", &s);
619 }
620 if (parts <= 0) return 0;
621 unsigned long total =
622 (unsigned long)h * 3600UL + (unsigned long)m * 60UL + (unsigned long)s;
623 return total * 1000UL;
624 }
625
628 // use selected device
630 if (s) return s;
631
632 // fallback: global search
633 return p_mgr->getService(id);
634 }
635};
636
637} // namespace tiny_dlna
Represents the result of invoking a DLNA service Action.
Definition: Action.h:50
const char * findArgument(const char *name)
Definition: Action.h:72
Represents a request to invoke a remote DLNA service action.
Definition: Action.h:109
void addArgument(Argument arg)
Definition: Action.h:118
DLNA Service: Action Argument.
Definition: Action.h:19
Class to control a MediaRenderer device from a control point.
Definition: DLNAControlPointMediaRenderer.h:24
bool setMute(bool mute)
Set mute state on the renderer.
Definition: DLNAControlPointMediaRenderer.h:258
const ActionReply & getLastReply() const
Return last ActionReply (from the most recent synchronous call)
Definition: DLNAControlPointMediaRenderer.h:465
const char * getCurrentURI(bool fromRemote=true)
Get the current media URI from the renderer.
Definition: DLNAControlPointMediaRenderer.h:376
bool play()
Start playback on the renderer.
Definition: DLNAControlPointMediaRenderer.h:160
int getMute(bool fromRemote=true)
Query mute state.
Definition: DLNAControlPointMediaRenderer.h:310
bool setVolume(int volumePercent)
Set renderer volume.
Definition: DLNAControlPointMediaRenderer.h:229
const char * local_transport_state
Definition: DLNAControlPointMediaRenderer.h:565
int local_mute
Definition: DLNAControlPointMediaRenderer.h:563
DLNAControlPointMediaRenderer(DLNAControlPoint &m, DLNAHttpRequest &http, IUDPService &udp)
Construct helper with references to control point manager and transport instances.
Definition: DLNAControlPointMediaRenderer.h:44
void setDeviceIndex(int idx)
Select the active renderer by index (0-based)
Definition: DLNAControlPointMediaRenderer.h:107
DLNAHttpRequest * p_http
Definition: DLNAControlPointMediaRenderer.h:560
IUDPService * p_udp
Definition: DLNAControlPointMediaRenderer.h:561
int local_volume
Definition: DLNAControlPointMediaRenderer.h:562
void setHttp(DLNAHttpRequest &http)
Setter for the HTTP wrapper used for subscriptions and callbacks.
Definition: DLNAControlPointMediaRenderer.h:110
void(* NotificationCallback)(void *reference, const char *sid, const char *varName, const char *newValue)
Definition: DLNAControlPointMediaRenderer.h:115
void setReference(void *ref)
Attach an opaque reference object that will be passed to callbacks.
Definition: DLNAControlPointMediaRenderer.h:477
bool stop()
Stop playback.
Definition: DLNAControlPointMediaRenderer.h:210
unsigned long getPositionMs()
Query current playback position (RelTime)
Definition: DLNAControlPointMediaRenderer.h:332
bool is_active
Definition: DLNAControlPointMediaRenderer.h:550
int device_index
Definition: DLNAControlPointMediaRenderer.h:552
const char * getTransportState(bool fromRemote=true)
Query transport state (e.g. STOPPED, PLAYING, PAUSED_PLAYBACK).
Definition: DLNAControlPointMediaRenderer.h:354
unsigned long getTrackDurationMs()
Get current track duration in milliseconds (from TrackDuration)
Definition: DLNAControlPointMediaRenderer.h:447
void setSubscribeNotificationsActive(bool flag)
Activate/deactivate subscription notifications.
Definition: DLNAControlPointMediaRenderer.h:120
DLNAServiceInfo & selectService(const char *id)
Select service by id.
Definition: DLNAControlPointMediaRenderer.h:627
void setDeviceTypeFilter(const char *filter)
Restrict this helper to devices of the given device type.
Definition: DLNAControlPointMediaRenderer.h:57
bool pause()
Pause playback.
Definition: DLNAControlPointMediaRenderer.h:192
static void processNotification(void *reference, const char *sid, const char *varName, const char *newValue)
Notification callback.
Definition: DLNAControlPointMediaRenderer.h:577
int getVolume(bool fromRemote=true)
Query the current volume from the renderer.
Definition: DLNAControlPointMediaRenderer.h:283
Str local_url
Definition: DLNAControlPointMediaRenderer.h:564
void * reference
Definition: DLNAControlPointMediaRenderer.h:558
ActionReply last_reply
Definition: DLNAControlPointMediaRenderer.h:557
const char * device_type_filter
Definition: DLNAControlPointMediaRenderer.h:556
static unsigned long parseTimeToMs(const char *t)
Definition: DLNAControlPointMediaRenderer.h:600
bool isActive()
Query if the helper considers the renderer to be actively playing.
Definition: DLNAControlPointMediaRenderer.h:471
int getDeviceCount()
Return the number of discovered devices matching the renderer filter.
Definition: DLNAControlPointMediaRenderer.h:93
bool getCurrentTransportActions(Str &out)
Query the AVTransport service for the current transport actions and return the raw Actions list into ...
Definition: DLNAControlPointMediaRenderer.h:494
bool play(const char *uri)
Set the media URI and start playback.
Definition: DLNAControlPointMediaRenderer.h:183
int getTrackIndex()
Get current track index (from GetPositionInfo)
Definition: DLNAControlPointMediaRenderer.h:429
const char * instance_id
Definition: DLNAControlPointMediaRenderer.h:553
unsigned long getMediaDurationMs()
Get total media duration in milliseconds (from MediaDuration)
Definition: DLNAControlPointMediaRenderer.h:411
void onActiveChanged(std::function< void(bool, void *)> cb)
Register a callback invoked when the active (playing) state changes.
Definition: DLNAControlPointMediaRenderer.h:484
void setActiveState(bool s)
Definition: DLNAControlPointMediaRenderer.h:568
bool getProtocalInfo(std::function< void(const char *entry, ProtocolRole role)> cb)
Fetch protocol-info via SOAP GetProtocolInfo on the ConnectionManager service and invoke cb for each ...
Definition: DLNAControlPointMediaRenderer.h:518
void setInstanceID(const char *id)
Set the InstanceID used for AVTransport and RenderingControl actions.
Definition: DLNAControlPointMediaRenderer.h:128
bool setMediaURI(const char *uri)
Set the media URI on the renderer.
Definition: DLNAControlPointMediaRenderer.h:135
int getNrTracks()
Get number of tracks in the current media.
Definition: DLNAControlPointMediaRenderer.h:394
void setUdp(IUDPService &udp)
Setter for UDP service used for discovery (SSDP)
Definition: DLNAControlPointMediaRenderer.h:113
DLNAControlPoint * p_mgr
Definition: DLNAControlPointMediaRenderer.h:549
std::function< void(bool, void *)> activeChangedCallback
Definition: DLNAControlPointMediaRenderer.h:551
bool begin(uint32_t minWaitMs=3000, uint32_t maxWaitMs=60000)
Begin discovery and processing.
Definition: DLNAControlPointMediaRenderer.h:69
void setDLNAControlPoint(DLNAControlPoint &m)
Set the control point manager instance (required before using helper)
Definition: DLNAControlPointMediaRenderer.h:49
DLNAControlPointMediaRenderer()=default
Default constructor.
const char * device_type_filter_default
Definition: DLNAControlPointMediaRenderer.h:554
Lightweight DLNA control point manager.
Definition: DLNAControlPoint.h:62
ActionRequest & addAction(ActionRequest act)
Registers a method that will be called.
Definition: DLNAControlPoint.h:243
DLNADeviceInfo & getDevice()
Provides the device information of the actually selected device.
Definition: DLNAControlPoint.h:325
Vector< DLNADeviceInfo > & getDevices()
Definition: DLNAControlPoint.h:365
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:126
DLNAServiceInfo & getService(const char *id)
Provide addess to the service information.
Definition: DLNAControlPoint.h:314
void setSubscribeNotificationsActive(bool flag)
Activate/deactivate subscription notifications.
Definition: DLNAControlPoint.h:470
ActionReply & executeActions(XMLCallback xmlProcessor=nullptr)
Executes action and parses the reply xml to collect the reply entries. If an XML processor is provide...
Definition: DLNAControlPoint.h:257
DLNAServiceInfo & getService(const char *id)
Finds a service definition by name.
Definition: DLNADeviceInfo.h:146
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:23
Abstract Interface for UDP API.
Definition: IUDPService.h:33
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:18
String implementation which keeps the data on the heap. We grow the allocated memory only if the copy...
Definition: Str.h:22
const char * c_str() const
Definition: Str.h:186
static bool parse(Stream &in, std::function< void(const char *entry, ProtocolRole role)> cb)
Definition: XMLProtocolInfoParser.h:41
Definition: AllocationTracker.h:9
ProtocolRole
Role to indicate whether a protocolInfo entry is a Source or a Sink.
Definition: DLNACommon.h:26
LoggerClass DlnaLogger
Definition: Logger.cpp:5
std::function< void(Client &client, ActionReply &reply)> XMLCallback
Definition: DLNAControlPoint.h:22