<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<meta name="generator" content="AsciiDoc 8.6.7" />
<title>Managing multiroom audio with Linn Songcast and upmpdcli</title>
<style type="text/css">
/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
/* Default font. */
body {
font-family: Georgia,serif;
}
/* Title font. */
h1, h2, h3, h4, h5, h6,
div.title, caption.title,
thead, p.table.header,
#toctitle,
#author, #revnumber, #revdate, #revremark,
#footer {
font-family: Arial,Helvetica,sans-serif;
}
body {
margin: 1em 5% 1em 5%;
}
a {
color: blue;
text-decoration: underline;
}
a:visited {
color: fuchsia;
}
em {
font-style: italic;
color: navy;
}
strong {
font-weight: bold;
color: #083194;
}
h1, h2, h3, h4, h5, h6 {
color: #527bbd;
margin-top: 1.2em;
margin-bottom: 0.5em;
line-height: 1.3;
}
h1, h2, h3 {
border-bottom: 2px solid silver;
}
h2 {
padding-top: 0.5em;
}
h3 {
float: left;
}
h3 + * {
clear: left;
}
h5 {
font-size: 1.0em;
}
div.sectionbody {
margin-left: 0;
}
hr {
border: 1px solid silver;
}
p {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
ul, ol, li > p {
margin-top: 0;
}
ul > li { color: #aaa; }
ul > li > * { color: black; }
pre {
padding: 0;
margin: 0;
}
#author {
color: #527bbd;
font-weight: bold;
font-size: 1.1em;
}
#email {
}
#revnumber, #revdate, #revremark {
}
#footer {
font-size: small;
border-top: 2px solid silver;
padding-top: 0.5em;
margin-top: 4.0em;
}
#footer-text {
float: left;
padding-bottom: 0.5em;
}
#footer-badges {
float: right;
padding-bottom: 0.5em;
}
#preamble {
margin-top: 1.5em;
margin-bottom: 1.5em;
}
div.imageblock, div.exampleblock, div.verseblock,
div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
div.admonitionblock {
margin-top: 1.0em;
margin-bottom: 1.5em;
}
div.admonitionblock {
margin-top: 2.0em;
margin-bottom: 2.0em;
margin-right: 10%;
color: #606060;
}
div.content { /* Block element content. */
padding: 0;
}
/* Block element titles. */
div.title, caption.title {
color: #527bbd;
font-weight: bold;
text-align: left;
margin-top: 1.0em;
margin-bottom: 0.5em;
}
div.title + * {
margin-top: 0;
}
td div.title:first-child {
margin-top: 0.0em;
}
div.content div.title:first-child {
margin-top: 0.0em;
}
div.content + div.title {
margin-top: 0.0em;
}
div.sidebarblock > div.content {
background: #ffffee;
border: 1px solid #dddddd;
border-left: 4px solid #f0f0f0;
padding: 0.5em;
}
div.listingblock > div.content {
border: 1px solid #dddddd;
border-left: 5px solid #f0f0f0;
background: #f8f8f8;
padding: 0.5em;
}
div.quoteblock, div.verseblock {
padding-left: 1.0em;
margin-left: 1.0em;
margin-right: 10%;
border-left: 5px solid #f0f0f0;
color: #888;
}
div.quoteblock > div.attribution {
padding-top: 0.5em;
text-align: right;
}
div.verseblock > pre.content {
font-family: inherit;
font-size: inherit;
}
div.verseblock > div.attribution {
padding-top: 0.75em;
text-align: left;
}
/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
div.verseblock + div.attribution {
text-align: left;
}
div.admonitionblock .icon {
vertical-align: top;
font-size: 1.1em;
font-weight: bold;
text-decoration: underline;
color: #527bbd;
padding-right: 0.5em;
}
div.admonitionblock td.content {
padding-left: 0.5em;
border-left: 3px solid #dddddd;
}
div.exampleblock > div.content {
border-left: 3px solid #dddddd;
padding-left: 0.5em;
}
div.imageblock div.content { padding-left: 0; }
span.image img { border-style: none; }
a.image:visited { color: white; }
dl {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
dt {
margin-top: 0.5em;
margin-bottom: 0;
font-style: normal;
color: navy;
}
dd > *:first-child {
margin-top: 0.1em;
}
ul, ol {
list-style-position: outside;
}
ol.arabic {
list-style-type: decimal;
}
ol.loweralpha {
list-style-type: lower-alpha;
}
ol.upperalpha {
list-style-type: upper-alpha;
}
ol.lowerroman {
list-style-type: lower-roman;
}
ol.upperroman {
list-style-type: upper-roman;
}
div.compact ul, div.compact ol,
div.compact p, div.compact p,
div.compact div, div.compact div {
margin-top: 0.1em;
margin-bottom: 0.1em;
}
tfoot {
font-weight: bold;
}
td > div.verse {
white-space: pre;
}
div.hdlist {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
div.hdlist tr {
padding-bottom: 15px;
}
dt.hdlist1.strong, td.hdlist1.strong {
font-weight: bold;
}
td.hdlist1 {
vertical-align: top;
font-style: normal;
padding-right: 0.8em;
color: navy;
}
td.hdlist2 {
vertical-align: top;
}
div.hdlist.compact tr {
margin: 0;
padding-bottom: 0;
}
.comment {
background: yellow;
}
.footnote, .footnoteref {
font-size: 0.8em;
}
span.footnote, span.footnoteref {
vertical-align: super;
}
#footnotes {
margin: 20px 0 20px 0;
padding: 7px 0 0 0;
}
#footnotes div.footnote {
margin: 0 0 5px 0;
}
#footnotes hr {
border: none;
border-top: 1px solid silver;
height: 1px;
text-align: left;
margin-left: 0;
width: 20%;
min-width: 100px;
}
div.colist td {
padding-right: 0.5em;
padding-bottom: 0.3em;
vertical-align: top;
}
div.colist td img {
margin-top: 0.3em;
}
@media print {
#footer-badges { display: none; }
}
#toc {
margin-bottom: 2.5em;
}
#toctitle {
color: #527bbd;
font-size: 1.1em;
font-weight: bold;
margin-top: 1.0em;
margin-bottom: 0.1em;
}
div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
margin-top: 0;
margin-bottom: 0;
}
div.toclevel2 {
margin-left: 2em;
font-size: 0.9em;
}
div.toclevel3 {
margin-left: 4em;
font-size: 0.9em;
}
div.toclevel4 {
margin-left: 6em;
font-size: 0.9em;
}
span.aqua { color: aqua; }
span.black { color: black; }
span.blue { color: blue; }
span.fuchsia { color: fuchsia; }
span.gray { color: gray; }
span.green { color: green; }
span.lime { color: lime; }
span.maroon { color: maroon; }
span.navy { color: navy; }
span.olive { color: olive; }
span.purple { color: purple; }
span.red { color: red; }
span.silver { color: silver; }
span.teal { color: teal; }
span.white { color: white; }
span.yellow { color: yellow; }
span.aqua-background { background: aqua; }
span.black-background { background: black; }
span.blue-background { background: blue; }
span.fuchsia-background { background: fuchsia; }
span.gray-background { background: gray; }
span.green-background { background: green; }
span.lime-background { background: lime; }
span.maroon-background { background: maroon; }
span.navy-background { background: navy; }
span.olive-background { background: olive; }
span.purple-background { background: purple; }
span.red-background { background: red; }
span.silver-background { background: silver; }
span.teal-background { background: teal; }
span.white-background { background: white; }
span.yellow-background { background: yellow; }
span.big { font-size: 2em; }
span.small { font-size: 0.6em; }
span.underline { text-decoration: underline; }
span.overline { text-decoration: overline; }
span.line-through { text-decoration: line-through; }
div.unbreakable { page-break-inside: avoid; }
/*
* xhtml11 specific
*
* */
tt {
font-family: "Courier New", Courier, monospace;
font-size: inherit;
color: navy;
}
div.tableblock {
margin-top: 1.0em;
margin-bottom: 1.5em;
}
div.tableblock > table {
border: 3px solid #527bbd;
}
thead, p.table.header {
font-weight: bold;
color: #527bbd;
}
p.table {
margin-top: 0;
}
/* Because the table frame attribute is overriden by CSS in most browsers. */
div.tableblock > table[frame="void"] {
border-style: none;
}
div.tableblock > table[frame="hsides"] {
border-left-style: none;
border-right-style: none;
}
div.tableblock > table[frame="vsides"] {
border-top-style: none;
border-bottom-style: none;
}
/*
* html5 specific
*
* */
.monospaced {
font-family: "Courier New", Courier, monospace;
font-size: inherit;
color: navy;
}
table.tableblock {
margin-top: 1.0em;
margin-bottom: 1.5em;
}
thead, p.tableblock.header {
font-weight: bold;
color: #527bbd;
}
p.tableblock {
margin-top: 0;
}
table.tableblock {
border-width: 3px;
border-spacing: 0px;
border-style: solid;
border-color: #527bbd;
border-collapse: collapse;
}
th.tableblock, td.tableblock {
border-width: 1px;
padding: 4px;
border-style: solid;
border-color: #527bbd;
}
table.tableblock.frame-topbot {
border-left-style: hidden;
border-right-style: hidden;
}
table.tableblock.frame-sides {
border-top-style: hidden;
border-bottom-style: hidden;
}
table.tableblock.frame-none {
border-style: hidden;
}
th.tableblock.halign-left, td.tableblock.halign-left {
text-align: left;
}
th.tableblock.halign-center, td.tableblock.halign-center {
text-align: center;
}
th.tableblock.halign-right, td.tableblock.halign-right {
text-align: right;
}
th.tableblock.valign-top, td.tableblock.valign-top {
vertical-align: top;
}
th.tableblock.valign-middle, td.tableblock.valign-middle {
vertical-align: middle;
}
th.tableblock.valign-bottom, td.tableblock.valign-bottom {
vertical-align: bottom;
}
/*
* manpage specific
*
* */
body.manpage h1 {
padding-top: 0.5em;
padding-bottom: 0.5em;
border-top: 2px solid silver;
border-bottom: 2px solid silver;
}
body.manpage h2 {
border-style: none;
}
body.manpage div.sectionbody {
margin-left: 3em;
}
@media print {
body.manpage div#toc { display: none; }
}
</style>
<script type="text/javascript">
/*<![CDATA[*/
var asciidoc = { // Namespace.
/////////////////////////////////////////////////////////////////////
// Table Of Contents generator
/////////////////////////////////////////////////////////////////////
/* Author: Mihai Bazon, September 2002
* http://students.infoiasi.ro/~mishoo
*
* Table Of Content generator
* Version: 0.4
*
* Feel free to use this script under the terms of the GNU General Public
* License, as long as you do not remove or alter this notice.
*/
/* modified by Troy D. Hanson, September 2006. License: GPL */
/* modified by Stuart Rackham, 2006, 2009. License: GPL */
// toclevels = 1..4.
toc: function (toclevels) {
function getText(el) {
var text = "";
for (var i = el.firstChild; i != null; i = i.nextSibling) {
if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
text += i.data;
else if (i.firstChild != null)
text += getText(i);
}
return text;
}
function TocEntry(el, text, toclevel) {
this.element = el;
this.text = text;
this.toclevel = toclevel;
}
function tocEntries(el, toclevels) {
var result = new Array;
var re = new RegExp('[hH]([1-'+(toclevels+1)+'])');
// Function that scans the DOM tree for header elements (the DOM2
// nodeIterator API would be a better technique but not supported by all
// browsers).
var iterate = function (el) {
for (var i = el.firstChild; i != null; i = i.nextSibling) {
if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
var mo = re.exec(i.tagName);
if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
}
iterate(i);
}
}
}
iterate(el);
return result;
}
var toc = document.getElementById("toc");
if (!toc) {
return;
}
// Delete existing TOC entries in case we're reloading the TOC.
var tocEntriesToRemove = [];
var i;
for (i = 0; i < toc.childNodes.length; i++) {
var entry = toc.childNodes[i];
if (entry.nodeName.toLowerCase() == 'div'
&& entry.getAttribute("class")
&& entry.getAttribute("class").match(/^toclevel/))
tocEntriesToRemove.push(entry);
}
for (i = 0; i < tocEntriesToRemove.length; i++) {
toc.removeChild(tocEntriesToRemove[i]);
}
// Rebuild TOC entries.
var entries = tocEntries(document.getElementById("content"), toclevels);
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
if (entry.element.id == "")
entry.element.id = "_toc_" + i;
var a = document.createElement("a");
a.href = "#" + entry.element.id;
a.appendChild(document.createTextNode(entry.text));
var div = document.createElement("div");
div.appendChild(a);
div.className = "toclevel" + entry.toclevel;
toc.appendChild(div);
}
if (entries.length == 0)
toc.parentNode.removeChild(toc);
},
/////////////////////////////////////////////////////////////////////
// Footnotes generator
/////////////////////////////////////////////////////////////////////
/* Based on footnote generation code from:
* http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
*/
footnotes: function () {
// Delete existing footnote entries in case we're reloading the footnodes.
var i;
var noteholder = document.getElementById("footnotes");
if (!noteholder) {
return;
}
var entriesToRemove = [];
for (i = 0; i < noteholder.childNodes.length; i++) {
var entry = noteholder.childNodes[i];
if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute("class") == "footnote")
entriesToRemove.push(entry);
}
for (i = 0; i < entriesToRemove.length; i++) {
noteholder.removeChild(entriesToRemove[i]);
}
// Rebuild footnote entries.
var cont = document.getElementById("content");
var spans = cont.getElementsByTagName("span");
var refs = {};
var n = 0;
for (i=0; i<spans.length; i++) {
if (spans[i].className == "footnote") {
n++;
var note = spans[i].getAttribute("data-note");
if (!note) {
// Use [\s\S] in place of . so multi-line matches work.
// Because JavaScript has no s (dotall) regex flag.
note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
spans[i].innerHTML =
"[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
"' title='View footnote' class='footnote'>" + n + "</a>]";
spans[i].setAttribute("data-note", note);
}
noteholder.innerHTML +=
"<div class='footnote' id='_footnote_" + n + "'>" +
"<a href='#_footnoteref_" + n + "' title='Return to text'>" +
n + "</a>. " + note + "</div>";
var id =spans[i].getAttribute("id");
if (id != null) refs["#"+id] = n;
}
}
if (n == 0)
noteholder.parentNode.removeChild(noteholder);
else {
// Process footnoterefs.
for (i=0; i<spans.length; i++) {
if (spans[i].className == "footnoteref") {
var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
href = href.match(/#.*/)[0]; // Because IE return full URL.
n = refs[href];
spans[i].innerHTML =
"[<a href='#_footnote_" + n +
"' title='View footnote' class='footnote'>" + n + "</a>]";
}
}
}
},
install: function(toclevels) {
var timerId;
function reinstall() {
asciidoc.footnotes();
if (toclevels) {
asciidoc.toc(toclevels);
}
}
function reinstallAndRemoveTimer() {
clearInterval(timerId);
reinstall();
}
timerId = setInterval(reinstall, 500);
if (document.addEventListener)
document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
else
window.onload = reinstallAndRemoveTimer;
}
}
asciidoc.install();
/*]]>*/
</script>
</head>
<body class="article">
<div id="header">
<h1>Managing multiroom audio with Linn Songcast and upmpdcli</h1>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph"><p>General information about <strong>upmpdcli</strong> support for <strong>Songcast</strong> can be found
<a href="sc2mpd.html">here</a>. This page explains how to set up a multiroom
synchronized audio system using <strong>upmpdcli</strong> and <strong>Songcast</strong>.</p></div>
<div class="admonitionblock">
<table><tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">multicast and WIFI don’t mix well in many cases. If you have
wireless Receivers experiencing sound drop issues, try selecting unicast in
the Songcast advanced configuration panel on the desktop.</td>
</tr></table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_multiple_receivers">Multiple Receivers</h2>
<div class="sectionbody">
<div class="paragraph"><p>Multiple <strong>Songcast</strong> <em>Receiver</em> hosts can connect to the same <em>Sender</em>, so
that they will all be playing the same audio. They form a group only in the
sense that they play from the same URI, and are not otherwise linked.</p></div>
<div class="paragraph"><p>The Mac and Windows <strong>Songcast</strong> apps only let you activate one
<em>Receiver</em> though.</p></div>
<div class="paragraph"><p>The <strong>upmpdcli</strong> package now includes the <strong>scctl</strong> command line utility, which
can list the state of the local <strong>Songcast</strong> <em>Receivers</em>, make a <em>Receiver</em>
play from the same URI as another one (for building multi-room groups), or
return a <em>Media Renderer</em> from <em>Receiver</em> to normal operation.</p></div>
<div class="paragraph"><p>As the Songcast application is only available on Windows or Mac desktops,
it would be inconvenient to have to access the Linux command line to
control the multi-room groups, and the <strong>upmpdcli</strong> package also includes a
small Web application which can be accessed from a desktop web browser to
control the groups.</p></div>
<div class="paragraph"><p>This has only be tested with <strong>upmpdcli</strong> and its <a href="sc2mpd.html">sc2mpd</a>
<strong>Songcast</strong> auxiliary process as Receiver implementation, but I’d guess that
there is a good chance it would work with others.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_synchronisation_issues">Synchronisation issues</h2>
<div class="sectionbody">
<div class="paragraph"><p>The short version is: all <strong>sc2mpd</strong> instances must be configured to play
directly to Alsa. See the <a href="sc2mpd.html#Configuration">configuration
section</a>.</p></div>
<div class="paragraph"><p>Longer version: <strong>Songcast</strong> is a real-time audio stream. As the <em>Sender</em> and
<em>Receiver</em> sample clocks (the 44.1 or 48 KHz clocks) are independant, audio
reproduction on the two systems will slowly drift. If nothing is done,
after a time, the <em>Receiver</em> will have to skip samples or add a period of
silence (depending if its clock is slower or faster), which is quite
audible and ennoying, and will happen "from time to time", depending of how
much the clocks differ.</p></div>
<div class="paragraph"><p>The only way to control this is to adjust the rate of reproduction on the
<em>Receiver</em>, which can be done in two ways:</p></div>
<div class="ulist"><ul>
<li>
<p>
<strong>Linn</strong> hardware uses timestamps embedded in the audio stream to adjust
their hardware sample clock.
</p>
</li>
<li>
<p>
<strong>sc2mpd</strong> in <em><tt>alsa</tt></em> mode uses sample rate conversion to adjust the stream.
</p>
</li>
</ul></div>
<div class="paragraph"><p>This is not specific to <strong>Songcast</strong> of course, all real time audio network
transports have to do something similar.</p></div>
<div class="paragraph"><p>Independantly of the clock issue, all <em>Receivers</em> should use approximately
the same amount of buffering for the audio to be reasonably synchronous
(with no more shifts than moving around would produce anyway). This is
impossible to achieve when going through mpd, and the second reason why
<strong>sc2mpd</strong> must be set in <em><tt>alsa</tt></em> mode for multiroom setups. In <em><tt>mpd</tt></em>
mode, the <em>Receivers</em> can be out of sync by several seconds.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_setting_things_up">Setting things up</h2>
<div class="sectionbody">
<div class="ulist"><ul>
<li>
<p>
Install upmpdcli and sc2mpd on each of the <em>Receiver</em> systems. Edit
<tt>/etc/upmpdcli.conf</tt> to set:
</p>
<div class="ulist"><ul>
<li>
<p>
<tt>friendlyname</tt>, this is quite useful when managing several systems.
</p>
</li>
<li>
<p>
<tt>scplaymethod</tt> = <em><tt>alsa</tt></em>
</p>
</li>
<li>
<p>
<tt>scalsadevice</tt>: use <tt>aplay -L</tt> to chose an appropriate device.
</p>
</li>
</ul></div>
</li>
<li>
<p>
Activate the web interface on one of the <em>Receivers</em> (or on any machine
with upmpdcli installed actually). Edit <tt>/etc/default/scweb</tt> to
configure the interface (see comments in there) and start it with
<tt>service scweb start</tt>.
</p>
</li>
<li>
<p>
Activate a <em>Receiver</em> from the PC <strong>Songcast</strong> interface. Play something and
leave it playing.
</p>
</li>
<li>
<p>
Connect to the Web interface (host and port chosen above) with a
browser, and use it to list, activate, or disconnect the <em>Receivers</em>.
</p>
</li>
</ul></div>
<div class="paragraph"><p>Once the slave <em>Receivers</em> are associated with the <em>Sender</em>, they should stay
in this state until you change it. So you can stop/start <strong>Songcast</strong> on the
PC, and they will usually just follow.</p></div>
<div class="paragraph"><p>An "associated" <em>Receiver</em> is just one which plays from the same URI, it
keeps no other relation to the "Master". Only one <em>Receiver</em> is a bit special
because it is the one known from the PC, but there is no specific reason to
use it as Master, the Master is only used to get the URI. Avoid changing
the state of the "PC"'s <em>Receiver</em> from outside the PC <strong>Songcast</strong> interface,
this can only confuse things.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_more_detail_about_the_web_interface">More detail about the Web interface</h2>
<div class="sectionbody">
<div class="paragraph"><p>To avoid having to access the command line interface to control the
<strong>Songcast</strong> groups, <strong>upmpdcli</strong> comes with a small Web server which uses
<strong>scctl</strong> to actually do the work. This is found inside the <tt>web/</tt>
subdirectory inside the <strong>upmpdcli</strong> source tree.</p></div>
<div class="paragraph"><p>The server is based on the
<a href="http://bottlepy.org/docs/dev/index.html">Bottle Python Web Framework</a>
and it only depends on Python (version 2 and 3 are supported by <strong>Bottle</strong>,
but the current app only works with Python 2).</p></div>
<div class="paragraph"><p>You can use the <tt>scweb-standalone.py</tt> script to manually start the
interface:</p></div>
<div class="literalblock">
<div class="content">
<pre><tt>python2 ./scweb-standalone.py</tt></pre>
</div></div>
<div class="paragraph"><p>This will start a server on localhost, on port 8680 by default which is
good for testing, but not very useful. Use the -a 0.0.0.0 option to let the
server answer on all local addresses (or specify the address to use a
specific interface):</p></div>
<div class="literalblock">
<div class="content">
<pre><tt>python2 ./scweb-standalone.py -a 0.0.0.0</tt></pre>
</div></div>
<div class="paragraph"><p>-p can be used to specify a port.</p></div>
<div class="paragraph"><p>Once started, connecting to the server from any browser should hopefully
display a reasonably self-explanatory interface.</p></div>
<div class="paragraph"><p>Recent <strong>upmpdcli</strong> packages install the web app as a service named
<strong>scweb</strong>. The service is not started by default though, you need to edit
<tt>/etc/default/scweb</tt>.</p></div>
</div>
</div>
</div>
<div id="footnotes"><hr /></div>
<div id="footer">
<div id="footer-text">
Last updated 2015-05-19 16:15:38 CEST
</div>
</div>
</body>
</html>