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/Str.h"
8#include "dlna/Action.h"
13#include "dlna/xml/XMLPrinter.h"
14#include "http/Http.h"
15
16namespace tiny_dlna {
17
43 public:
57 typedef void (*PrepareDataCallback)(
58 const char* objectID, ContentQueryType queryType, const char* filter,
59 int startingIndex, int requestedCount, const char* sortCriteria,
60 int& numberReturned, int& totalMatches, int& updateID, void* reference);
61
69 typedef bool (*GetDataCallback)(int index, MediaItem& item, void* reference);
70
78 setFriendlyName("ArduinoMediaServer");
79 setManufacturer("TinyDLNA");
80 setManufacturerURL("https://github.com/pschatzmann/arduino-dlna");
81 setModelName("TinyDLNA MediaServer");
82 setModelNumber("1.0");
83 setBaseURL("http://localhost:44757");
84 setupRules();
85 }
93 // use setters so derived classes or overrides get a consistent path
94 setHttpServer(server);
95 setUdpService(udp);
96 }
97
100
102 void setHttpServer(HttpServer& server) {
103 // ensure instance pointer is available for callbacks
104 self = this;
105 p_server = &server;
106 // prepare handler context and register service endpoints on the provided
107 // server
108 ref_ctx[0] = this;
109 setupServicesImpl(&server);
110 }
111
114
118 bool begin() {
119 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::begin");
120 return dlna_device.begin(*this, *p_udp_member, *p_server);
121 }
122
124 void end() {
125 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::end");
127 }
128
130 bool loop() { return dlna_device.loop(); }
131
133 void setSearchCapabilities(const char* caps) { g_search_capabiities = caps; }
134
137
139 void setSortCapabilities(const char* caps) { g_sort_capabilities = caps; }
140
142 const char* getSortCapabilities() { return g_sort_capabilities; }
143
145 void setConnectionID(const char* id) { connectionID = id; }
146
148 const char* getConnectionID() { return connectionID; }
149
155 void setProtocols(const char* source, const char* sink = "") {
156 sourceProto = source;
157 sinkProto = sink;
158 // publish protocol info change to ConnectionManager subscribers
159 // Build writer that will use live state from the device at publish time
160 auto writer = [](Print& out, void* ref) -> size_t {
161 auto self = (DLNAMediaServer*)ref;
162 size_t result = 0;
163 result += out.print("<SourceProtocolInfo>\n");
164 result += out.print(StringRegistry::nullStr(self->getSourceProtocols()));
165 result += out.print("</SourceProtocolInfo>\n");
166 result += out.print("<SinkProtocolInfo>\n");
167 result += out.print(StringRegistry::nullStr(self->getSinkProtocols()));
168 result += out.print("</SinkProtocolInfo>\n");
169 return result;
170 };
171 addChange("CMS", writer);
172 }
173
175 const char* getSourceProtocols() { return sourceProto; }
176
178 const char* getSinkProtocols() { return sinkProto; }
179
182
185
187 void setReference(void* ref) { reference_ = ref; }
188
191
194
198
201
203 void setCustomActionRule(const char* suffix,
204 bool (*handler)(DLNAMediaServer*, ActionRequest&,
205 HttpServer&)) {
206 for (size_t i = 0; i < rules.size(); ++i) {
207 if (StrView(rules[i].suffix).equals(suffix)) {
208 rules[i].handler = handler;
209 return;
210 }
211 }
212 rules.push_back({suffix, handler});
213 }
214
215 protected:
216 // Action rule struct for ContentDirectory
217 struct ActionRule {
218 const char* suffix;
220 };
221
222 static inline DLNAMediaServer* self = nullptr;
223 // internal DLNA device instance owned by this MediaServer
225 // stored callbacks
228
231 void* ref_ctx[1] = {nullptr};
232 void* reference_ = nullptr;
238 void* g_stream_reference = nullptr;
239 const char* st = "urn:schemas-upnp-org:device:MediaServer:1";
240 const char* usn = "uuid:media-server-0000-0000-0000-000000000001";
242 "dc:title,dc:creator,upnp:class,upnp:genre,"
243 "upnp:album,upnp:artist,upnp:albumArtURI";
245 "dc:title,dc:date,upnp:class,upnp:album,upnp:episodeNumber,upnp:"
246 "originalTrackNumber";
247 // Default protocol info values (use DLNA_PROTOCOL by default)
249 const char* sinkProto = "";
250 const char* connectionID = "0";
252
255 void addChange(const char* serviceAbbrev,
256 std::function<size_t(Print&, void*)> changeWriter) {
257 dlna_device.addChange(serviceAbbrev, changeWriter, this);
258 }
259
261 void publishAVT() {
262 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::publishAVT");
263 // Publish SystemUpdateID using a writer that reads the current value
264 auto writer = [](Print& out, void* ref) -> size_t {
265 auto self = (DLNAMediaServer*)ref;
266 size_t result = 0;
267 result += out.print("<SystemUpdateID val=\"");
268 result += out.print(self->g_stream_updateID);
269 result += out.print("\"/>\n");
270 return result;
271 };
272 addChange("AVT", writer);
273 }
274
276 void publishCMS() {
277 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::publishCMS");
278 // Publish current ConnectionManager properties using a writer that
279 // reads live state from the device
280 auto writer = [](Print& out, void* ref) -> size_t {
281 auto self = (DLNAMediaServer*)ref;
282 size_t result = 0;
283 result += out.print("<SourceProtocolInfo>\n");
284 result += out.print(StringRegistry::nullStr(self->sourceProto));
285 result += out.print("</SourceProtocolInfo>\n");
286 result += out.print("<SinkProtocolInfo>\n");
287 result += out.print(StringRegistry::nullStr(self->sinkProto));
288 result += out.print("</SinkProtocolInfo>\n");
289 result += out.print("<CurrentConnectionIDs>");
290 result += out.print(StringRegistry::nullStr(self->connectionID));
291 result += out.print("</CurrentConnectionIDs>\n");
292 return result;
293 };
294 addChange("CMS", writer);
295 }
296
299 DlnaLogger.log(DlnaLogLevel::Info, "MediaServer::setupServices");
300
301 // register the individual services via helpers
304 }
305
307 static void contentDescCB(HttpServer* server, const char* requestPath,
309 server->reply("text/xml", [](Print& out) {
311 descr.printDescr(out);
312 });
313 }
314
316 static void connDescCB(HttpServer* server, const char* requestPath,
318 server->reply("text/xml", [](Print& out) {
320 descr.printDescr(out);
321 });
322 }
323
326 const char* requestPath,
328 DLNADevice::eventSubscriptionHandler(server, requestPath, hl);
330 if (device) {
331 StrView request_path_str(requestPath);
332 if (request_path_str.contains("/CD/"))
333 device->publishAVT();
334 else if (request_path_str.contains("/CM/"))
335 device->publishCMS();
336 else
338 "eventSubscriptionHandler: Unknown request path: %s",
339 requestPath);
340 }
341 }
342
346 cd.setup("urn:schemas-upnp-org:service:ContentDirectory:1",
347 "urn:upnp-org:serviceId:ContentDirectory", "/CD/service.xml",
348 &DLNAMediaServer::contentDescCB, "/CD/control",
350 [](HttpServer* server, const char* requestPath,
351 HttpRequestHandlerLine* hl) { server->replyOK(); });
352
353 // subscription namespace abbreviation used for event publishing
355
356 // register URLs on the provided server so SCPD, control and event
357 // subscription endpoints are available immediately
358 server->on(cd.scpd_url, T_GET, cd.scp_cb, ref_ctx, 1);
359 server->on(cd.control_url, T_POST, cd.control_cb, ref_ctx, 1);
360 // Register event subscription handlers for SUBSCRIBE, UNSUBSCRIBE and
361 // POST so the server accepts subscription requests and related POSTS on
362 // the same event URL.
364 1);
365 server->on(cd.event_sub_url, T_UNSUBSCRIBE,
367 server->on(cd.event_sub_url, T_POST,
369
370 addService(cd);
371 }
372
376 cm.setup("urn:schemas-upnp-org:service:ConnectionManager:1",
377 "urn:upnp-org:serviceId:ConnectionManager", "/CM/service.xml",
378 &DLNAMediaServer::connDescCB, "/CM/control",
380 [](HttpServer* server, const char* requestPath,
381 HttpRequestHandlerLine* hl) { server->replyOK(); });
382
383 // subscription namespace abbreviation used for event publishing
385
386 server->on(cm.scpd_url, T_GET, cm.scp_cb, ref_ctx, 1);
387 server->on(cm.control_url, T_POST, cm.control_cb, ref_ctx, 1);
388 // Register event subscription handlers for SUBSCRIBE, UNSUBSCRIBE and
389 // POST so the server accepts subscription requests and related POSTS on
390 // the same event URL.
392 1);
393 server->on(cm.event_sub_url, T_UNSUBSCRIBE,
395 server->on(cm.event_sub_url, T_POST,
397
398 addService(cm);
399 }
400
402 bool processAction(ActionRequest& action, HttpServer& server) {
403 DlnaLogger.log(DlnaLogLevel::Info, "DLNAMediaServer::processAction: %s",
405 StrView action_str(action.action);
406 if (action_str.isEmpty()) {
407 DlnaLogger.log(DlnaLogLevel::Error, "Empty action received");
408 server.replyNotFound();
409 return false;
410 }
411 for (const auto& rule : rules) {
412 if (action_str.endsWith(rule.suffix)) {
413 return rule.handler(this, action, server);
414 }
415 }
416 DlnaLogger.log(DlnaLogLevel::Error, "Unsupported action: %s",
417 action.action);
418 server.replyNotFound();
419 return false;
420 }
421
424 DlnaLogger.log(DlnaLogLevel::Info, "processActionBrowse");
425 int numberReturned = 0;
426 int totalMatches = 0;
427 int updateID = g_stream_updateID;
428
429 // extract paging args
430 int startingIndex = action.getArgumentIntValue("StartingIndex");
431 int requestedCount = action.getArgumentIntValue("RequestedCount");
432
433 // determine query type (BrowseDirectChildren / BrowseMetadata / Search)
434 ContentQueryType qtype =
435 parseContentQueryType(action.getArgumentValue("BrowseFlag"));
436 // query for data
437 if (prepare_data_cb) {
438 prepare_data_cb(action.getArgumentValue("ObjectID"), qtype,
439 action.getArgumentValue("Filter"), startingIndex,
440 requestedCount, action.getArgumentValue("SortCriteria"),
441 numberReturned, totalMatches, updateID, reference_);
442 }
443
444 // provide result
445 return streamActionItems("BrowseResponse",
446 action.getArgumentValue("ObjectID"), qtype,
447 action.getArgumentValue("Filter"), startingIndex,
448 requestedCount, server);
449 }
450
453 DlnaLogger.log(DlnaLogLevel::Info, "processActionSearch");
454
455 int numberReturned = 0;
456 int totalMatches = 0;
457 int updateID = g_stream_updateID;
458
459 // extract paging args
460 int startingIndex = action.getArgumentIntValue("StartingIndex");
461 int requestedCount = action.getArgumentIntValue("RequestedCount");
462
463 // For Search actions we always use the Search query type regardless of
464 // any provided BrowseFlag; this ensures Search semantics are preserved.
466 // query for data
467 if (prepare_data_cb) {
468 prepare_data_cb(action.getArgumentValue("ContainerID"), qtype,
469 action.getArgumentValue("Filter"), startingIndex,
470 requestedCount, action.getArgumentValue("SortCriteria"),
471 numberReturned, totalMatches, updateID, reference_);
472 }
473
474 // provide result
475 return streamActionItems("SearchResponse",
476 action.getArgumentValue("ContainerID"), qtype,
477 action.getArgumentValue("SearchCriteria"),
478 startingIndex, requestedCount, server);
479 }
480
483 HttpServer& server) {
484 ChunkPrint chunk{server.client()};
485 server.replyChunked("text/xml");
486
487 soapEnvelopeStart(chunk);
488 actionResponseStart(chunk, "GetSearchCapabilitiesResponse",
489 "urn:schemas-upnp-org:service:ContentDirectory:1");
490
491 chunk.print("<SearchCaps>");
493 chunk.println("</SearchCaps>");
494
495 actionResponseEnd(chunk, "GetSearchCapabilitiesResponse");
496 soapEnvelopeEnd(chunk);
497 chunk.end();
499 "processActionGetSearchCapabilities (streamed)");
500 return true;
501 }
502
505 HttpServer& server) {
506 ChunkPrint chunk{server.client()};
507 server.replyChunked("text/xml");
508
509 soapEnvelopeStart(chunk);
510 actionResponseStart(chunk, "GetSortCapabilitiesResponse",
511 "urn:schemas-upnp-org:service:ContentDirectory:1");
512
513 chunk.print("<SortCaps>");
515 chunk.println("</SortCaps>");
516
517 actionResponseEnd(chunk, "GetSortCapabilitiesResponse");
518 soapEnvelopeEnd(chunk);
519 chunk.end();
521 "processActionGetSortCapabilities (streamed)");
522 return true;
523 }
524
527 HttpServer& server) {
528 ChunkPrint chunk{server.client()};
529 server.replyChunked("text/xml");
530
531 soapEnvelopeStart(chunk);
532 actionResponseStart(chunk, "GetSystemUpdateIDResponse",
533 "urn:schemas-upnp-org:service:ContentDirectory:1");
534
535 chunk.printf("<Id>%d</Id>\r\n", g_stream_updateID);
536
537 actionResponseEnd(chunk, "GetSystemUpdateIDResponse");
538 soapEnvelopeEnd(chunk);
539 chunk.end();
541 "processActionGetSystemUpdateID (streamed): %d",
543 return true;
544 }
545
548 // Stream the GetProtocolInfo response using chunked encoding to avoid
549 // building large in-memory strings and to keep behavior consistent with
550 // other actions (e.g. Browse/Search).
551 ChunkPrint chunk{server.client()};
552 server.replyChunked("text/xml");
553
554 soapEnvelopeStart(chunk);
555 actionResponseStart(chunk, "GetProtocolInfoResponse",
556 "urn:schemas-upnp-org:service:ConnectionManager:1");
557
558 chunk.print("<Source>");
560 chunk.println("</Source>");
561
562 chunk.print("<Sink>");
564 chunk.println("</Sink>");
565
566 actionResponseEnd(chunk, "GetProtocolInfoResponse");
567 soapEnvelopeEnd(chunk);
568 chunk.end();
569 return true;
570 }
571
574 HttpServer& server) {
575 // Stream the empty ConnectionIDs response using chunked encoding to avoid
576 // allocating the full response in memory and keep consistent streaming
577 // behavior with other services.
578 ChunkPrint chunk{server.client()};
579 server.replyChunked("text/xml");
580
581 soapEnvelopeStart(chunk);
582 actionResponseStart(chunk, "GetCurrentConnectionIDsResponse",
583 "urn:schemas-upnp-org:service:ConnectionManager:1");
584
585 // list with one default value now
586 chunk.println("<ConnectionIDs>01</ConnectionIDs>");
587
588 actionResponseEnd(chunk, "GetCurrentConnectionIDsResponse");
589 soapEnvelopeEnd(chunk);
590 chunk.end();
591 return true;
592 }
595 HttpServer& server) {
596 // Read requested ConnectionID (not used in this simple implementation)
597 int connId = action.getArgumentIntValue("ConnectionID");
598
599 // Stream the GetCurrentConnectionInfo response using chunked encoding
600 // to avoid building the full response in memory and to remain
601 // consistent with other handlers.
602 ChunkPrint chunk{server.client()};
603 server.replyChunked("text/xml");
604
605 soapEnvelopeStart(chunk);
606 actionResponseStart(chunk, "GetCurrentConnectionInfoResponse",
607 "urn:schemas-upnp-org:service:ConnectionManager:1");
608
609 // RcsID and AVTransportID
610 chunk.println("<RcsID>0</RcsID>");
611 chunk.println("<AVTransportID>0</AVTransportID>");
612
613 // ProtocolInfo (empty by default)
614 chunk.print("<ProtocolInfo>");
616 chunk.println("</ProtocolInfo>");
617
618 // PeerConnectionManager and PeerConnectionID
619 chunk.print("<PeerConnectionManager>");
620 chunk.println("</PeerConnectionManager>");
621 chunk.println("<PeerConnectionID>0</PeerConnectionID>");
622
623 // Direction and Status
624 chunk.println("<Direction>Output</Direction><Status>OK</Status>");
625
626 actionResponseEnd(chunk, "GetCurrentConnectionInfoResponse");
627 soapEnvelopeEnd(chunk);
628 chunk.end();
629 return true;
630 }
631
634 StrView fv(flag);
635 if (fv.equals("BrowseDirectChildren"))
637 if (fv.equals("BrowseMetadata")) return ContentQueryType::BrowseMetadata;
638 // fallback
640 }
642 chunk.print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n");
643 chunk.print(
644 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
645 "\r\n");
646 chunk.print(
647 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n");
648 chunk.print("<s:Body>\r\n");
649 }
650
652 chunk.print("</s:Body>\r\n");
653 chunk.print("</s:Envelope>\r\n");
654 }
655
656 void actionResponseStart(ChunkPrint& chunk, const char* responseName,
657 const char* serviceNS) {
658 // writes: <u:ResponseName xmlns:u="serviceNS">
659 chunk.printf("<u:%s xmlns:u=\"%s\">\r\n", responseName, serviceNS);
660 }
661
662 void actionResponseEnd(ChunkPrint& chunk, const char* responseName) {
663 chunk.printf("</u:%s>\r\n", responseName);
664 }
665
666 bool streamActionItems(const char* responseName, const char* objectID,
667 ContentQueryType queryType, const char* filter,
668 int startingIndex, int requestedCount,
669 HttpServer& server) {
670 int numberReturned = 0;
671 int totalMatches = 0;
672 int updateID = g_stream_updateID;
673
674 // allow caller to prepare counts/updateID
675 if (prepare_data_cb) {
676 prepare_data_cb(objectID, queryType, filter, startingIndex,
677 requestedCount, nullptr, numberReturned, totalMatches,
678 updateID, reference_);
679 }
680
681 ChunkPrint chunk_writer{server.client()};
682 server.replyChunked("text/xml");
683
684 // SOAP envelope start and response wrapper
685 soapEnvelopeStart(chunk_writer);
686 // responseName is assumed to be e.g. "BrowseResponse" or "SearchResponse"
687 actionResponseStart(chunk_writer, responseName,
688 "urn:schemas-upnp-org:service:ContentDirectory:1");
689
690 // Result -> DIDL-Lite
691 chunk_writer.println("<Result>");
692 // DIDL root with namespaces
693 streamDIDL(chunk_writer, numberReturned, startingIndex);
694 chunk_writer.print("</Result>\r\n");
695
696 // numeric fields
697 chunk_writer.printf("<NumberReturned>%d</NumberReturned>\r\n",
698 numberReturned);
699 chunk_writer.printf("<TotalMatches>%d</TotalMatches>\r\n", totalMatches);
700 chunk_writer.printf("<UpdateID>%d</UpdateID>\r\n", updateID);
701
702 actionResponseEnd(chunk_writer, responseName);
703 soapEnvelopeEnd(chunk_writer);
704 chunk_writer.end();
705 return true;
706 }
707
709 void streamDIDL(ChunkPrint& chunk_writer, int numberReturned,
710 int startingIndex) {
711 // Stream DIDL-Lite Result payload using helper
712 chunk_writer.print(
713 "&lt;DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
714 "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
715 "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"&gt;\r\n");
716
717 if (get_data_cb) {
718 Str url{160};
719 for (int i = 0; i < numberReturned; ++i) {
720 MediaItem item;
721 int idx = startingIndex + i;
722 if (!get_data_cb(idx, item, reference_)) break;
723
724 const char* mediaItemClassStr = toStr(item.itemClass);
725 const char* nodeName =
726 (item.itemClass == MediaItemClass::Folder) ? "container" : "item";
727
728 chunk_writer.printf(
729 "&lt;%s id=\"%s\" parentID=\"%s\" restricted=\"%d\"&gt;", nodeName,
730 item.id ? item.id : "", item.parentID ? item.parentID : "0",
731 item.restricted ? 1 : 0);
732
733 // title
734 chunk_writer.print("&lt;dc:title&gt;");
735 chunk_writer.print(StringRegistry::nullStr(item.title));
736 chunk_writer.print("&lt;/dc:title&gt;\r\n");
737 if (mediaItemClassStr != nullptr) {
738 chunk_writer.printf("&lt;upnp:class&gt;%s&lt;/upnp:class&gt;\r\n",
739 mediaItemClassStr);
740 }
741
742 // res with optional protocolInfo attribute
743 url.set(item.resourceURL);
744 if (!url.isEmpty()) {
745 url.replaceAll("&", "&amp;");
746 if (!StrView(item.mimeType).isEmpty()) {
747 chunk_writer.printf("&lt;res protocolInfo=\"http-get:*:%s:*\"&gt;",
748 item.mimeType);
749 chunk_writer.print(StringRegistry::nullStr(url.c_str()));
750 chunk_writer.print("&lt;/res&gt;\r\n");
751 } else {
752 chunk_writer.print("&lt;res&gt;");
753 chunk_writer.print(StringRegistry::nullStr(url.c_str()));
754 chunk_writer.print("&lt;/res&gt;\r\n");
755 }
756 }
757
758 chunk_writer.printf("&lt;/%s&gt;\r\n", nodeName);
759 }
760 }
761
762 chunk_writer.print("&lt;/DIDL-Lite&gt;\r\n");
763 }
764
765 const char* toStr(MediaItemClass itemClass) {
766 switch (itemClass) {
768 return "object.item.audioItem.musicTrack";
770 return "object.item.audioItem.audioBroadcast";
772 return "object.item.videoItem.movie";
774 return "object.item.imageItem.photo";
776 return "object.container";
777 default:
778 return nullptr;
779 }
780 }
781
784 const char* requestPath,
786 ActionRequest action;
787 DLNADevice::parseActionRequest(server, requestPath, hl, action);
788
789 // If a user-provided callback is registered, hand over the parsed
790 // ActionRequest for custom handling. The callback is responsible for
791 // writing the HTTP reply if it handles the action.
792 DLNAMediaServer* ms = nullptr;
793 if (hl && hl->context[0]) ms = (DLNAMediaServer*)hl->context[0];
794
795 // process the requested action using instance method if available
796 if (ms) {
797 if (ms->processAction(action, *server)) return;
798 }
799
800 server->replyNotFound();
801 }
802
804 static void connmgrControlCB(HttpServer* server, const char* requestPath,
806 ActionRequest action;
807 DLNADevice::parseActionRequest(server, requestPath, hl, action);
808
809 DLNAMediaServer* ms = nullptr;
810 if (hl && hl->context[0]) ms = (DLNAMediaServer*)hl->context[0];
811 if (!ms) {
812 server->replyNotFound();
813 return;
814 }
815
816 // Use rules-based dispatch for ConnectionManager actions
817 if (ms->processAction(action, *server)) return;
818 server->replyNotFound();
819 }
820
823 void setupRules() {
824 // ContentDirectory rules
825 rules.push_back({"Browse", [](DLNAMediaServer* self, ActionRequest& action,
826 HttpServer& server) {
827 return self->processActionBrowse(action, server);
828 }});
829 rules.push_back({"Search", [](DLNAMediaServer* self, ActionRequest& action,
830 HttpServer& server) {
831 return self->processActionSearch(action, server);
832 }});
833 rules.push_back(
834 {"GetSearchCapabilities",
835 [](DLNAMediaServer* self, ActionRequest& action, HttpServer& server) {
836 return self->processActionGetSearchCapabilities(action, server);
837 }});
838 rules.push_back(
839 {"GetSortCapabilities",
840 [](DLNAMediaServer* self, ActionRequest& action, HttpServer& server) {
841 return self->processActionGetSortCapabilities(action, server);
842 }});
843 rules.push_back(
844 {"GetSystemUpdateID",
845 [](DLNAMediaServer* self, ActionRequest& action, HttpServer& server) {
846 return self->processActionGetSystemUpdateID(action, server);
847 }});
848 // ConnectionManager rules
849 rules.push_back(
850 {"GetProtocolInfo",
851 [](DLNAMediaServer* self, ActionRequest& action, HttpServer& server) {
852 return self->processActionGetProtocolInfo(action, server);
853 }});
854 rules.push_back(
855 {"GetCurrentConnectionIDs",
856 [](DLNAMediaServer* self, ActionRequest& action, HttpServer& server) {
857 return self->processActionGetCurrentConnectionIDs(action, server);
858 }});
859 rules.push_back(
860 {"GetCurrentConnectionInfo",
861 [](DLNAMediaServer* self, ActionRequest& action, HttpServer& server) {
862 return self->processActionGetCurrentConnectionInfo(action, server);
863 }});
864 }
865};
866
867} // namespace tiny_dlna
Represents a request to invoke a remote DLNA service action.
Definition: Action.h:109
const char * getArgumentValue(const char *name)
Definition: Action.h:133
int getArgumentIntValue(const char *name)
Definition: Action.h:154
const char * action
Definition: Action.h:159
Print implementation for HTTP chunked transfer encoding.
Definition: HttpChunkWriter.h:56
size_t printf(const char *fmt,...)
Definition: HttpChunkWriter.h:118
size_t print(const char *str)
Definition: HttpChunkWriter.h:72
Device Attributes and generation of XML using urn:schemas-upnp-org:device-1-0. We could just return a...
Definition: DLNADeviceInfo.h:27
void setDeviceType(const char *st)
Definition: DLNADeviceInfo.h:48
void print(Print &out)
renders the device xml
Definition: DLNADeviceInfo.h:40
void addService(DLNAServiceInfo s)
Adds a service definition.
Definition: DLNADeviceInfo.h:143
void setManufacturerURL(const char *url)
Definition: DLNADeviceInfo.h:127
void setModelName(const char *name)
Definition: DLNADeviceInfo.h:131
void setSerialNumber(const char *sn)
Definition: DLNADeviceInfo.h:135
void setManufacturer(const char *man)
Definition: DLNADeviceInfo.h:125
void setFriendlyName(const char *name)
Definition: DLNADeviceInfo.h:123
void setModelNumber(const char *number)
Definition: DLNADeviceInfo.h:133
void setBaseURL(const char *url)
Defines the base url.
Definition: DLNADeviceInfo.h:59
Setup of a Basic DLNA Device service. The device registers itself to the network and answers to the D...
Definition: DLNADevice.h:27
void end()
Stops the processing and releases the resources.
Definition: DLNADevice.h:85
void addChange(const char *serviceAbbrev, std::function< size_t(Print &, void *)> changeWriter, void *ref)
Definition: DLNADevice.h:156
bool begin(DLNADeviceInfo &device, IUDPService &udp, HttpServer &server)
start the
Definition: DLNADevice.h:30
static void parseActionRequest(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl, ActionRequest &action)
Parses the SOAP content of a DLNA action request.
Definition: DLNADevice.h:275
static void eventSubscriptionHandler(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Static handler for SUBSCRIBE/UNSUBSCRIBE requests on service event URLs.
Definition: DLNADevice.h:189
bool loop()
call this method in the Arduino loop as often as possible
Definition: DLNADevice.h:104
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:42
void setGetDataCallback(GetDataCallback cb)
Sets the callback that provides a MediaItem by index.
Definition: DLNAMediaServer.h:184
static void connDescCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Static descriptor callback for ConnectionManager SCPD.
Definition: DLNAMediaServer.h:316
~DLNAMediaServer()
Destructor.
Definition: DLNAMediaServer.h:99
void setCustomActionRule(const char *suffix, bool(*handler)(DLNAMediaServer *, ActionRequest &, HttpServer &))
Define your own custom logic.
Definition: DLNAMediaServer.h:203
void setupContentDirectoryService(HttpServer *server)
Setup and register ContentDirectory service.
Definition: DLNAMediaServer.h:344
bool processActionGetSortCapabilities(ActionRequest &action, HttpServer &server)
Handle ContentDirectory:GetSortCapabilities action.
Definition: DLNAMediaServer.h:504
void soapEnvelopeStart(ChunkPrint &chunk)
Definition: DLNAMediaServer.h:641
const char * g_search_capabiities
Definition: DLNAMediaServer.h:241
void streamDIDL(ChunkPrint &chunk_writer, int numberReturned, int startingIndex)
Stream DIDL-Lite payload for a Browse/Search result.
Definition: DLNAMediaServer.h:709
int g_stream_numberReturned
Definition: DLNAMediaServer.h:234
void setSearchCapabilities(const char *caps)
Define the search capabilities: use csv.
Definition: DLNAMediaServer.h:133
static DLNAMediaServer * self
Definition: DLNAMediaServer.h:222
bool begin()
Definition: DLNAMediaServer.h:118
void setupRules()
Definition: DLNAMediaServer.h:823
DLNADevice dlna_device
Definition: DLNAMediaServer.h:224
const char * toStr(MediaItemClass itemClass)
Definition: DLNAMediaServer.h:765
bool processActionSearch(ActionRequest &action, HttpServer &server)
Handle ContentDirectory:Search action.
Definition: DLNAMediaServer.h:452
static void contentDescCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Static descriptor callback for ContentDirectory SCPD.
Definition: DLNAMediaServer.h:307
int g_instance_id
Definition: DLNAMediaServer.h:237
int g_stream_updateID
Definition: DLNAMediaServer.h:236
PrepareDataCallback prepare_data_cb
Definition: DLNAMediaServer.h:226
const char * sourceProto
Definition: DLNAMediaServer.h:248
void * reference_
Definition: DLNAMediaServer.h:232
int g_stream_totalMatches
Definition: DLNAMediaServer.h:235
void setHttpServer(HttpServer &server)
Set the http server instance the MediaServer should use.
Definition: DLNAMediaServer.h:102
void soapEnvelopeEnd(ChunkPrint &chunk)
Definition: DLNAMediaServer.h:651
const char * g_sort_capabilities
Definition: DLNAMediaServer.h:244
bool processActionGetSearchCapabilities(ActionRequest &action, HttpServer &server)
Handle ContentDirectory:GetSearchCapabilities action.
Definition: DLNAMediaServer.h:482
HttpServer * p_server
Definition: DLNAMediaServer.h:229
GetDataCallback g_stream_get_data_cb
Definition: DLNAMediaServer.h:233
bool processActionGetCurrentConnectionInfo(ActionRequest &action, HttpServer &server)
Handle ConnectionManager:GetCurrentConnectionInfo action.
Definition: DLNAMediaServer.h:594
void setSortCapabilities(const char *caps)
Define the sort capabilities: use csv.
Definition: DLNAMediaServer.h:139
int getSystemUpdateID()
Provides access to the system update ID.
Definition: DLNAMediaServer.h:193
const char * connectionID
Definition: DLNAMediaServer.h:250
int incrementSystemUpdateID()
Definition: DLNAMediaServer.h:197
void addChange(const char *serviceAbbrev, std::function< size_t(Print &, void *)> changeWriter)
Definition: DLNAMediaServer.h:255
const char * st
Definition: DLNAMediaServer.h:239
DLNAMediaServer()
Default constructor for MediaServer. Initializes device information and default properties.
Definition: DLNAMediaServer.h:75
bool processActionGetCurrentConnectionIDs(ActionRequest &action, HttpServer &server)
Handle ConnectionManager:GetCurrentConnectionIDs action.
Definition: DLNAMediaServer.h:573
void actionResponseStart(ChunkPrint &chunk, const char *responseName, const char *serviceNS)
Definition: DLNAMediaServer.h:656
void setupServicesImpl(HttpServer *server)
Setup the service endpoints.
Definition: DLNAMediaServer.h:298
bool processActionGetSystemUpdateID(ActionRequest &action, HttpServer &server)
Handle ContentDirectory:GetSystemUpdateID action.
Definition: DLNAMediaServer.h:526
static void eventSubscriptionHandler(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
After the subscription we publish all relevant properties.
Definition: DLNAMediaServer.h:325
void setReference(void *ref)
Sets a user reference pointer, available in callbacks.
Definition: DLNAMediaServer.h:187
DLNAMediaServer(HttpServer &server, IUDPService &udp)
Recommended constructor Construct a MediaServer with associated HTTP server and UDP service This cons...
Definition: DLNAMediaServer.h:92
const char * getSortCapabilities()
Get the sort capabilities string (CSV)
Definition: DLNAMediaServer.h:142
bool(* GetDataCallback)(int index, MediaItem &item, void *reference)
Callback signature for retrieving a MediaItem by index.
Definition: DLNAMediaServer.h:69
ContentQueryType parseContentQueryType(const char *flag)
Common helper to stream a ContentDirectory response (Browse or Search)
Definition: DLNAMediaServer.h:633
HttpServer * getHttpServer()
Provides access to the http server.
Definition: DLNAMediaServer.h:190
GetDataCallback get_data_cb
Definition: DLNAMediaServer.h:227
IUDPService * p_udp_member
Definition: DLNAMediaServer.h:230
void * ref_ctx[1]
Definition: DLNAMediaServer.h:231
bool loop()
call this method in the Arduino loop as often as possible
Definition: DLNAMediaServer.h:130
bool streamActionItems(const char *responseName, const char *objectID, ContentQueryType queryType, const char *filter, int startingIndex, int requestedCount, HttpServer &server)
Definition: DLNAMediaServer.h:666
const char * sinkProto
Definition: DLNAMediaServer.h:249
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)
Callback signature for preparing data for Browse or Search requests.
Definition: DLNAMediaServer.h:57
const char * getSearchCapabilities()
Get the search capabilities string (CSV)
Definition: DLNAMediaServer.h:136
static void connmgrControlCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
simple connection manager control that replies OK
Definition: DLNAMediaServer.h:804
bool processActionBrowse(ActionRequest &action, HttpServer &server)
Handle ContentDirectory:Browse action.
Definition: DLNAMediaServer.h:423
Vector< ActionRule > rules
Definition: DLNAMediaServer.h:251
bool processActionGetProtocolInfo(ActionRequest &action, HttpServer &server)
Replies with Source and Sink protocol lists (CSV protocolInfo strings)
Definition: DLNAMediaServer.h:547
void actionResponseEnd(ChunkPrint &chunk, const char *responseName)
Definition: DLNAMediaServer.h:662
void * g_stream_reference
Definition: DLNAMediaServer.h:238
const char * getSourceProtocols()
Get the current source ProtocolInfo string.
Definition: DLNAMediaServer.h:175
void setupConnectionManagerService(HttpServer *server)
Setup and register ConnectionManager service.
Definition: DLNAMediaServer.h:374
void publishCMS()
Publish a ConnectionManager event (CurrentConnectionIDs)
Definition: DLNAMediaServer.h:276
DLNADevice & device()
Provides access to the internal DLNA device instance.
Definition: DLNAMediaServer.h:200
const char * getSinkProtocols()
Get the current sink ProtocolInfo string.
Definition: DLNAMediaServer.h:178
void setPrepareDataCallback(PrepareDataCallback cb)
Sets the callback that prepares the data for the Browse and Search.
Definition: DLNAMediaServer.h:181
bool processAction(ActionRequest &action, HttpServer &server)
Process action requests using rules-based dispatch.
Definition: DLNAMediaServer.h:402
void end()
Stops the processing and releases the resources.
Definition: DLNAMediaServer.h:124
static void contentDirectoryControlCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Control handler for ContentDirectory service.
Definition: DLNAMediaServer.h:783
void setConnectionID(const char *id)
Set the active ConnectionID for the connection manager.
Definition: DLNAMediaServer.h:145
void publishAVT()
Publish a ContentDirectory event (SystemUpdateID)
Definition: DLNAMediaServer.h:261
void setUdpService(IUDPService &udp)
Set the UDP service instance the MediaServer should use.
Definition: DLNAMediaServer.h:113
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:155
const char * usn
Definition: DLNAMediaServer.h:240
const char * getConnectionID()
Return the currently configured ConnectionID.
Definition: DLNAMediaServer.h:148
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:16
const char * scpd_url
Definition: DLNAServiceInfo.h:35
http_callback control_cb
Definition: DLNAServiceInfo.h:40
http_callback scp_cb
Definition: DLNAServiceInfo.h:39
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)
Definition: DLNAServiceInfo.h:19
const char * control_url
Definition: DLNAServiceInfo.h:36
const char * subscription_namespace_abbrev
Definition: DLNAServiceInfo.h:49
const char * event_sub_url
Definition: DLNAServiceInfo.h:37
Used to register and process callbacks.
Definition: HttpRequestHandlerLine.h:19
void ** context
Definition: HttpRequestHandlerLine.h:39
A Simple Header only implementation of Http Server that allows the registration of callback functions...
Definition: HttpServer.h:25
void on(const char *url, TinyMethodID method, web_callback_fn fn, void *ctx[]=nullptr, int ctxCount=0)
register a generic handler
Definition: HttpServer.h:90
void replyOK()
write OK reply with 200 SUCCESS
Definition: HttpServer.h:380
Client & client()
Provides the current client.
Definition: HttpServer.h:476
void reply(const char *contentType, Stream &inputStream, int size, int status=200, const char *msg=SUCCESS)
write reply - copies data from input stream with header size
Definition: HttpServer.h:296
void replyNotFound()
write 404 reply
Definition: HttpServer.h:383
void replyChunked(const char *contentType, Stream &inputStream, int status=200, const char *msg=SUCCESS)
chunked reply with data from an input stream
Definition: HttpServer.h:271
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
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:383
virtual bool endsWith(const char *str)
checks if the string ends with the indicated substring
Definition: StrView.h:191
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
String implementation which keeps the data on the heap. We grow the allocated memory only if the copy...
Definition: Str.h:22
static const char * nullStr(const char *str, const char *alt="")
Return str if not null, alt otherwise.
Definition: StringRegistry.h:54
Vector implementation which provides the most important methods as defined by std::vector....
Definition: Vector.h:19
#define DLNA_PROTOCOL_AUDIO
Definition: dlna_config.h:135
Definition: AllocationTracker.h:9
ContentQueryType
Type of content query for DLNA browsing/searching.
Definition: DLNACommon.h:36
MediaItemClass
Definition: MediaItem.h:12
@ T_UNSUBSCRIBE
Definition: HttpHeader.h:46
@ T_SUBSCRIBE
Definition: HttpHeader.h:47
@ T_GET
Definition: HttpHeader.h:37
@ T_POST
Definition: HttpHeader.h:39
LoggerClass DlnaLogger
Definition: Logger.cpp:5
Definition: DLNAMediaServer.h:217
bool(* handler)(DLNAMediaServer *, ActionRequest &, HttpServer &)
Definition: DLNAMediaServer.h:219
const char * suffix
Definition: DLNAMediaServer.h:218
Media item description used to build DIDL-Lite entries.
Definition: MediaItem.h:22
const char * parentID
Definition: MediaItem.h:24
const char * resourceURL
Definition: MediaItem.h:27
const char * mimeType
Definition: MediaItem.h:28
const char * id
Definition: MediaItem.h:23
MediaItemClass itemClass
Definition: MediaItem.h:29
bool restricted
Definition: MediaItem.h:25
const char * title
Definition: MediaItem.h:26