Arduino DLNA Server
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
MediaRenderer.h
Go to the documentation of this file.
1 #include "AudioToolsConfig.h"
2 #include "AudioTools/CoreAudio/AudioStreams.h"
3 #include "AudioTools/CoreAudio/AudioOutput.h"
4 #include "AudioTools/AudioCodecs/AudioEncoded.h"
5 #include "AudioTools/CoreAudio/StreamCopy.h"
6 #include "AudioTools/CoreAudio/Pipeline.h"
7 #include "AudioTools/CoreAudio/VolumeStream.h"
8 #include "AudioTools/CoreAudio/AudioHttp/URLStream.h"
9 #include "conmgr.h"
10 #include "control.h"
11 #include "dlna/DLNADeviceMgr.h"
12 #include "transport.h"
13 
14 namespace tiny_dlna {
15 
20 class MediaRenderer : public DLNADevice {
21  public:
23  // Constructor
24  DlnaLogger.log(DlnaInfo, "MediaRenderer::MediaRenderer");
27  setFriendlyName("MediaRenderer");
28  setManufacturer("TinyDLNA");
29  setModelName("TinyDLNA");
30  setModelNumber("1.0");
31  setBaseURL("http://0.0.0.0:44757");
32  }
33 
34  MediaRenderer(AudioStream& out, AudioDecoder& decoder) : MediaRenderer() {
35  setOutput(out);
36  setDecoder(decoder);
37  }
38 
39  MediaRenderer(AudioOutput& out, AudioDecoder& decoder) : MediaRenderer() {
40  setOutput(out);
41  setDecoder(decoder);
42  }
43 
44  MediaRenderer(Print& out, AudioDecoder& decoder) : MediaRenderer() {
45  setOutput(out);
46  setDecoder(decoder);
47  }
48 
50 
52  void setLogin(const char* ssid, const char* password) {
53  url.setPassword(password);
54  url.setSSID(ssid);
55  }
56 
57  bool begin() {
58  if (!p_decoder) {
59  DlnaLogger.log(DlnaError, "No decoder set");
60  return false;
61  }
62  if (!p_stream && !p_out && !p_print) {
63  DlnaLogger.log(DlnaError, "No output stream set");
64  return false;
65  }
66 
67  dec_stream.setDecoder(p_decoder);
68  // Connect components
69  // Set up audio pipeline
70  if (pipeline.size() == 0) {
71  pipeline.add(dec_stream);
72  pipeline.add(volume);
73  }
74  // Add output stream
75  if (p_stream) {
76  pipeline.setOutput(*p_stream);
77  } else if (p_out) {
78  pipeline.setOutput(*p_out);
79  } else if (p_print) {
80  pipeline.setOutput(*p_print);
81  } else {
82  DlnaLogger.log(DlnaError, "No output stream set");
83  return false;
84  }
85  bool result = pipeline.begin();
86 
87  return true;
88  }
89 
90  void end() {
91  // Stop playback and clean up
92  stop();
93  pipeline.end();
94  }
95 
97  bool play(const char* urlStr) {
98  if (urlStr == nullptr) return false;
99  DlnaLogger.log(DlnaInfo, "Playing URL: %s", urlStr);
100 
101  // Stop any current playback
102  stop();
103 
104  // Start network stream
105  if (!url.begin(urlStr)) {
106  DlnaLogger.log(DlnaError, "Failed to open URL");
107  return false;
108  }
109 
110  playing = true;
111  start_time = millis();
112  return true;
113  }
114 
115  bool stop() {
116  playing = false;
117  return true;
118  }
119 
120  bool pause() {
121  if (!playing) return false;
122  playing = false;
123  return true;
124  }
125 
126  bool resume() {
127  playing = true;
128  return true;
129  }
130 
132  bool setVolume(uint8_t volumePercent) {
133  if (volumePercent > 100) volumePercent = 100;
134  float volumeFloat = volumePercent / 100.0;
135  volume.setVolume(volumeFloat);
136  current_volume = volumePercent;
137  return true;
138  }
139 
141  uint8_t getVolume() { return current_volume; }
142 
143  bool setMute(bool mute) {
144  is_muted = mute;
145  if (mute) {
146  volume.setVolume(0);
147  } else {
148  volume.setVolume(current_volume / 100.0);
149  }
150  return true;
151  }
152 
153  bool isMuted() { return is_muted; }
154 
155  bool isPlaying() { return playing; }
156 
157  bool isPaused() { return !playing; }
158 
159  bool seek(unsigned long position) {
160  // Not fully supported with most streams
161  DlnaLogger.log(DlnaWarning, "Seek not fully supported");
162  return false;
163  }
164 
165  unsigned long getPosition() {
166  // Estimate position
167  return millis() - start_time;
168  }
169 
170  unsigned long getDuration() {
171  return 0; // Unknown for most streams
172  }
173 
174  void setOutput(AudioStream& out) { p_stream = &out; }
175  void setOutput(AudioOutput& out) { p_out = &out; }
176  void setOutput(Print& out) { p_print = &out; }
177  void setDecoder(AudioDecoder& decoder) { this->p_decoder = &decoder; }
178 
179  size_t copy() {
180  // Call this in your main loop
181  size_t bytes = 0;
182  if (playing) {
183  bytes = copier.copy();
184  } else {
185  delay(5);
186  }
187  return bytes;
188  }
189 
191  void loop() { copy(); }
192 
193  void setupServices(HttpServer& server, IUDPService& udp) {
194  setupServicesImpl(&server);
195  }
196 
197  protected:
198  URLStream url;
199  Pipeline pipeline;
200  AudioDecoder* p_decoder = nullptr;
201  EncodedAudioStream dec_stream;
202  VolumeStream volume;
203  AudioStream* p_stream = nullptr;
204  AudioOutput* p_out = nullptr;
205  Print* p_print = nullptr;
206  StreamCopy copier{pipeline, url};
207  uint8_t current_volume = 50;
208  bool is_muted = false;
209  bool playing = false;
210  unsigned long start_time = 0;
211  const char* st = "urn:schemas-upnp-org:device:MediaRenderer:1";
212  const char* usn = "uuid:09349455-2941-4cf7-9847-1dd5ab210e97";
213 
214  static const char* reply() {
215  static const char* result =
216  "<s:Envelope "
217  "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/"
218  "\"><s:Body><u:%1 "
219  "xmlns:u=\"urn:schemas-upnp-org:service:%2:"
220  "1\"></u:%1></s:Body></s:Envelope>";
221  return result;
222  }
223 
224  // Transport control handler
225  static void transportControlCB(HttpServer* server, const char* requestPath,
227  // Parse SOAP request and extract action
228  Str soap = server->contentStr();
229  DlnaLogger.log(DlnaInfo, "Transport Control: %s", soap.c_str());
230  MediaRenderer& media_renderer = *((MediaRenderer*)hl->context[0]);
231 
232  Str reply_str{reply()};
233  reply_str.replaceAll("%2", "AVTransport");
234  if (soap.indexOf("Play") >= 0) {
235  media_renderer.resume();
236  reply_str.replaceAll("%1", "PlayResponse");
237  server->reply("text/xml", reply_str.c_str());
238  } else if (soap.indexOf("Pause") >= 0) {
239  media_renderer.pause();
240  reply_str.replaceAll("%1", "PauseResponse");
241  server->reply("text/xml", reply_str.c_str());
242  } else if (soap.indexOf("Stop") >= 0) {
243  media_renderer.stop();
244  reply_str.replaceAll("%1", "StopResponse");
245  server->reply("text/xml", reply_str.c_str());
246  } else if (soap.indexOf("SetAVTransportURI") >= 0) {
247  // Extract URL from SOAP request
248  int urlStart = soap.indexOf("<CurrentURI>") + 12;
249  int urlEnd = soap.indexOf("</CurrentURI>");
250  Str url = soap.substring(urlStart, urlEnd);
251  media_renderer.play(url.c_str());
252  reply_str.replaceAll("%1", "SetAVTransportURIResponse");
253  server->reply("text/xml", reply_str.c_str());
254  } else {
255  // Handle other transport controls
256  reply_str.replaceAll("%1", "ActionResponse");
257  server->reply("text/xml", reply_str.c_str());
258  }
259  }
260 
261  // Rendering control handler
262  static void renderingControlCB(HttpServer* server, const char* requestPath,
264  // Parse SOAP request and extract action
265  MediaRenderer& media_renderer = *((MediaRenderer*)hl->context[0]);
266  Str soap = server->contentStr();
267  DlnaLogger.log(DlnaInfo, "Rendering Control: %s", soap.c_str());
268  Str reply_str{reply()};
269  reply_str.replaceAll("%2", "RenderingControl");
270 
271  if (soap.indexOf("SetVolume") >= 0) {
272  // Extract volume from SOAP request
273  int volStart = soap.indexOf("<DesiredVolume>") + 15;
274  int volEnd = soap.indexOf("</DesiredVolume>");
275  int volume = soap.substring(volStart, volEnd).toInt();
276  media_renderer.setVolume(volume);
277  reply_str.replaceAll("%1", "SetVolumeResponse");
278  server->reply("text/xml", reply_str.c_str());
279  } else if (soap.indexOf("SetMute") >= 0) {
280  // Extract mute state from SOAP request
281  int muteStart = soap.indexOf("<DesiredMute>") + 13;
282  int muteEnd = soap.indexOf("</DesiredMute>");
283  bool mute = (soap.substring(muteStart, muteEnd) == "1");
284  media_renderer.setMute(mute);
285  reply_str.replaceAll("%1", "SetMuteResponse");
286  server->reply("text/xml", reply_str.c_str());
287  } else {
288  // Handle other rendering controls
289  reply_str.replaceAll("%1", "RenderingControl");
290  server->reply("text/xml", reply_str.c_str());
291  }
292  };
293 
295  DlnaLogger.log(DlnaInfo, "MediaRenderer::setupServices");
296 
297  auto transportCB = [](HttpServer* server, const char* requestPath,
299  server->reply("text/xml", transport_xml);
300  };
301 
302  auto connmgrCB = [](HttpServer* server, const char* requestPath,
304  server->reply("text/xml", connmgr_xml);
305  };
306 
307  auto controlCB = [](HttpServer* server, const char* requestPath,
309  server->reply("text/xml", control_xml);
310  };
311 
312  // define services
313  DLNAServiceInfo rc, cm, avt;
314  avt.setup("urn:schemas-upnp-org:service:AVTransport:1",
315  "urn:upnp-org:serviceId:AVTransport", "/AVT/service.xml",
316  transportCB, "/AVT/control", transportControlCB, "/AVT/event",
317  [](HttpServer* server, const char* requestPath,
318  HttpRequestHandlerLine* hl) { server->replyOK(); });
319 
320  cm.setup(
321  "urn:schemas-upnporg:service:ConnectionManager:1",
322  "urn:upnp-org:serviceId:ConnectionManager", "/CM/service.xml",
323  connmgrCB, "/CM/control",
324  [](HttpServer* server, const char* requestPath,
325  HttpRequestHandlerLine* hl) { server->replyOK(); },
326  "/CM/event",
327  [](HttpServer* server, const char* requestPath,
328  HttpRequestHandlerLine* hl) { server->replyOK(); });
329 
330  rc.setup("urn:schemas-upnporg:service:RenderingControl:1",
331  "urn:upnp-org:serviceId:RenderingControl", "/RC/service.xml",
332  controlCB, "/RC/control", renderingControlCB, "/RC/event",
333  [](HttpServer* server, const char* requestPath,
334  HttpRequestHandlerLine* hl) { server->replyOK(); });
335 
336  addService(rc);
337  addService(cm);
338  addService(avt);
339  }
340 };
341 
342 } // namespace tiny_dlna
Device Attributes and generation of XML using urn:schemas-upnp-org:device-1-0. We could just return a...
Definition: DLNADevice.h:27
void setDeviceType(const char *st)
Definition: DLNADevice.h:44
void setSerialNumber(const char *sn)
Definition: DLNADevice.h:104
void setBaseURL(const char *url)
Defines the base url.
Definition: DLNADevice.h:55
void setFriendlyName(const char *name)
Definition: DLNADevice.h:92
void setManufacturer(const char *man)
Definition: DLNADevice.h:94
void setModelNumber(const char *number)
Definition: DLNADevice.h:102
void addService(DLNAServiceInfo s)
Adds a service defintion.
Definition: DLNADevice.h:110
void setModelName(const char *name)
Definition: DLNADevice.h:100
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:452
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:20
AudioStream * p_stream
Definition: MediaRenderer.h:203
size_t copy()
Definition: MediaRenderer.h:179
bool play(const char *urlStr)
Defines and opens the URL to be played.
Definition: MediaRenderer.h:97
void loop()
loop called by DeviceMgr
Definition: MediaRenderer.h:191
void setOutput(AudioStream &out)
Definition: MediaRenderer.h:174
Pipeline pipeline
Definition: MediaRenderer.h:199
MediaRenderer(Print &out, AudioDecoder &decoder)
Definition: MediaRenderer.h:44
bool setVolume(uint8_t volumePercent)
Defines the volume in percent (0-100)
Definition: MediaRenderer.h:132
void setLogin(const char *ssid, const char *password)
Provides the optional login information.
Definition: MediaRenderer.h:52
static void transportControlCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Definition: MediaRenderer.h:225
bool resume()
Definition: MediaRenderer.h:126
bool stop()
Definition: MediaRenderer.h:115
void setOutput(Print &out)
Definition: MediaRenderer.h:176
void setupServicesImpl(HttpServer *server)
Definition: MediaRenderer.h:294
AudioDecoder * p_decoder
Definition: MediaRenderer.h:200
bool is_muted
Definition: MediaRenderer.h:208
void setOutput(AudioOutput &out)
Definition: MediaRenderer.h:175
~MediaRenderer()
Definition: MediaRenderer.h:49
MediaRenderer(AudioOutput &out, AudioDecoder &decoder)
Definition: MediaRenderer.h:39
uint8_t getVolume()
Returns the volume in percent (0-100)
Definition: MediaRenderer.h:141
static const char * reply()
Definition: MediaRenderer.h:214
bool seek(unsigned long position)
Definition: MediaRenderer.h:159
MediaRenderer(AudioStream &out, AudioDecoder &decoder)
Definition: MediaRenderer.h:34
void setDecoder(AudioDecoder &decoder)
Definition: MediaRenderer.h:177
AudioOutput * p_out
Definition: MediaRenderer.h:204
bool setMute(bool mute)
Definition: MediaRenderer.h:143
void end()
Definition: MediaRenderer.h:90
MediaRenderer()
Definition: MediaRenderer.h:22
uint8_t current_volume
Definition: MediaRenderer.h:207
unsigned long getDuration()
Definition: MediaRenderer.h:170
void setupServices(HttpServer &server, IUDPService &udp)
Definition: MediaRenderer.h:193
bool begin()
Definition: MediaRenderer.h:57
bool playing
Definition: MediaRenderer.h:209
unsigned long getPosition()
Definition: MediaRenderer.h:165
const char * st
Definition: MediaRenderer.h:211
Print * p_print
Definition: MediaRenderer.h:205
VolumeStream volume
Definition: MediaRenderer.h:202
EncodedAudioStream dec_stream
Definition: MediaRenderer.h:201
bool isPlaying()
Definition: MediaRenderer.h:155
const char * usn
Definition: MediaRenderer.h:212
URLStream url
Definition: MediaRenderer.h:198
static void renderingControlCB(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *hl)
Definition: MediaRenderer.h:262
bool isPaused()
Definition: MediaRenderer.h:157
StreamCopy copier
Definition: MediaRenderer.h:206
bool pause()
Definition: MediaRenderer.h:120
unsigned long start_time
Definition: MediaRenderer.h:210
bool isMuted()
Definition: MediaRenderer.h:153
virtual int indexOf(const char c, int start=0)
Definition: StrView.h:269
int toInt()
Converts the string to an int.
Definition: StrView.h:589
String implementation which keeps the data on the heap. We grow the allocated memory only if the copy...
Definition: Str.h:22
Str substring(int start, int end)
copies a substring into the current string
Definition: Str.h:196
const char * c_str()
provides the string value as const char*
Definition: Str.h:188
const char connmgr_xml[]
Definition: conmgr.h:1
const char * control_xml
Rendering control, controls volume, mute, and other rendering settings.
Definition: control.h:2
Definition: Allocator.h:6
@ DlnaInfo
Definition: Logger.h:16
@ DlnaWarning
Definition: Logger.h:16
@ DlnaError
Definition: Logger.h:16
LoggerClass DlnaLogger
Definition: Logger.cpp:5
const char transport_xml[]
Definition: transport.h:3