<!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 class="sect1">
<h2 id="_what_is_linn_songcast">What is Linn Songcast</h2>
<div class="sectionbody">
<div class="paragraph"><p><strong>Songcast</strong> is a protocol for transporting audio streams across the network,
developped by <a href="http://oss.linn.co.uk/trac/wiki/Songcast">Linn</a> as a
series of open-source libraries and applications.</p></div>
<div class="paragraph"><p>There are two types of entities involved in the protocol:</p></div>
<div class="ulist"><ul>
<li>
<p>
A <strong>Songcast</strong> <em>Sender</em> generates an audio stream.
</p>
</li>
<li>
<p>
A <strong>Songcast</strong> <em>Receiver</em>, which typically runs on an OpenHome Renderer,
receives the stream and plays it.
</p>
</li>
</ul></div>
<div class="paragraph"><p>The streams transported by <strong>Songcast</strong> are actual real-time audio data, which
can go straight to an audio card for playing.</p></div>
<div class="paragraph"><p>Controlling the streams (connecting, starting, stopping) is
done through an UPnP service named OpenHome <em>Receiver</em>, which runs on an
UPnP Media Renderer implementing the OpenHome services.</p></div>
<div class="paragraph"><p>The typical use of <strong>Songcast</strong> is to have an audio driver on a Windows or OS X
desktop capture and forward the audio stream to a remote <strong>Songcast</strong> device
(this is the main purpose of the <strong>Songcast</strong> Mac and Windows applications,
apart from actually controlling the audio destination).</p></div>
<div class="paragraph"><p>Any application on the desktop will (be compelled to) transparently play to
the remote device, without need to know anything about <strong>Songcast</strong>.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_multiple_receivers">Multiple Receivers</h2>
<div class="sectionbody">
<div class="paragraph"><p>Multiple <em>Receiver</em> hosts can connect to the same <em>Sender</em>, so that they
will all be playing the same audio.</p></div>
<div class="paragraph"><p>The Mac and Windows <strong>Songcast</strong> apps only let you connect the <em>Sender</em> to one
Receiver though.</p></div>
<div class="paragraph"><p><strong>upmpdcli</strong> now includes a small application which can list the state of
the local <strong>Songcast</strong> Receivers, make a Receiver play from the same URI as
another one (for building multiroom groups), or return a Media Renderer
from Receiver to normal operation.</p></div>
<div class="paragraph"><p>The functions can be accessed either from the <strong>scctl</strong> command line utility,
or from a local Web application (as Songcast is mostly used from a Windows
or Mac PC, it would be inconvenient to have to access the Linux command
line to control the multiroom 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 class="paragraph"><p>This is very preliminary for now and there are a number of issues, I hope
to improve them, but for now, this is definitely an <em>early adopters</em> system:</p></div>
<div class="ulist"><ul>
<li>
<p>
The initial synchronisation when forming the group is very bad. To
resynchronize everything, stop or pause the playing on the PC (from the
player, e.g. Media Player, not from the Songcast utility), wait for the
audio to drain, and a few seconds more, then restart playing.
</p>
</li>
<li>
<p>
Receivers slowly drift out of sync, especially if they use different
hardware. I intend to work on this, but it’s a difficult issue.
</p>
</li>
<li>
<p>
It’s quite easy to put the system in a confused state where nothing
seems to work any more. Stopping all Receivers (with <strong>scctl</strong> or the Web
interface) and restarting the PC will normally get things back to
sanity, but it will sometimes be necessary to restart everything.
<strong>Always give the commands a little time to take effect</strong>. Especially,
it’s quite common that the audio will not begin to play for around
10 S after activation from the <strong>Songcast</strong> PC interface. Clicking on
stuff too early is the surest way to get into bad states, always give
10 S to the system when things seem to not be happening.
</p>
</li>
<li>
<p>
<em>The following problem seems to be gone from the latest <strong>Songcast</strong>
code</em> (which is used by the current <tt>ohbuild.sh</tt> script). Songcast can
be transported by either unicast or multicast IP. multicast is of
course much better for the network load, but I seem to have seen random
<strong>sc2mpd</strong> crashes with it. I am not sure of the cause (may be not linked
to multicast at all), but if you experience random <strong>sc2mpd</strong> crashes,
switch <strong>Songcast</strong> to unicast (in the PC Songcast app advanced
configuration panel. Unicast is the default).
</p>
</li>
</ul></div>
</div>
</div>
<div class="sect1">
<h2 id="_setting_things_up">Setting things up</h2>
<div class="sectionbody">
<div class="paragraph"><p>The following seems to work for me:</p></div>
<div class="ulist"><ul>
<li>
<p>
Remove libupnpp and upmpdcli packages from the system to avoid confusion
</p>
</li>
<li>
<p>
Clone the libupnpp, upmpdcli and sc2mpd repositories from
<a href="https://github.com/">GitHub</a>
</p>
</li>
<li>
<p>
Follow the usual procedure to build. This should just be the usual for
libupnpp and upmpdcli:
</p>
<div class="literalblock">
<div class="content">
<pre><tt>sh autogen.sh
./configure --prefix=/usr
make
sudo make install</tt></pre>
</div></div>
</li>
<li>
<p>
For sc2mpd, things are a small bit more complicated, see the <em>Building
sc2mpd</em> section in <a href="sc2mpd.html">this document</a>.
</p>
</li>
<li>
<p>
Repeat the above steps on all the machines which you want to be Receivers.
</p>
</li>
<li>
<p>
Activate a Receiver from the PC <strong>Songcast</strong> interface. Play something and
leave it playing.
</p>
</li>
<li>
<p>
Use either <strong>scctl</strong> (<tt>scctl -h</tt> prints a simple help message), or the Web
interface (see further) to associate other Receivers to the same Sender.
</p>
</li>
<li>
<p>
Stop or Pause the music. Wait 10 S, restart. <strong><em>Multiroom !</em></strong>
</p>
</li>
</ul></div>
<div class="paragraph"><p>Once the slave Receivers are associated with the Sender, they should stay
in this state until you change it. So you can stop/start Songcast on the
PC, and they will usually just follow.</p></div>
<div class="paragraph"><p>An "associated" Receiver is just one which plays from the same URI, it
keeps no other relation to the "Master". Only one Receiver 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"l Receiver from outside the PC <strong>Songcast</strong> interface,
this can only confuse things.</p></div>
<div class="paragraph"><p>Every time you change the group configuration, you need to resynchronize
the audio by pausing, waiting, restarting.</p></div>
<div class="paragraph"><p>I do know that the whole thing is not very solid, this is a prototype and I
hope to improve some of the issues in the future: constructive problem
reports are more than welcome, but no flaming (for now) please :)</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_controlling_the_songcast_groups_from_the_web_interface">Controlling the Songcast groups from 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>I’ll find ways to autostart the server in the future, but for now,
use the <tt>scweb-standalone.py</tt> script to manually start it:</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 8777 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>
</div>
</div>
<div id="footnotes"><hr /></div>
<div id="footer">
<div id="footer-text">
Last updated 2015-04-29 14:50:56 CEST
</div>
</div>
</body>
</html>