Arduino DLNA Server
Loading...
Searching...
No Matches
MediaRenderer.h
Go to the documentation of this file.
1#pragma once
2#include <cctype>
3#include <cstring>
4
5#include "basic/Str.h"
6#include "dlna/DLNADevice.h"
8#include "mr_conmgr.h"
9#include "mr_control.h"
10#include "mr_transport.h"
11
12namespace tiny_dlna {
33
63 public:
64 // event handler: (event, reference to MediaRenderer)
65 typedef void (*MediaEventHandler)(MediaEvent event, MediaRenderer& renderer);
66
75 // Constructor
76 DlnaLogger.log(DlnaLogLevel::Info, "MediaRenderer::MediaRenderer");
79 setFriendlyName("MediaRenderer");
80 setManufacturer("TinyDLNA");
81 setModelName("TinyDLNA");
82 setModelNumber("1.0");
83 setBaseURL("http://localhost:44757");
84 }
85
88
90 bool setVolume(uint8_t volumePercent) {
91 if (volumePercent > 100) volumePercent = 100;
92 DlnaLogger.log(DlnaLogLevel::Info, "Set volume: %d", volumePercent);
93 current_volume = volumePercent;
95 return true;
96 }
97
99 uint8_t getVolume() { return current_volume; }
100
102 bool setMute(bool mute) {
103 is_muted = mute;
104 DlnaLogger.log(DlnaLogLevel::Info, "Set mute: %s", mute ? "true" : "false");
106 return true;
107 }
108
110 bool isMuted() { return is_muted; }
111
113 bool isActive() { return is_active; }
114
116 void setActive(bool active) {
117 DlnaLogger.log(DlnaLogLevel::Info, "Set active: %s",
118 active ? "true" : "false");
119 is_active = active;
120 }
121
123 unsigned long getPosition() {
124 // Estimate position; return 0 if not active or start_time not set
125 if (!is_active || start_time == 0) return 0;
126 return millis() - start_time;
127 }
128
130 const char* getMime() {
131 // Prefer explicit MIME from DIDL if available
132 if (!current_mime.isEmpty()) return current_mime.c_str();
133 return nullptr;
134 }
135
136 // Access current URI
137 const char* getCurrentUri() { return current_uri.c_str(); }
138
140 const char* getTransportState() { return transport_state.c_str(); }
141
142 protected:
146 uint8_t current_volume = 50;
147 bool is_muted = false;
148 bool is_active = false;
149 unsigned long start_time = 0;
150 // Current transport state string (e.g. "STOPPED", "PLAYING", "PAUSED_PLAYBACK")
151 tiny_dlna::Str transport_state = "NO_MEDIA_PRESENT";
152 const char* st = "urn:schemas-upnp-org:device:MediaRenderer:1";
153 const char* usn = "uuid:09349455-2941-4cf7-9847-1dd5ab210e97";
154
157 setupServicesImpl(&server);
158 }
159
161 bool play(const char* urlStr) {
162 if (urlStr == nullptr) return false;
163 DlnaLogger.log(DlnaLogLevel::Info, "play URL: %s", urlStr);
164 // store URI
165 current_uri = Str(urlStr);
166 // notify handler about the new URI and play
167 if (event_cb) {
170 }
171 is_active = true;
172 start_time = millis();
173 return true;
174 }
175
177 void setMime(const char* mime) {
178 if (mime) {
179 current_mime = mime;
181 }
182 }
183
193 DlnaLogger.log(DlnaLogLevel::Info, "Playback completed");
194 transport_state = "STOPPED";
195 is_active = false;
196 start_time = 0;
197 // notify application handler about the stop
199 // publish UPnP event to subscribers (if subscription manager available)
201 if (mgr) {
202 mgr->publishProperty("/AVT/event", "TransportState", "STOPPED");
203 }
204 }
205
208 void setMimeFromDIDL(const char* didl) {
209 if (!didl) return;
210 // Convenience helper: extract protocolInfo attribute from a tag and
211 // return the contentFormat (3rd token) portion, e.g. from
212 // protocolInfo="http-get:*:audio/mpeg:*" -> "audio/mpeg".
213 char mimeBuf[64] = {0};
215 didl, "<res", "protocolInfo=", 3, mimeBuf, sizeof(mimeBuf))) {
216 setMime(mimeBuf);
217 }
218 }
219
220 static const char* reply() {
221 static const char* result =
222 "<s:Envelope "
223 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/"
224 "\"><s:Body><u:%1 "
225 "xmlns:u=\"urn:schemas-upnp-org:service:%2:"
226 "1\"></u:%1></s:Body></s:Envelope>";
227 return result;
228 }
229
230 // Transport control handler
231 static void transportControlCB(HttpServer* server, const char* requestPath,
233 // Parse SOAP request and extract action
234 Str soap = server->contentStr();
235 DlnaLogger.log(DlnaLogLevel::Info, "Transport Control: %s", soap.c_str());
236 MediaRenderer& media_renderer = *((MediaRenderer*)hl->context[0]);
237
238 Str reply_str{reply()};
239 reply_str.replaceAll("%2", "AVTransport");
240 if (soap.indexOf("Play") >= 0) {
241 media_renderer.setActive(true);
242 reply_str.replaceAll("%1", "PlayResponse");
243 server->reply("text/xml", reply_str.c_str());
244 } else if (soap.indexOf("Pause") >= 0) {
245 media_renderer.setActive(false);
246 reply_str.replaceAll("%1", "PauseResponse");
247 server->reply("text/xml", reply_str.c_str());
248 } else if (soap.indexOf("Stop") >= 0) {
249 media_renderer.setActive(false);
250 reply_str.replaceAll("%1", "StopResponse");
251 server->reply("text/xml", reply_str.c_str());
252 } else if (soap.indexOf("SetAVTransportURI") >= 0) {
253 // Extract URL from SOAP request safely
254 int urlTagStart = soap.indexOf("<CurrentURI>");
255 int urlTagEnd = soap.indexOf("</CurrentURI>");
256 if (urlTagStart >= 0 && urlTagEnd > urlTagStart) {
257 int urlStart = urlTagStart + (int)strlen("<CurrentURI>");
258 Str url = soap.substring(urlStart, urlTagEnd);
259 media_renderer.play(url.c_str());
260 } else {
262 "SetAVTransportURI called with invalid SOAP payload");
263 }
264 reply_str.replaceAll("%1", "SetAVTransportURIResponse");
265 server->reply("text/xml", reply_str.c_str());
266 } else {
267 // Handle other transport controls
268 reply_str.replaceAll("%1", "ActionResponse");
269 server->reply("text/xml", reply_str.c_str());
270 }
271 }
272
273 // Rendering control handler
274 static void renderingControlCB(HttpServer* server, const char* requestPath,
276 // Parse SOAP request and extract action
277 MediaRenderer& media_renderer = *((MediaRenderer*)hl->context[0]);
278 Str soap = server->contentStr();
279 DlnaLogger.log(DlnaLogLevel::Info, "Rendering Control: %s", soap.c_str());
280 Str reply_str{reply()};
281 reply_str.replaceAll("%2", "RenderingControl");
282
283 if (soap.indexOf("SetVolume") >= 0) {
284 // Extract volume from SOAP request safely
285 int volTagStart = soap.indexOf("<DesiredVolume>");
286 int volTagEnd = soap.indexOf("</DesiredVolume>");
287 if (volTagStart >= 0 && volTagEnd > volTagStart) {
288 int volStart = volTagStart + (int)strlen("<DesiredVolume>");
289 int volume = soap.substring(volStart, volTagEnd).toInt();
290 media_renderer.setVolume(volume);
291 } else {
293 "SetVolume called with invalid SOAP payload");
294 }
295 reply_str.replaceAll("%1", "SetVolumeResponse");
296 server->reply("text/xml", reply_str.c_str());
297 } else if (soap.indexOf("SetMute") >= 0) {
298 // Extract mute state from SOAP request safely
299 int muteTagStart = soap.indexOf("<DesiredMute>");
300 int muteTagEnd = soap.indexOf("</DesiredMute>");
301 if (muteTagStart >= 0 && muteTagEnd > muteTagStart) {
302 int muteStart = muteTagStart + (int)strlen("<DesiredMute>");
303 bool mute = (soap.substring(muteStart, muteTagEnd) == "1");
304 media_renderer.setMute(mute);
305 } else {
307 "SetMute called with invalid SOAP payload");
308 }
309 reply_str.replaceAll("%1", "SetMuteResponse");
310 server->reply("text/xml", reply_str.c_str());
311 } else {
312 // Handle other rendering controls
313 reply_str.replaceAll("%1", "RenderingControl");
314 server->reply("text/xml", reply_str.c_str());
315 }
316 };
317
319 DlnaLogger.log(DlnaLogLevel::Info, "MediaRenderer::setupServices");
320
321 auto transportCB = [](HttpServer* server, const char* requestPath,
323 server->reply("text/xml", [](Print& out){ mr_connmgr_xml_printer(out); });
324 };
325
326 auto connmgrCB = [](HttpServer* server, const char* requestPath,
328 server->reply("text/xml", [](Print& out){ mr_connmgr_xml_printer(out); });
329 };
330
331 auto controlCB = [](HttpServer* server, const char* requestPath,
333 server->reply("text/xml", [](Print& out){ mr_control_xml_printer(out); });
334 };
335
336 // define services
337 DLNAServiceInfo rc, cm, avt;
338 avt.setup("urn:schemas-upnp-org:service:AVTransport:1",
339 "urn:upnp-org:serviceId:AVTransport", "/AVT/service.xml",
340 transportCB, "/AVT/control", transportControlCB, "/AVT/event",
341 [](HttpServer* server, const char* requestPath,
342 HttpRequestHandlerLine* hl) { server->replyOK(); });
343
344 cm.setup(
345 "urn:schemas-upnp-org:service:ConnectionManager:1",
346 "urn:upnp-org:serviceId:ConnectionManager", "/CM/service.xml",
347 connmgrCB, "/CM/control",
348 [](HttpServer* server, const char* requestPath,
349 HttpRequestHandlerLine* hl) { server->replyOK(); },
350 "/CM/event",
351 [](HttpServer* server, const char* requestPath,
352 HttpRequestHandlerLine* hl) { server->replyOK(); });
353
354 rc.setup("urn:schemas-upnp-org:service:RenderingControl:1",
355 "urn:upnp-org:serviceId:RenderingControl", "/RC/service.xml",
356 controlCB, "/RC/control", renderingControlCB, "/RC/event",
357 [](HttpServer* server, const char* requestPath,
358 HttpRequestHandlerLine* hl) { server->replyOK(); });
359
360 addService(rc);
361 addService(cm);
362 addService(avt);
363 }
364};
365
366} // namespace tiny_dlna
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 addService(DLNAServiceInfo s)
Adds a service definition.
Definition: DLNADeviceInfo.h:138
void setModelName(const char *name)
Definition: DLNADeviceInfo.h:127
void setSerialNumber(const char *sn)
Definition: DLNADeviceInfo.h:131
void setManufacturer(const char *man)
Definition: DLNADeviceInfo.h:121
void setFriendlyName(const char *name)
Definition: DLNADeviceInfo.h:119
void setModelNumber(const char *number)
Definition: DLNADeviceInfo.h:129
void setBaseURL(const char *url)
Defines the base url.
Definition: DLNADeviceInfo.h:59
static SubscriptionMgr * getSubscriptionMgr()
Definition: DLNADevice.h:83
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:16
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
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:24
void replyOK()
write OK reply with 200 SUCCESS
Definition: HttpServer.h:363
Str contentStr()
converts the client content to a string
Definition: HttpServer.h:450
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:293
Abstract Interface for UDP API.
Definition: IUDPService.h:34
void log(DlnaLogLevel current_level, const char *fmt...)
Print log message.
Definition: Logger.h:40
MediaRenderer DLNA Device.
Definition: MediaRenderer.h:62
void setActive(bool active)
Set the active state (used by transport callbacks)
Definition: MediaRenderer.h:116
bool play(const char *urlStr)
Start playback of a network resource (returns true on success)
Definition: MediaRenderer.h:161
const char * getCurrentUri()
Definition: MediaRenderer.h:137
tiny_dlna::Str current_mime
Definition: MediaRenderer.h:144
bool isActive()
Query whether renderer is active (playing or ready)
Definition: MediaRenderer.h:113
tiny_dlna::Str current_uri
Definition: MediaRenderer.h:143
void(* MediaEventHandler)(MediaEvent event, MediaRenderer &renderer)
Definition: MediaRenderer.h:65
bool setVolume(uint8_t volumePercent)
Set the renderer volume (0..100 percent)
Definition: MediaRenderer.h:90
static void transportControlCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Definition: MediaRenderer.h:231
void setupServicesImpl(HttpServer *server)
Definition: MediaRenderer.h:318
tiny_dlna::Str transport_state
Definition: MediaRenderer.h:151
bool is_muted
Definition: MediaRenderer.h:147
uint8_t getVolume()
Get current volume (0..100 percent)
Definition: MediaRenderer.h:99
bool setMute(bool mute)
Enable or disable mute.
Definition: MediaRenderer.h:102
void setMediaEventHandler(MediaEventHandler cb)
Register a media event handler callback.
Definition: MediaRenderer.h:87
MediaRenderer()
Default constructor.
Definition: MediaRenderer.h:74
void setMime(const char *mime)
Set MIME explicitly (used when DIDL-Lite metadata provides protocolInfo)
Definition: MediaRenderer.h:177
uint8_t current_volume
Definition: MediaRenderer.h:146
void setupServices(HttpServer &server, IUDPService &udp)
Setup the HTTP and UDP services.
Definition: MediaRenderer.h:156
unsigned long getPosition()
Get estimated playback position (ms)
Definition: MediaRenderer.h:123
void setMimeFromDIDL(const char *didl)
Definition: MediaRenderer.h:208
bool is_active
Definition: MediaRenderer.h:148
const char * st
Definition: MediaRenderer.h:152
void playbackCompleted()
Notify the renderer that playback completed.
Definition: MediaRenderer.h:192
MediaEventHandler event_cb
Definition: MediaRenderer.h:145
const char * usn
Definition: MediaRenderer.h:153
const char * getTransportState()
Get textual transport state.
Definition: MediaRenderer.h:140
static void renderingControlCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Definition: MediaRenderer.h:274
const char * getMime()
Provides the mime from the DIDL or nullptr.
Definition: MediaRenderer.h:130
static const char * reply()
Definition: MediaRenderer.h:220
unsigned long start_time
Definition: MediaRenderer.h:149
bool isMuted()
Query mute state.
Definition: MediaRenderer.h:110
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:383
virtual int indexOf(const char c, int start=0)
Definition: StrView.h:275
int toInt()
Converts the string to an int.
Definition: StrView.h:596
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
Str substring(int start, int end)
copies a substring into the current string
Definition: Str.h:195
Manages event subscriptions and notification delivery.
Definition: SubscriptionMgr.h:45
void publishProperty(const char *serviceId, const char *varName, const char *value)
Definition: SubscriptionMgr.h:80
static bool extractAttributeToken(const char *xml, const char *tagName, const char *attrName, int n, char *outBuf, size_t bufSize)
Extract the nth (1-based) colon-separated token from an attribute value located on the first occurren...
Definition: XMLAttributeParser.h:94
Definition: Allocator.h:6
MediaEvent
Events emitted by the MediaRenderer to notify the application about playback and rendering control ch...
Definition: MediaRenderer.h:32
LoggerClass DlnaLogger
Definition: Logger.cpp:5