Arduino DLNA Server
Loading...
Searching...
No Matches
DLNAMediaServer.h
Go to the documentation of this file.
1// Minimal header-only Digital Media Server (MediaServer) device
2#pragma once
3
5#include "MediaItem.h"
7#include "basic/Printf.h"
8#include "basic/Str.h"
14#include "dlna/xml/XMLPrinter.h"
15#include "http/Http.h"
17
18namespace tiny_dlna {
19
45template <typename ClientType>
47 public:
48 using HttpClient = ClientType;
52 typedef void (*PrepareDataCallback)(
53 const char* objectID, ContentQueryType queryType, const char* filter,
54 int startingIndex, int requestedCount, const char* sortCriteria,
55 int& numberReturned, int& totalMatches, int& updateID, void* reference);
56
58 typedef bool (*GetDataCallback)(int index, MediaItem& item, void* reference);
61 typedef size_t (*GetDataCallbackPrint)(int index, Print& out,
62 void* reference);
63
66 setUDN(usn);
67 setSerialNumber("1.0");
69 setFriendlyName("ArduinoMediaServer");
70 setManufacturer("TinyDLNA");
71 setManufacturerURL("https://github.com/pschatzmann/arduino-dlna");
72 setModelName("TinyDLNA MediaServer");
73 setModelNumber("1.0");
74 setBaseURL("http://localhost:44757");
75 setupRules();
76 }
79 // use setters so derived classes or overrides get a consistent path
80 setHttpServer(server);
81 setUdpService(udp);
83 }
84
87
89 void setHttpServer(IHttpServer& server) {
90 p_server = &server;
91 setupServicesImpl(&server);
92 }
93
95 void setUdpService(IUDPService& udp) { p_udp_member = &udp; }
96
100 bool begin() {
101 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::begin");
102 return dlna_device.begin(*this, *p_udp_member, *p_server);
103 }
104
106 void end() {
107 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::end");
109 }
110
112 bool loop(int loopAction = RUN_ALL) { return dlna_device.loop(loopAction); }
113
115 void setSearchCapabilities(const char* caps) { g_search_capabiities = caps; }
116
119
121 void setSortCapabilities(const char* caps) { g_sort_capabilities = caps; }
122
124 const char* getSortCapabilities() { return g_sort_capabilities; }
125
127 void setConnectionID(const char* id) { connectionID = id; }
128
130 const char* getConnectionID() { return connectionID; }
131
137 void setProtocols(const char* source, const char* sink = "") {
138 sourceProto = source;
139 sinkProto = sink;
140 // publish protocol info change to ConnectionManager subscribers
141 // Build writer that will use live state from the device at publish time
142 auto writer = [](Print& out, void* ref) -> size_t {
143 auto self = (DLNAMediaServer*)ref;
144 size_t result = 0;
145 result += out.print("<SourceProtocolInfo val=\"");
146 result += out.print(StrView(self->getSourceProtocols()).c_str());
147 result += out.println("\"/>");
148 result += out.print("<SinkProtocolInfo val=\"");
149 result += out.print(StrView(self->getSinkProtocols()).c_str());
150 result += out.println("\"/>");
151 return result;
152 };
153 addChange("CMS", writer);
154 }
155
157 const char* getSourceProtocols() { return sourceProto; }
158
160 const char* getSinkProtocols() { return sinkProto; }
161
164
169
171 void setReference(void* ref) { reference_ = ref; }
172
175
178
182
185
187 void setSubscriptionsActive(bool flag) {
189 }
190
193
195 void setCustomActionRule(const char* suffix,
197 for (size_t i = 0; i < rules.size(); ++i) {
198 if (StrView(rules[i].suffix).equals(suffix)) {
199 rules[i].handler = handler;
200 return;
201 }
202 }
203 rules.push_back({suffix, handler});
204 }
205
208
211
214
217
219 void logStatus() { return dlna_device.logStatus(); }
220
221 protected:
223 struct ActionRule {
224 const char* suffix;
226 };
227
234 void* reference_ = nullptr;
240 void* g_stream_reference = nullptr;
241 const char* st = "urn:schemas-upnp-org:device:MediaServer:1";
242 const char* usn = "uuid:media-server-0000-0000-0000-000000000001";
244 "dc:title,dc:creator,upnp:class,upnp:genre,"
245 "upnp:album,upnp:artist,upnp:albumArtURI";
247 "dc:title,dc:date,upnp:class,upnp:album,upnp:episodeNumber,upnp:"
248 "originalTrackNumber";
249 // Default protocol info values (use DLNA_PROTOCOL by default)
251 const char* sinkProto = "";
252 const char* connectionID = "0";
254 // Per-instance descriptor objects (default-initialized). These allow callers
255 // to customize the SCPD delivered for this MediaServer instance.
260
263 void addChange(const char* serviceAbbrev,
264 std::function<size_t(Print&, void*)> changeWriter) {
265 dlna_device.addChange(serviceAbbrev, changeWriter, this);
266 }
267
269 void publishAVT() {
270 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::publishAVT");
271 // Publish SystemUpdateID using a writer that reads the current value
272 auto writer = [](Print& out, void* ref) -> size_t {
273 auto self = (DLNAMediaServer*)ref;
274 size_t result = 0;
275 result += out.print("<SystemUpdateID val=\"");
276 result += out.print(self->g_stream_updateID);
277 result += out.println("\"/>");
278 return result;
279 };
280 addChange("AVT", writer);
281 }
282
284 void publishCMS() {
285 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::publishCMS");
286 // Publish current ConnectionManager properties using a writer that
287 // reads live state from the device
288 auto writer = [](Print& out, void* ref) -> size_t {
289 auto self = (DLNAMediaServer*)ref;
290 size_t result = 0;
291 result += out.print("<SourceProtocolInfo val=\"");
292 result += out.print(StrView(self->getSourceProtocols()).c_str());
293 result += out.println("\"/>");
294 result += out.print("<SinkProtocolInfo val=\"");
295 result += out.print(StrView(self->getSinkProtocols()).c_str());
296 result += out.println("\"/>");
297 result += out.print("<CurrentConnectionIDs val=\"");
298 result += out.print(StrView(self->connectionID).c_str());
299 result += out.println("\"/>");
300 return result;
301 };
302 addChange("CMS", writer);
303 }
304
307 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::setupServices");
308
309 // register the individual services via helpers
312 }
313
315 static void contentDescCB(IClientHandler& client, IHttpServer* server, const char* requestPath,
317 DLNAMediaServer* self = getMediaServer(server);
318 assert(self != nullptr);
319 // Non-capturing lambda matches function-pointer signature and can be
320 // passed directly to reply (avoids extra static helpers).
321 client.reply(
322 "text/xml",
323 [](Print& out, void* ref) -> size_t {
324 size_t result = 0;
325 if (ref) {
326 auto self = (DLNAMediaServer*)ref;
327 result += self->getContentDirectoryDescr().printDescr(out);
328 } else {
330 result += descr.printDescr(out);
331 }
332 return result;
333 },
334 200, nullptr, self);
335 }
336
338 static void connDescCB(IClientHandler& client, IHttpServer* server, const char* requestPath,
340 DLNAMediaServer* self = getMediaServer(server);
341
342 client.reply(
343 "text/xml",
344 [](Print& out, void* ref) -> size_t {
345 size_t result = 0;
346 if (ref) {
347 auto self = (DLNAMediaServer*)ref;
348 result += self->getConnectionMgrDescr().printDescr(out);
349 } else {
351 result += descr.printDescr(out);
352 }
353 return result;
354 },
355 200, nullptr, self);
356 }
357
358 // (Removed static helper functions — lambdas are used inline at callsite.)
359
362 const char* requestPath,
364 bool is_subscribe = false;
365 DeviceType::handleSubscription(server, &client, requestPath, hl, is_subscribe);
366 if (is_subscribe) {
367 DLNAMediaServer* self = getMediaServer(server);
368 assert(self != nullptr);
369 StrView request_path_str(requestPath);
370 if (request_path_str.contains("/CD/"))
371 self->publishAVT();
372 else if (request_path_str.contains("/CM/"))
373 self->publishCMS();
374 else
375 DlnaLogger.log(DlnaLogLevel::Warning,
376 "eventSubscriptionHandler: Unknown request path: %s",
377 requestPath);
378 }
379 }
380
384 cd.setup("urn:schemas-upnp-org:service:ContentDirectory:1",
385 "urn:upnp-org:serviceId:ContentDirectory", "/CD/service.xml",
386 &DLNAMediaServer::contentDescCB, "/CD/control",
388 [](IClientHandler& client, IHttpServer*, const char*, HttpRequestHandlerLine*) { client.replyOK(); });
389
390 // subscription namespace abbreviation used for event publishing
392
393 addService(cd);
394 }
395
399 cm.setup("urn:schemas-upnp-org:service:ConnectionManager:1",
400 "urn:upnp-org:serviceId:ConnectionManager", "/CM/service.xml",
401 &DLNAMediaServer::connDescCB, "/CM/control",
404
405 // subscription namespace abbreviation used for event publishing
407
408 addService(cm);
409 }
410
412 bool processAction(ActionRequest& action, IHttpServer& server, IClientHandler& client) {
413 DlnaLogger.log(DlnaLogLevel::Info, "DLNAMediaServer::processAction: %s",
414 action.getAction());
415 auto& action_str = action.getActionStr();
416 if (action_str.isEmpty()) {
417 DlnaLogger.log(DlnaLogLevel::Error, "Empty action received");
418 client.replyNotFound();
419 return false;
420 }
421 for (const auto& rule : rules) {
422 if (action_str.endsWith(rule.suffix)) {
423 return rule.handler(client, this, action, server);
424 }
425 }
426 DlnaLogger.log(DlnaLogLevel::Error, "Unsupported action: %s",
427 action.getAction());
428 client.replyNotFound();
429 return false;
430 }
431
434 DlnaLogger.log(DlnaLogLevel::Info, "processActionBrowse");
435 int numberReturned = 0;
436 int totalMatches = 0;
437 int updateID = g_stream_updateID;
438
439 // extract paging args
440 int startingIndex = action.getArgumentIntValue("StartingIndex");
441 int requestedCount = action.getArgumentIntValue("RequestedCount");
442
443 // determine query type (BrowseDirectChildren / BrowseMetadata / Search)
444 ContentQueryType qtype =
445 parseContentQueryType(action.getArgumentValue("BrowseFlag"));
446 // query for data
447 if (prepare_data_cb) {
448 prepare_data_cb(action.getArgumentValue("ObjectID"), qtype,
449 action.getArgumentValue("Filter"), startingIndex,
450 requestedCount, action.getArgumentValue("SortCriteria"),
451 numberReturned, totalMatches, updateID, reference_);
452 }
453
454 // Store streaming state and reply using callback writer
455 g_stream_numberReturned = numberReturned;
456 g_stream_totalMatches = totalMatches;
457 g_stream_updateID = updateID;
458 g_stream_startingIndex = startingIndex;
459
460 client.reply(
461 "text/xml",
462 [](Print& out, void* ref) -> size_t {
463 auto self = (DLNAMediaServer*)ref;
464 return self->streamActionItems(out, "BrowseResponse",
465 self->g_stream_startingIndex);
466 },
467 200, nullptr, this);
468 return true;
469 }
470
473 DlnaLogger.log(DlnaLogLevel::Info, "processActionSearch");
474 int numberReturned = 0;
475 int totalMatches = 0;
476 int updateID = g_stream_updateID;
477
478 // extract paging args
479 int startingIndex = action.getArgumentIntValue("StartingIndex");
480 int requestedCount = action.getArgumentIntValue("RequestedCount");
481
482 // For Search actions we always use the Search query type regardless of
483 // any provided BrowseFlag; this ensures Search semantics are preserved.
485 // query for data
486 if (prepare_data_cb) {
487 prepare_data_cb(action.getArgumentValue("ContainerID"), qtype,
488 action.getArgumentValue("Filter"), startingIndex,
489 requestedCount, action.getArgumentValue("SortCriteria"),
490 numberReturned, totalMatches, updateID, reference_);
491 }
492
493 // Store streaming state and reply using callback writer
494 g_stream_numberReturned = numberReturned;
495 g_stream_totalMatches = totalMatches;
496 g_stream_updateID = updateID;
497 g_stream_startingIndex = startingIndex;
498
499 client.reply(
500 "text/xml",
501 [](Print& out, void* ref) -> size_t {
502 auto self = (DLNAMediaServer*)ref;
503 return self->streamActionItems(out, "SearchResponse",
504 self->g_stream_startingIndex);
505 },
506 200, nullptr, this);
507 return true;
508 }
509
512 IHttpServer& server, IClientHandler& client) {
513 client.reply(
514 "text/xml",
515 [](Print& out, void* ref) -> size_t {
516 auto self = (DLNAMediaServer*)ref;
517 size_t written = 0;
518 written += self->soapEnvelopeStart(out);
519 written += self->actionResponseStart(
520 out, "GetSearchCapabilitiesResponse",
521 "urn:schemas-upnp-org:service:ContentDirectory:1");
522 written += out.print("<SearchCaps>");
523 written += out.print(StrView(self->g_search_capabiities).c_str());
524 written += out.print("</SearchCaps>\r\n");
525 written +=
526 self->actionResponseEnd(out, "GetSearchCapabilitiesResponse");
527 written += self->soapEnvelopeEnd(out);
528 return written;
529 },
530 200, nullptr, this);
531 DlnaLogger.log(DlnaLogLevel::Info,
532 "processActionGetSearchCapabilities (callback)");
533 return true;
534 }
535
538 IHttpServer& server, IClientHandler& client) {
539 client.reply(
540 "text/xml",
541 [](Print& out, void* ref) -> size_t {
542 auto self = (DLNAMediaServer*)ref;
543 size_t written = 0;
544 written += self->soapEnvelopeStart(out);
545 written += self->actionResponseStart(
546 out, "GetSortCapabilitiesResponse",
547 "urn:schemas-upnp-org:service:ContentDirectory:1");
548 written += out.print("<SortCaps>");
549 written += out.print(StrView(self->g_sort_capabilities).c_str());
550 written += out.print("</SortCaps>\r\n");
551 written +=
552 self->actionResponseEnd(out, "GetSortCapabilitiesResponse");
553 written += self->soapEnvelopeEnd(out);
554 return written;
555 },
556 200, nullptr, this);
557 DlnaLogger.log(DlnaLogLevel::Info,
558 "processActionGetSortCapabilities (callback)");
559 return true;
560 }
561
564 IHttpServer& server, IClientHandler& client) {
565 client.reply(
566 "text/xml",
567 [](Print& out, void* ref) -> size_t {
568 auto self = (DLNAMediaServer*)ref;
569 size_t written = 0;
570 written += self->soapEnvelopeStart(out);
571 written += self->actionResponseStart(
572 out, "GetSystemUpdateIDResponse",
573 "urn:schemas-upnp-org:service:ContentDirectory:1");
574 Printf pr{out};
575 written += static_cast<size_t>(
576 pr.printf("<Id>%d</Id>\r\n", self->g_stream_updateID));
577 written += self->actionResponseEnd(out, "GetSystemUpdateIDResponse");
578 written += self->soapEnvelopeEnd(out);
579 return written;
580 },
581 200, nullptr, this);
582 DlnaLogger.log(DlnaLogLevel::Info,
583 "processActionGetSystemUpdateID (callback): %d",
585 return true;
586 }
587
590 IHttpServer& server, IClientHandler& client) {
591 // Stream reply directly using a writer callback to avoid temporary buffers
592 client.reply(
593 "text/xml",
594 [](Print& out, void* ref) -> size_t {
595 auto self = static_cast<DLNAMediaServer*>(ref);
596 return DeviceType::replyGetProtocolInfo(out, self->sourceProto,
597 self->sinkProto);
598 },
599 200, nullptr, this);
600 return true;
601 }
602
605 IHttpServer& server, IClientHandler& client) {
606 // Stream reply directly using a writer callback
607 client.reply(
608 "text/xml",
609 [](Print& out, void* ref) -> size_t {
610 auto self = static_cast<DLNAMediaServer*>(ref);
612 self->connectionID);
613 },
614 200, nullptr, this);
615 return true;
616 }
619 IHttpServer& server, IClientHandler& client) {
620 // Read requested ConnectionID (not used in this simple implementation)
621 int connId = action.getArgumentIntValue("ConnectionID");
622
623 // Stream the GetCurrentConnectionInfo response using chunked encoding
624 // to avoid building the full response in memory and to remain
625 // consistent with other handlers.
626 client.reply(
627 "text/xml",
628 [](Print& out, void* ref) -> size_t {
629 auto self = static_cast<DLNAMediaServer*>(ref);
631 out, self->sourceProto, self->connectionID, "Output");
632 },
633 200, nullptr, this);
634 return true;
635 }
636
639 StrView fv(flag);
640 if (fv.equals("BrowseDirectChildren"))
642 if (fv.equals("BrowseMetadata")) return ContentQueryType::BrowseMetadata;
643 // fallback
645 }
646
647 size_t soapEnvelopeStart(Print& out) {
648 size_t written = 0;
649 written += out.print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n");
650 written += out.print(
651 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" ");
652 written += out.print(
653 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n");
654 written += out.print("<s:Body>\r\n");
655 return written;
656 }
657
658 size_t soapEnvelopeEnd(Print& out) {
659 size_t written = 0;
660 written += out.print("</s:Body>\r\n");
661 written += out.print("</s:Envelope>\r\n");
662 return written;
663 }
664
665 size_t actionResponseStart(Print& out, const char* responseName,
666 const char* serviceNS) {
667 // writes: <u:ResponseName xmlns:u="serviceNS">
668 Printf pr{out};
669 return static_cast<size_t>(
670 pr.printf("<u:%s xmlns:u=\"%s\">\r\n", responseName, serviceNS));
671 }
672
673 size_t actionResponseEnd(Print& out, const char* responseName) {
674 Printf pr{out};
675 return static_cast<size_t>(pr.printf("</u:%s>\r\n", responseName));
676 }
677
678 size_t streamActionItems(Print& out, const char* responseName,
679 int startingIndex) {
680 // SOAP envelope start and response wrapper
681 size_t written = 0;
682 written += soapEnvelopeStart(out);
683 written += actionResponseStart(
684 out, responseName, "urn:schemas-upnp-org:service:ContentDirectory:1");
685
686 // Result -> DIDL-Lite
687 written += out.println("<Result>");
688 // DIDL root with namespaces
689 written += streamDIDL(out, g_stream_numberReturned, startingIndex);
690 written += out.print("</Result>\r\n");
691
692 // numeric fields
693 Printf pr{out};
694 written += static_cast<size_t>(pr.printf(
695 "<NumberReturned>%d</NumberReturned>\r\n", g_stream_numberReturned));
696 written += static_cast<size_t>(pr.printf(
697 "<TotalMatches>%d</TotalMatches>\r\n", g_stream_totalMatches));
698 written += static_cast<size_t>(
699 pr.printf("<UpdateID>%d</UpdateID>\r\n", g_stream_updateID));
700
701 written += actionResponseEnd(out, responseName);
702 written += soapEnvelopeEnd(out);
703 return written;
704 }
705
707 size_t streamDIDL(Print& out, int numberReturned, int startingIndex) {
708 size_t written = 0;
709 // Stream DIDL-Lite Result payload using helper
710 written += out.print(
711 "&lt;DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
712 "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
713 "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"&gt;\r\n");
714
715 written += streamDIDLItems(out, numberReturned, startingIndex);
716
717 written += out.print("&lt;/DIDL-Lite&gt;\r\n");
718 return written;
719 }
720
721 // Streams each MediaItem entry (container/item with title,
722 // class and optional resource). Returns bytes written.
723 // Single-pass loop that prefers get_data_print_cb and falls back
724 // to get_data_cb for any item where print produced 0 bytes.
725 size_t streamDIDLItems(Print& out, int numberReturned, int startingIndex) {
726 if (get_data_print_cb == nullptr && get_data_cb == nullptr) {
727 // no data callback defined
728 return 0;
729 }
730 EscapingPrint esc_out(out);
731 size_t total = 0;
732 for (int i = 0; i < numberReturned; ++i) {
733 int idx = startingIndex + i;
734
735 // 1) Try print callback if present
736 if (get_data_print_cb) {
737 size_t w = get_data_print_cb(idx, esc_out, reference_);
738 if (w > 0) {
739 total += w;
740 continue; // done with this item
741 }
742 }
743
744 // 2) Structured callback path
745 if (get_data_cb) {
746 MediaItem item;
747 if (!get_data_cb(idx, item, reference_)) break; // end-of-list
748 total += streamDIDLItem(esc_out, item);
749 continue;
750 }
751 }
752 return total;
753 }
754
755 // Print a single MediaItem as escaped DIDL (returns bytes written)
756 size_t streamDIDLItem(Print& out, const MediaItem& item) {
757 size_t written = 0;
758 const char* mediaItemClassStr = toStr(item.itemClass);
759 const char* nodeName =
760 (item.itemClass == MediaItemClass::Folder) ? "container" : "item";
761 Printf pr{out};
762 written += static_cast<size_t>(
763 pr.printf("<%s id=\"%s\" parentID=\"%s\" restricted=\"%d\">", nodeName,
764 item.id ? item.id : "", item.parentID ? item.parentID : "0",
765 item.restricted ? 1 : 0));
766
767 written += out.print("<dc:title>");
768 written += out.print(StrView(item.title).c_str());
769 written += out.print("</dc:title>\r\n");
770 if (mediaItemClassStr != nullptr) {
771 written += static_cast<size_t>(
772 pr.printf("<upnp:class>%s</upnp:class>\r\n", mediaItemClassStr));
773 }
774
775 // Optional album art URI
776 if (!StrView(item.albumArtURI).isEmpty()) {
778 written += out.print("<upnp:albumArtURI>");
779 written += out.print(url_str.c_str());
780 written += out.print("</upnp:albumArtURI>\r\n");
781 }
782
783 // res with optional protocolInfo attribute
784 if (!StrView(item.resourceURI).isEmpty()) {
785 if (!StrView(item.mimeType).isEmpty()) {
786 written += static_cast<size_t>(
787 pr.printf("<res protocolInfo=\"http-get:*:%s:*\">", item.mimeType));
788 } else {
789 written += out.print("<res>");
790 }
792 written += out.print(url_str.c_str());
793 written += out.print("</res>\r\n");
794 }
795
796 written += static_cast<size_t>(pr.printf("</%s>\r\n", nodeName));
797 return written;
798 }
799
801 Str getUri(const char* path) {
802 Str url{256};
803 // We got a valid url
804 url = path;
805 if (url.startsWith("http://")) {
806 return url;
807 }
808 // We need to add the path to the base url
809 url = getBaseURL();
810 if (!StrView(path).startsWith("/")) {
811 url += "/";
812 }
813 url += path;
814 return url;
815 }
816
818 const char* toStr(MediaItemClass itemClass) {
819 switch (itemClass) {
821 return "object.item.audioItem.musicTrack";
823 return "object.item.audioItem.audioBroadcast";
825 return "object.item.videoItem.movie";
827 return "object.item.imageItem.photo";
829 return "object.container";
830 default:
831 return nullptr;
832 }
833 }
834
837 const char* requestPath,
839 ActionRequest action;
840 DeviceType::parseActionRequest(server, client, requestPath, hl, action);
841
842 // If a user-provided callback is registered, hand over the parsed
843 // ActionRequest for custom handling. The callback is responsible for
844 // writing the HTTP reply if it handles the action.
845 DLNAMediaServer* ms = getMediaServer(server);
846
847 // process the requested action using instance method if available
848 if (ms) {
849 if (ms->processAction(action, *server, client)) return;
850 }
851 client.replyNotFound();
852 }
853
865 if (!server) return nullptr;
866 auto* dev = static_cast<IDevice*>(server->getReference());
867 if (!dev) return nullptr;
868 return static_cast<DLNAMediaServer*>(dev->getReference());
869 }
870
872 static void connmgrControlCB(IClientHandler& client, IHttpServer* server, const char* requestPath,
874 ActionRequest action;
875 DeviceType::parseActionRequest(server, client, requestPath, hl, action);
876
877 DLNAMediaServer* ms = getMediaServer(server);
878 if (!ms) {
879 client.replyNotFound();
880 return;
881 }
882 // Use rules-based dispatch for ConnectionManager actions
883 if (ms->processAction(action, *server, client)) return;
884 client.replyNotFound();
885 }
886
889 void setupRules() {
890 // ContentDirectory rules
891 rules.push_back({"Browse", [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action,
892 IHttpServer& server) {
893 return self->processActionBrowse(action, server, client);
894 }});
895 rules.push_back({"Search", [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action,
896 IHttpServer& server) {
897 return self->processActionSearch(action, server, client);
898 }});
899 rules.push_back(
900 {"GetSearchCapabilities",
901 [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action, IHttpServer& server) {
902 return self->processActionGetSearchCapabilities(action, server, client);
903 }});
904 rules.push_back(
905 {"GetSortCapabilities",
906 [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action, IHttpServer& server) {
907 return self->processActionGetSortCapabilities(action, server, client);
908 }});
909 rules.push_back(
910 {"GetSystemUpdateID",
911 [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action, IHttpServer& server) {
912 return self->processActionGetSystemUpdateID(action, server, client);
913 }});
914 // ConnectionManager rules
915 rules.push_back(
916 {"GetProtocolInfo",
917 [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action, IHttpServer& server) {
918 return self->processActionGetProtocolInfo(action, server, client);
919 }});
920 rules.push_back(
921 {"GetCurrentConnectionIDs",
922 [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action, IHttpServer& server) {
923 return self->processActionGetCurrentConnectionIDs(action, server, client);
924 }});
925 rules.push_back(
926 {"GetCurrentConnectionInfo",
927 [](IClientHandler& client, DLNAMediaServer* self, ActionRequest& action, IHttpServer& server) {
928 return self->processActionGetCurrentConnectionInfo(action, server, client);
929 }});
930 }
931};
932
933} // namespace tiny_dlna
Represents a request to invoke a remote DLNA service action.
Definition: Action.h:104
Str & getActionStr()
Definition: Action.h:159
const char * getArgumentValue(const char *name)
Definition: Action.h:128
int getArgumentIntValue(const char *name)
Definition: Action.h:146
const char * getAction()
Definition: Action.h:157
Abstract DLNA Descriptor Generation.
Definition: DLNADescr.h:9
virtual size_t printDescr(Print &out)=0
Device Attributes and generation of XML using urn:schemas-upnp-org:device-1-0. We could just return a...
Definition: DLNADeviceInfo.h:28
void setDeviceType(const char *st)
Definition: DLNADeviceInfo.h:82
void addService(DLNAServiceInfo s)
Adds a service definition.
Definition: DLNADeviceInfo.h:180
void setManufacturerURL(const char *url)
Definition: DLNADeviceInfo.h:162
Str url_str
Definition: DLNADeviceInfo.h:303
void setModelName(const char *name)
Definition: DLNADeviceInfo.h:166
void setSerialNumber(const char *sn)
Definition: DLNADeviceInfo.h:170
const char * getBaseURL()
Provides the base url.
Definition: DLNADeviceInfo.h:120
void setManufacturer(const char *man)
Definition: DLNADeviceInfo.h:160
void setUDN(const char *id)
Define the udn uuid.
Definition: DLNADeviceInfo.h:87
void setFriendlyName(const char *name)
Definition: DLNADeviceInfo.h:158
void setModelNumber(const char *number)
Definition: DLNADeviceInfo.h:168
void setBaseURL(const char *url)
Defines the base url.
Definition: DLNADeviceInfo.h:93
Setup of a Basic DLNA Device service. The device registers itself to the network and answers to the D...
Definition: DLNADevice.h:46
void end() override
Stops the processing and releases the resources.
Definition: DLNADevice.h:118
void setReference(void *ref) override
Sets a reference pointer that can be used to associate application.
Definition: DLNADevice.h:452
static void parseActionRequest(IHttpServer *server, IClientHandler &client, const char *requestPath, HttpRequestHandlerLine *hl, ActionRequest &action)
Parses the SOAP content of a DLNA action request.
Definition: DLNADevice.h:274
bool begin(DLNADeviceInfo &device, IUDPService &udp, IHttpServer &server) override
start the
Definition: DLNADevice.h:53
static size_t replyGetCurrentConnectionInfo(Print &out, const char *protocolInfo, const char *connectionID, const char *direction)
Definition: DLNADevice.h:397
bool loop(int loopAction=RUN_ALL) override
Definition: DLNADevice.h:141
static size_t replyGetProtocolInfo(Print &out, const char *source="", const char *sink="")
Definition: DLNADevice.h:364
void logStatus()
Definition: DLNADevice.h:457
static bool handleSubscription(IHttpServer *server, IClientHandler *client, const char *requestPath, HttpRequestHandlerLine *hl, bool &is_subscribe)
Static handler for SUBSCRIBE/UNSUBSCRIBE requests on service event URLs.
Definition: DLNADevice.h:427
static size_t replyGetCurrentConnectionIDs(Print &out, const char *ids)
Definition: DLNADevice.h:381
void setSubscriptionsActive(bool flag) override
Enable or disable subscription notifications: call before begin.
Definition: DLNADevice.h:260
bool isSubscriptionsActive() const override
Check if subscription notifications are active.
Definition: DLNADevice.h:269
void addChange(const char *serviceAbbrev, std::function< size_t(Print &, void *)> changeWriter, void *ref) override
Record a state variable change for subscription notifications.
Definition: DLNADevice.h:231
ConnectionManager SCPD descriptor for MediaServer.
Definition: DLNAMediaServerDescr.h:20
size_t printDescr(Print &out) override
Emit ConnectionManager SCPD XML.
Definition: DLNAMediaServerDescr.h:30
ContentDirectory SCPD descriptor for MediaServer.
Definition: DLNAMediaServerDescr.h:160
size_t printDescr(Print &out) override
Emit ContentDirectory SCPD XML.
Definition: DLNAMediaServerDescr.h:170
Digital Media Server implementation.
Definition: DLNAMediaServer.h:46
DLNAMediaServer()
Default constructor: initialize device info and defaults.
Definition: DLNAMediaServer.h:65
static void contentDescCB(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Static descriptor callback for ContentDirectory SCPD.
Definition: DLNAMediaServer.h:315
DLNADescr & getContentDirectoryDescr()
Get pointer to the per-instance ContentDirectory descriptor.
Definition: DLNAMediaServer.h:210
bool processActionGetCurrentConnectionIDs(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ConnectionManager:GetCurrentConnectionIDs action.
Definition: DLNAMediaServer.h:604
bool processAction(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Process action requests using rules-based dispatch.
Definition: DLNAMediaServer.h:412
int g_stream_numberReturned
Definition: DLNAMediaServer.h:236
void end()
Stops the processing and releases the resources.
Definition: DLNAMediaServer.h:106
int getSystemUpdateID()
Provides access to the system update ID.
Definition: DLNAMediaServer.h:177
GetDataCallback g_stream_get_data_cb
Definition: DLNAMediaServer.h:235
int incrementSystemUpdateID()
Definition: DLNAMediaServer.h:181
void logStatus()
Log current status of subscriptions and scheduler.
Definition: DLNAMediaServer.h:219
void setContentDirectoryDescr(DLNADescr &d)
Set a custom ContentDirectory SCPD descriptor (per-instance)
Definition: DLNAMediaServer.h:207
const char * getSortCapabilities()
Get the sort capabilities string (CSV)
Definition: DLNAMediaServer.h:124
bool processActionBrowse(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ContentDirectory:Browse action.
Definition: DLNAMediaServer.h:433
void * reference_
Definition: DLNAMediaServer.h:234
void * g_stream_reference
Definition: DLNAMediaServer.h:240
int g_stream_updateID
Definition: DLNAMediaServer.h:238
static void connmgrControlCB(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
simple connection manager control that replies OK
Definition: DLNAMediaServer.h:872
const char * getConnectionID()
Return the currently configured ConnectionID.
Definition: DLNAMediaServer.h:130
ContentQueryType parseContentQueryType(const char *flag)
Common helper to stream a ContentDirectory response (Browse or Search)
Definition: DLNAMediaServer.h:638
bool processActionSearch(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ContentDirectory:Search action.
Definition: DLNAMediaServer.h:472
const char * getSinkProtocols()
Get the current sink ProtocolInfo string.
Definition: DLNAMediaServer.h:160
int g_stream_totalMatches
Definition: DLNAMediaServer.h:237
PrepareDataCallback prepare_data_cb
Definition: DLNAMediaServer.h:229
void setConnectionID(const char *id)
Set the active ConnectionID for the connection manager.
Definition: DLNAMediaServer.h:127
~DLNAMediaServer()
Destructor.
Definition: DLNAMediaServer.h:86
int g_stream_startingIndex
Definition: DLNAMediaServer.h:239
void addChange(const char *serviceAbbrev, std::function< size_t(Print &, void *)> changeWriter)
Definition: DLNAMediaServer.h:263
IUDPService * p_udp_member
Definition: DLNAMediaServer.h:233
bool processActionGetSearchCapabilities(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ContentDirectory:GetSearchCapabilities action.
Definition: DLNAMediaServer.h:511
void setConnectionMgrDescr(DLNADescr &d)
Set a custom ConnectionManager SCPD descriptor (per-instance)
Definition: DLNAMediaServer.h:213
size_t actionResponseEnd(Print &out, const char *responseName)
Definition: DLNAMediaServer.h:673
const char * connectionID
Definition: DLNAMediaServer.h:252
size_t soapEnvelopeEnd(Print &out)
Definition: DLNAMediaServer.h:658
const char * st
Definition: DLNAMediaServer.h:241
bool processActionGetProtocolInfo(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Replies with Source and Sink protocol lists (CSV protocolInfo strings)
Definition: DLNAMediaServer.h:589
ClientType HttpClient
Definition: DLNAMediaServer.h:48
Vector< ActionRule > rules
Definition: DLNAMediaServer.h:253
void setSubscriptionsActive(bool flag)
Enable/disable subscription notifications.
Definition: DLNAMediaServer.h:187
void setUdpService(IUDPService &udp)
Set the UDP service instance the MediaServer should use.
Definition: DLNAMediaServer.h:95
GetDataCallback get_data_cb
Definition: DLNAMediaServer.h:230
void setupServicesImpl(IHttpServer *server)
Setup the service endpoints.
Definition: DLNAMediaServer.h:306
bool(* GetDataCallback)(int index, MediaItem &item, void *reference)
Callback: retrieve MediaItem by index. Returns true if item is valid.
Definition: DLNAMediaServer.h:58
void setReference(void *ref)
Sets a user reference pointer, available in callbacks.
Definition: DLNAMediaServer.h:171
const char * toStr(MediaItemClass itemClass)
convert MediaItemClass to UPnP class string
Definition: DLNAMediaServer.h:818
bool loop(int loopAction=RUN_ALL)
call this method in the Arduino loop as often as possible
Definition: DLNAMediaServer.h:112
void setGetDataCallback(GetDataCallback cb)
Sets the data callback that provides a MediaItem by index.
Definition: DLNAMediaServer.h:166
static void contentDirectoryControlCB(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Control handler for ContentDirectory service.
Definition: DLNAMediaServer.h:836
DLNAMediaServer(IHttpServer &server, IUDPService &udp)
Construct MediaServer with an HttpServer and IUDPService pre-set.
Definition: DLNAMediaServer.h:78
IDevice & device()
Provides access to the internal DLNA device instance.
Definition: DLNAMediaServer.h:184
static DLNAMediaServer * getMediaServer(IHttpServer *server)
Helper to retrieve DLNAMediaServer instance from HttpServer reference chain.
Definition: DLNAMediaServer.h:864
bool isSubscriptionsActive()
Query whether subscription notifications are active.
Definition: DLNAMediaServer.h:192
void setPrepareDataCallback(PrepareDataCallback cb)
Sets the callback that prepares the data for the Browse and Search.
Definition: DLNAMediaServer.h:163
size_t streamActionItems(Print &out, const char *responseName, int startingIndex)
Definition: DLNAMediaServer.h:678
void setGetDataCallback(GetDataCallbackPrint cb)
Sets the alternative data callback that prints a DIDL entry directly.
Definition: DLNAMediaServer.h:168
size_t streamDIDLItems(Print &out, int numberReturned, int startingIndex)
Definition: DLNAMediaServer.h:725
void setSortCapabilities(const char *caps)
Define the sort capabilities: use csv.
Definition: DLNAMediaServer.h:121
void setCustomActionRule(const char *suffix, bool(*handler)(IClientHandler *, DLNAMediaServer *, ActionRequest &, IHttpServer &))
Define your own custom logic.
Definition: DLNAMediaServer.h:195
const char * getSourceProtocols()
Get the current source ProtocolInfo string.
Definition: DLNAMediaServer.h:157
static void connDescCB(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Static descriptor callback for ConnectionManager SCPD.
Definition: DLNAMediaServer.h:338
DLNADescr & getConnectionMgrDescr()
Get pointer to the per-instance ConnectionManager descriptor.
Definition: DLNAMediaServer.h:216
const char * sinkProto
Definition: DLNAMediaServer.h:251
bool begin()
Definition: DLNAMediaServer.h:100
DeviceType dlna_device
Definition: DLNAMediaServer.h:228
size_t streamDIDLItem(Print &out, const MediaItem &item)
Definition: DLNAMediaServer.h:756
size_t actionResponseStart(Print &out, const char *responseName, const char *serviceNS)
Definition: DLNAMediaServer.h:665
size_t(* GetDataCallbackPrint)(int index, Print &out, void *reference)
Definition: DLNAMediaServer.h:61
size_t soapEnvelopeStart(Print &out)
Definition: DLNAMediaServer.h:647
void setupConnectionManagerService(IHttpServer *server)
Setup and register ConnectionManager service.
Definition: DLNAMediaServer.h:397
void setProtocols(const char *source, const char *sink="")
Define the source protocol info: use csv Default is http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN,...
Definition: DLNAMediaServer.h:137
bool processActionGetSortCapabilities(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ContentDirectory:GetSortCapabilities action.
Definition: DLNAMediaServer.h:537
IHttpServer * getHttpServer()
Provides access to the http server.
Definition: DLNAMediaServer.h:174
bool processActionGetCurrentConnectionInfo(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ConnectionManager:GetCurrentConnectionInfo action.
Definition: DLNAMediaServer.h:618
const char * getSearchCapabilities()
Get the search capabilities string (CSV)
Definition: DLNAMediaServer.h:118
GetDataCallbackPrint get_data_print_cb
Definition: DLNAMediaServer.h:231
IHttpServer * p_server
Definition: DLNAMediaServer.h:232
DLNADescr * p_contentDirectoryDescr
Definition: DLNAMediaServer.h:257
static void eventSubscriptionHandler(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
After the subscription we publish all relevant properties.
Definition: DLNAMediaServer.h:361
void setupRules()
Definition: DLNAMediaServer.h:889
bool processActionGetSystemUpdateID(ActionRequest &action, IHttpServer &server, IClientHandler &client)
Handle ContentDirectory:GetSystemUpdateID action.
Definition: DLNAMediaServer.h:563
const char * g_search_capabiities
Definition: DLNAMediaServer.h:243
void setupContentDirectoryService(IHttpServer *server)
Setup and register ContentDirectory service.
Definition: DLNAMediaServer.h:382
void setSearchCapabilities(const char *caps)
Define the search capabilities: use csv.
Definition: DLNAMediaServer.h:115
void(* PrepareDataCallback)(const char *objectID, ContentQueryType queryType, const char *filter, int startingIndex, int requestedCount, const char *sortCriteria, int &numberReturned, int &totalMatches, int &updateID, void *reference)
Definition: DLNAMediaServer.h:52
Str getUri(const char *path)
Complete Uri.
Definition: DLNAMediaServer.h:801
void setHttpServer(IHttpServer &server)
Set the http server instance the MediaServer should use.
Definition: DLNAMediaServer.h:89
void publishAVT()
Publish a ContentDirectory event (SystemUpdateID)
Definition: DLNAMediaServer.h:269
const char * sourceProto
Definition: DLNAMediaServer.h:250
DLNADescr * p_connmgrDescr
Definition: DLNAMediaServer.h:259
const char * usn
Definition: DLNAMediaServer.h:242
void publishCMS()
Publish a ConnectionManager event (CurrentConnectionIDs)
Definition: DLNAMediaServer.h:284
size_t streamDIDL(Print &out, int numberReturned, int startingIndex)
Stream DIDL-Lite payload for a Browse/Search result.
Definition: DLNAMediaServer.h:707
DLNAMediaServerConnectionMgrDescr default_connectionMgrDescr
Definition: DLNAMediaServer.h:258
DLNAMediaServerContentDirectoryDescr default_contentDirectoryDescr
Definition: DLNAMediaServer.h:256
const char * g_sort_capabilities
Definition: DLNAMediaServer.h:246
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:18
void setup(const char *type, const char *id, const char *scp, http_callback cbScp, const char *control, http_callback cbControl, const char *event, http_callback cbEvent)
Setup all relevant values.
Definition: DLNAServiceInfo.h:23
const char * subscription_namespace_abbrev
Definition: DLNAServiceInfo.h:53
Used to register and process callbacks.
Definition: HttpRequestHandlerLine.h:12
Definition: IHttpServer.h:19
virtual void replyNotFound()=0
virtual void reply(const char *contentType, Stream &inputStream, int size, int status=200, const char *msg=SUCCESS)=0
virtual void replyOK()=0
Abstract interface for DLNA device functionality.
Definition: IDevice.h:30
Abstract interface for HTTP server functionality.
Definition: IHttpServer.h:50
virtual void * getReference()=0
Abstract Interface for UDP API.
Definition: IUDPService.h:33
Printf support with output to Print. This class does not do any heap allocations!
Definition: Printf.h:12
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:18
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:383
virtual const char * c_str()
provides the string value as const char*
Definition: StrView.h:376
virtual bool equals(const char *str)
checks if the string equals indicated parameter string
Definition: StrView.h:177
virtual bool contains(const char *str)
checks if the string contains a substring
Definition: StrView.h:284
Heap-backed string utility used throughout tiny_dlna.
Definition: Str.h:27
const char * c_str() const
C-string pointer to internal buffer.
Definition: Str.h:88
Lightweight wrapper around std::vector with Arduino-friendly helpers and a pluggable allocator.
Definition: Vector.h:39
#define DLNA_PROTOCOL_AUDIO
All possible audio protocols.
Definition: dlna_config.h:154
Definition: Allocator.h:13
@ RUN_ALL
Definition: IDevice.h:18
ContentQueryType
Type of content query for DLNA browsing/searching.
Definition: DLNACommon.h:36
MediaItemClass
Definition: MediaItem.h:14
void(* http_callback)(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Definition: DLNAServiceInfo.h:11
Individual action rule for handling specific actions.
Definition: DLNAMediaServer.h:223
bool(* handler)(IClientHandler &, DLNAMediaServer *, ActionRequest &, IHttpServer &)
Definition: DLNAMediaServer.h:225
const char * suffix
Definition: DLNAMediaServer.h:224
Print wrapper that escapes & < > " ' while forwarding to an underlying Print. Returns the expanded ou...
Definition: EscapingPrint.h:9
Media item description used to build DIDL-Lite entries.
Definition: MediaItem.h:24
const char * parentID
Definition: MediaItem.h:26
const char * mimeType
Definition: MediaItem.h:30
const char * id
Definition: MediaItem.h:25
MediaItemClass itemClass
Definition: MediaItem.h:31
bool restricted
Definition: MediaItem.h:27
const char * title
Definition: MediaItem.h:28
const char * resourceURI
Definition: MediaItem.h:29
const char * albumArtURI
Definition: MediaItem.h:32