Arduino DLNA Server
Loading...
Searching...
No Matches
ControlPointMediaServer.h
Go to the documentation of this file.
1// Header-only control point helper for MediaServer devices
2#pragma once
3
7
8namespace tiny_dlna {
9
19 public:
20 typedef void (*ItemCallback)(const MediaItem& item, void* ref);
21 typedef void (*NotificationCallback)(void* reference, const char* sid,
22 const char* varName,
23 const char* newValue);
24
31
41 bool begin(DLNAHttpRequest& http, IUDPService& udp, uint32_t minWaitMs = 3000,
42 uint32_t maxWaitMs = 60000) {
44 "ControlPointMediaServer::begin device_type_filter='%s'",
46 return mgr.begin(http, udp, device_type_filter, minWaitMs, maxWaitMs);
47 }
48
54 // count only devices matching the device_type_filter
55 int count = 0;
56 for (auto& d : mgr.getDevices()) {
57 const char* dt = d.getDeviceType();
58 if (!device_type_filter || StrView(dt).contains(device_type_filter))
59 count++;
60 }
61 return count;
62 }
63
68 const ActionReply& getLastReply() const { return last_reply; }
69
74 void setDeviceIndex(int idx) { device_index = idx; }
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
101 void setDeviceTypeFilter(const char* filter) {
103 }
104
109 void setReference(void* ref) { reference = ref; }
110
125 bool browse(int startingIndex, int requestedCount, ItemCallback itemCallback,
126 int& numberReturned, int& totalMatches, int& updateID,
127 const char* browseFlag = nullptr) {
128 // Build and post browse action
129 DLNAServiceInfo& svc =
130 selectService("urn:upnp-org:serviceId:ContentDirectory");
131 if (!svc) return false;
132 ActionRequest &act =
133 createBrowseAction(svc, browseFlag, startingIndex, requestedCount);
134 mgr.addAction(act);
136 if (!last_reply) return false;
137
138 // parse numeric result fields
139 parseNumericFields(last_reply, numberReturned, totalMatches, updateID);
140
141 // process DIDL Result and call callback per item
142 const char* resultXml = findArgument(last_reply, "Result");
143 if (resultXml) processResult(resultXml, itemCallback);
144 return true;
145 }
146
152 DLNAServiceInfo& svc =
153 selectService("urn:upnp-org:serviceId:ContentDirectory");
154 if (!svc) return -1;
155 ActionRequest act(svc, "GetSystemUpdateID");
156 mgr.addAction(act);
158 if (!last_reply) return -1;
159 const char* v = findArgument(last_reply, "Id");
160 if (!v) v = findArgument(last_reply, "Id");
161 return v ? atoi(v) : -1;
162 }
163
168 const char* getSearchCapabilities() {
169 DLNAServiceInfo& svc =
170 selectService("urn:upnp-org:serviceId:ContentDirectory");
171 if (!svc) return nullptr;
172 ActionRequest act(svc, "GetSearchCapabilities");
173 mgr.addAction(act);
175 if (!last_reply) return nullptr;
176 return findArgument(last_reply, "SearchCaps");
177 }
178
183 const char* getSortCapabilities() {
184 DLNAServiceInfo& svc =
185 selectService("urn:upnp-org:serviceId:ContentDirectory");
186 if (!svc) return nullptr;
187 ActionRequest act(svc, "GetSortCapabilities");
188 mgr.addAction(act);
190 if (!last_reply) return nullptr;
191 return findArgument(last_reply, "SortCaps");
192 }
193
198 const char* getProtocolInfo() {
199 DLNAServiceInfo& svc =
200 selectService("urn:upnp-org:serviceId:ConnectionManager");
201 if (!svc) return nullptr;
202 ActionRequest act(svc, "GetProtocolInfo");
203 mgr.addAction(act);
205 if (!last_reply) return nullptr;
206 return findArgument(last_reply, "Source");
207 }
208
213 void setObjectID(const char* id) { object_id = id; }
214
219 const char* getObjectID() const { return object_id; }
220
221 protected:
225 "urn:schemas-upnp-org:device:MediaServer:1";
229 void* reference = nullptr;
230 const char* object_id = "0";
231
233 static void processNotification(void* reference, const char* sid,
234 const char* varName, const char* newValue) {
235 // Log every notification invocation for debugging/visibility
237 "processNotification sid='%s' var='%s' value='%s'",
238 sid ? sid : "(null)", varName ? varName : "(null)",
239 newValue ? newValue : "(null)");
240 }
241
244 // Build list of devices matching the optional filter
246 int idx = 0;
247 for (auto& d : all) {
248 const char* dt = d.getDeviceType();
249 if (device_type_filter && !StrView(dt).contains(device_type_filter))
250 continue;
251 if (idx == device_index) {
252 DLNAServiceInfo& s = d.getService(id);
253 if (s) return s;
254 }
255 idx++;
256 }
257
258 // fallback: global search
259 return mgr.getService(id);
260 }
261
262 const char* findArgument(ActionReply& r, const char* name) {
263 for (auto& a : r.arguments) {
264 if (a.name != nullptr) {
265 StrView nm(a.name);
266 if (nm == name) return a.value.c_str();
267 }
268 }
269 return nullptr;
270 }
271
273 ActionRequest &createBrowseAction(DLNAServiceInfo& svc, const char* browseFlag,
274 int startingIndex, int requestedCount) {
275 static ActionRequest act(svc, "Browse");
276 // Use the canonical argument name expected by ContentDirectory: "ObjectID"
277 act.addArgument("ObjectID", object_id);
278 act.addArgument("BrowseFlag",
279 browseFlag ? browseFlag : "BrowseDirectChildren");
280 act.addArgument("Filter", "");
281 char buf[32];
282 snprintf(buf, sizeof(buf), "%d", startingIndex);
283 act.addArgument("StartingIndex", buf);
284 snprintf(buf, sizeof(buf), "%d", requestedCount);
285 act.addArgument("RequestedCount", buf);
286 act.addArgument("SortCriteria", "");
287 return act;
288 }
289
291 void parseNumericFields(ActionReply& reply, int& numberReturned,
292 int& totalMatches, int& updateID) {
293 const char* nret = findArgument(reply, "NumberReturned");
294 const char* tmatch = findArgument(reply, "TotalMatches");
295 const char* uid = findArgument(reply, "UpdateID");
296 numberReturned = nret ? atoi(nret) : 0;
297 totalMatches = tmatch ? atoi(tmatch) : 0;
298 updateID = uid ? atoi(uid) : 0;
299 }
300
302 void processResult(const char* resultXml, ItemCallback itemCallback) {
303 if (!resultXml) return;
304 StrView res(resultXml);
305 int pos = 0;
306 while (true) {
307 // find next <item or <container element
308 int itPos1 = res.indexOf("<item", pos);
309 int itPos2 = res.indexOf("<container", pos);
310 int itPos = -1;
311 bool isContainer = false;
312 if (itPos1 >= 0 && (itPos2 < 0 || itPos1 < itPos2)) {
313 itPos = itPos1;
314 isContainer = false;
315 } else if (itPos2 >= 0) {
316 itPos = itPos2;
317 isContainer = true;
318 }
319 if (itPos < 0) break;
320 int itEnd = isContainer ? res.indexOf("</container>", itPos)
321 : res.indexOf("</item>", itPos);
322 if (itEnd < 0) break;
323 int headerEnd = res.indexOf('>', itPos);
324 if (headerEnd < 0 || headerEnd >= itEnd) break;
325
327 // extract id attribute
328 int idPos = res.indexOf("id=\"", itPos);
329 if (idPos >= 0 && idPos < headerEnd) {
330 int valStart = idPos + 4;
331 int valEnd = res.indexOf('"', valStart);
332 if (valEnd > valStart) {
333 Str tmp;
334 tmp.copyFrom(res.c_str() + valStart, valEnd - valStart);
335 item.id = strings.add((char*)tmp.c_str());
336 }
337 }
338
339 // title
340 int t1 = res.indexOf("<dc:title>", headerEnd);
341 if (t1 >= 0 && t1 < itEnd) {
342 int t1s = t1 + strlen("<dc:title>");
343 int t1e = res.indexOf("</dc:title>", t1s);
344 if (t1e > t1s) {
345 Str tmp;
346 tmp.copyFrom(res.c_str() + t1s, t1e - t1s);
347 item.title = strings.add((char*)tmp.c_str());
348 }
349 }
350
351 // res
352 int r1 = res.indexOf("<res", headerEnd);
353 if (r1 >= 0 && r1 < itEnd) {
354 int r1s = res.indexOf('>', r1);
355 if (r1s >= 0 && r1s < itEnd) {
356 r1s += 1;
357 int r1e = res.indexOf("</res>", r1s);
358 if (r1e > r1s) {
359 Str tmp;
360 tmp.copyFrom(res.c_str() + r1s, r1e - r1s);
361 item.res = strings.add((char*)tmp.c_str());
362 }
363 }
364 }
365
366 if (itemCallback) itemCallback(item, reference);
367 pos = itEnd + 7;
368 }
369 }
370};
371
372} // 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/query a MediaServer from a control point.
Definition: ControlPointMediaServer.h:18
const char * getSortCapabilities()
Get the ContentDirectory SortCapabilities string.
Definition: ControlPointMediaServer.h:183
ActionRequest & createBrowseAction(DLNAServiceInfo &svc, const char *browseFlag, int startingIndex, int requestedCount)
Build a Browse ActionRequest.
Definition: ControlPointMediaServer.h:273
void setDeviceTypeFilter(const char *filter)
Restrict this helper to devices of the given device type.
Definition: ControlPointMediaServer.h:101
void(* ItemCallback)(const MediaItem &item, void *ref)
Definition: ControlPointMediaServer.h:20
void(* NotificationCallback)(void *reference, const char *sid, const char *varName, const char *newValue)
Definition: ControlPointMediaServer.h:21
int getSystemUpdateID()
Get current SystemUpdateID for the ContentDirectory.
Definition: ControlPointMediaServer.h:151
void setDeviceIndex(int idx)
Select a device by index (0-based) for subsequent actions.
Definition: ControlPointMediaServer.h:74
DLNAServiceInfo & selectService(const char *id)
Select service by id.
Definition: ControlPointMediaServer.h:243
ActionReply last_reply
Definition: ControlPointMediaServer.h:227
void setObjectID(const char *id)
Set the object id used for browse operations (default: "0")
Definition: ControlPointMediaServer.h:213
const char * findArgument(ActionReply &r, const char *name)
Definition: ControlPointMediaServer.h:262
void * reference
Definition: ControlPointMediaServer.h:229
const char * getProtocolInfo()
Query ConnectionManager:GetProtocolInfo.
Definition: ControlPointMediaServer.h:198
void parseNumericFields(ActionReply &reply, int &numberReturned, int &totalMatches, int &updateID)
Parse numeric result fields from an ActionReply.
Definition: ControlPointMediaServer.h:291
const char * device_type_filter_default
Definition: ControlPointMediaServer.h:224
bool subscribeNotifications(int timeoutSeconds=60, NotificationCallback cb=nullptr)
Subscribe to event notifications for the selected server/device.
Definition: ControlPointMediaServer.h:83
int device_index
Definition: ControlPointMediaServer.h:223
ControlPointMediaServer(DLNAControlPoint &mgr)
Construct helper with reference to control point manager.
Definition: ControlPointMediaServer.h:30
int getDeviceCount()
Return number of discovered devices known to the control point.
Definition: ControlPointMediaServer.h:53
bool begin(DLNAHttpRequest &http, IUDPService &udp, uint32_t minWaitMs=3000, uint32_t maxWaitMs=60000)
Begin discovery and processing (forwards to underlying control point)
Definition: ControlPointMediaServer.h:41
bool browse(int startingIndex, int requestedCount, ItemCallback itemCallback, int &numberReturned, int &totalMatches, int &updateID, const char *browseFlag=nullptr)
Browse the given object_id and invoke callback for each returned item.
Definition: ControlPointMediaServer.h:125
void setReference(void *ref)
Attach an opaque reference pointer passed to callbacks.
Definition: ControlPointMediaServer.h:109
const char * device_type_filter
Definition: ControlPointMediaServer.h:226
const char * object_id
Definition: ControlPointMediaServer.h:230
DLNAControlPoint & mgr
Definition: ControlPointMediaServer.h:222
const char * getSearchCapabilities()
Get the ContentDirectory SearchCapabilities string.
Definition: ControlPointMediaServer.h:168
const ActionReply & getLastReply() const
Return the ActionReply from the last synchronous request.
Definition: ControlPointMediaServer.h:68
const char * getObjectID() const
Return the current object id used for browse.
Definition: ControlPointMediaServer.h:219
StringRegistry strings
Definition: ControlPointMediaServer.h:228
void processResult(const char *resultXml, ItemCallback itemCallback)
Parse DIDL-Lite Result and invoke callback for each item.
Definition: ControlPointMediaServer.h:302
static void processNotification(void *reference, const char *sid, const char *varName, const char *newValue)
Notification callback: just log for now.
Definition: ControlPointMediaServer.h:233
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
virtual const char * c_str()
provides the string value as const char*
Definition: StrView.h:376
virtual int indexOf(const char c, int start=0)
Definition: StrView.h:275
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()
provides the string value as const char*
Definition: Str.h:187
void copyFrom(const char *source, int len, int maxlen=0)
assigns a memory buffer
Definition: Str.h:93
Make sure that a string is stored only once.
Definition: StringRegistry.h:9
const char * add(char *in)
adds a string
Definition: StringRegistry.h:12
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
Media item description used to build DIDL-Lite entries.
Definition: MediaItem.h:6
const char * res
Definition: MediaItem.h:11
const char * id
Definition: MediaItem.h:7
const char * title
Definition: MediaItem.h:10