--- a
+++ b/scctl_src/scctl.cpp
@@ -0,0 +1,410 @@
+/* Copyright (C) 2015 J.F.Dockes
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+///// Songcast UPnP Controller
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include <string>
+#include <iostream>
+#include <vector>
+#include <algorithm>
+using namespace std;
+
+#include "libupnpp/upnpplib.hxx"
+#include "libupnpp/log.hxx"
+#include "libupnpp/upnpputils.hxx"
+#include "libupnpp/ptmutex.hxx"
+#include "libupnpp/control/service.hxx"
+#include "libupnpp/control/mediarenderer.hxx"
+#include "libupnpp/control/renderingcontrol.hxx"
+#include "libupnpp/control/discovery.hxx"
+
+using namespace UPnPClient;
+using namespace UPnPP;
+
+UPnPDeviceDirectory *superdir;
+
+MRDH getRenderer(const string& name)
+{
+ if (superdir == 0) {
+ superdir = UPnPDeviceDirectory::getTheDir();
+ }
+
+ UPnPDeviceDesc ddesc;
+ if (superdir->getDevByUDN(name, ddesc)) {
+ return MRDH(new MediaRenderer(ddesc));
+ } else if (superdir->getDevByFName(name, ddesc)) {
+ return MRDH(new MediaRenderer(ddesc));
+ }
+ cerr << "getDevByFname failed for " << name << endl;
+ return MRDH();
+}
+
+struct SongcastState {
+ string nm;
+ string UDN;
+ enum SCState {SCS_GENERROR, SCS_NOOH, SCS_NOTRECEIVER,
+ SCS_STOPPED, SCS_PLAYING};
+ SCState state;
+ string uri;
+ string meta;
+ int receiverSourceIndex;
+ string reason;
+
+ OHPRH prod;
+ OHRCH rcv;
+
+ SongcastState()
+ : state(SCS_GENERROR), receiverSourceIndex(-1) {
+ }
+
+ void reset() {
+ nm.clear();
+ state = SongcastState::SCS_GENERROR;
+ receiverSourceIndex = -1;
+ reason.clear();
+ uri.clear();
+ meta.clear();
+ prod.reset();
+ rcv.reset();
+ }
+};
+
+void getSongcastState(const string& nm, SongcastState& st, bool live = true)
+{
+ st.reset();
+ st.nm = nm;
+
+ MRDH rdr = getRenderer(nm);
+ if (!rdr) {
+ st.reason = nm + " not a media renderer?";
+ return;
+ }
+ st.nm = rdr->desc()->friendlyName;
+ st.UDN = rdr->desc()->UDN;
+
+ OHPRH prod = rdr->ohpr();
+ if (!prod) {
+ st.state = SongcastState::SCS_NOOH;
+ st.reason = nm + ": device has no OHProduct service";
+ return;
+ }
+ int index;
+ if (prod->sourceIndex(&index)) {
+ st.reason = nm + " : sourceIndex failed";
+ return;
+ }
+
+ vector<OHProduct::Source> sources;
+ if (prod->getSources(sources) || sources.size() == 0) {
+ st.reason = nm + ": getSources failed";
+ return;
+ }
+ unsigned int rcvi = 0;
+ for (; rcvi < sources.size(); rcvi++) {
+ if (!sources[rcvi].name.compare("Receiver"))
+ break;
+ }
+ if (rcvi == sources.size()) {
+ st.state = SongcastState::SCS_NOOH;
+ st.reason = nm + " has no Receiver service";
+ return;
+ }
+ st.receiverSourceIndex = int(rcvi);
+
+ if (index < 0 || index >= int(sources.size())) {
+ st.reason = nm + ": bad index " + SoapHelp::i2s(index) +
+ " not inside sources of size " + SoapHelp::i2s(sources.size());
+ return;
+ }
+
+ // Looks like the device has a receiver service. We may have to return a
+ // handle for it.
+ OHRCH rcv = rdr->ohrc();
+
+ string sname = sources[index].name;
+ if (sname.compare("Receiver")) {
+ st.state = SongcastState::SCS_NOTRECEIVER;
+ st.reason = nm + " not in receiver mode ";
+ goto out;
+ }
+
+ if (!rcv) {
+ st.reason = nm + ": no receiver service??";
+ goto out;
+ }
+ if (rcv->sender(st.uri, st.meta)) {
+ st.reason = nm + ": Receiver::Sender failed";
+ goto out;
+ }
+
+ OHPlaylist::TPState tpst;
+ if (rcv->transportState(&tpst)) {
+ st.reason = nm + ": Receiver::transportState() failed";
+ goto out;
+ }
+
+ if (tpst == OHPlaylist::TPS_Playing) {
+ st.state = SongcastState::SCS_PLAYING;
+ } else {
+ st.state = SongcastState::SCS_STOPPED;
+ }
+out:
+ if (live) {
+ st.prod = prod;
+ st.rcv = rcv;
+ }
+
+ return;
+}
+
+void listReceivers(vector<SongcastState>& vscs)
+{
+ vector<UPnPDeviceDesc> vdds;
+ if (!MediaRenderer::getDeviceDescs(vdds)) {
+ cerr << "listReceivers::getDeviceDescs failed" << endl;
+ return;
+ }
+
+ for (auto& entry : vdds) {
+ SongcastState st;
+ getSongcastState(entry.UDN, st, false);
+ if (st.state == SongcastState::SCS_NOTRECEIVER ||
+ st.state == SongcastState::SCS_PLAYING ||
+ st.state == SongcastState::SCS_STOPPED) {
+ vscs.push_back(st);
+ }
+ }
+}
+
+bool setReceiverPlaying(const string& nm, SongcastState& st,
+ const string& uri, const string& meta)
+{
+ if (!st.rcv || !st.prod) {
+ st.reason = nm + " : null handle ??";
+ return false;
+ }
+ if (st.prod->setSourceIndex(st.receiverSourceIndex)) {
+ st.reason = nm + " : can't set source index to " +
+ SoapHelp::i2s(st.receiverSourceIndex);
+ return false;
+ }
+ if (st.rcv->setSender(uri, meta)) {
+ st.reason = nm + " Receiver::setSender() failed";
+ return false;
+ }
+ if (st.rcv->play()) {
+ st.reason = nm + " Receiver::play() failed";
+ return false;
+ }
+ return true;
+}
+
+bool stopReceiver(const string& nm, SongcastState st)
+{
+ if (!st.rcv || !st.prod) {
+ st.reason = nm + " : null handle ??";
+ return false;
+ }
+ if (st.rcv->stop()) {
+ st.reason = nm + " Receiver::play() failed";
+ return false;
+ }
+ if (st.prod->setSourceIndex(0)) {
+ st.reason = nm + " : can't set source index to " +
+ SoapHelp::i2s(st.receiverSourceIndex);
+ return false;
+ }
+ return true;
+}
+
+void ohSongcast(const string& masterName, const vector<string>& slaves)
+{
+ SongcastState mstate;
+ getSongcastState(masterName, mstate);
+ if (mstate.state != SongcastState::SCS_PLAYING) {
+ cerr << "Required master not in Receiver Playing mode" << endl;
+ return;
+ }
+
+ // Note: sequence sent from windows songcast when setting up a receiver:
+ // Product::SetSourceIndex / Receiver::SetSender / Receiver::Play
+ // When stopping:
+ // Receiver::Stop / Product::SetStandby
+ for (auto& sl: slaves) {
+ cerr << "Setting up " << sl << endl;
+ SongcastState sstate;
+ getSongcastState(sl, sstate);
+
+ switch (sstate.state) {
+ case SongcastState::SCS_GENERROR:
+ case SongcastState::SCS_NOOH:
+ cerr << sl << sstate.reason << endl;
+ continue;
+ case SongcastState::SCS_STOPPED:
+ case SongcastState::SCS_PLAYING:
+ cerr << sl << ": already in receiver mode" << endl;
+ continue;
+ case SongcastState::SCS_NOTRECEIVER:
+ if (setReceiverPlaying(sl, sstate, mstate.uri, mstate.meta)) {
+ cerr << sl << " set up for playing " << mstate.uri << endl;
+ } else {
+ cerr << sstate.reason << endl;
+ }
+ }
+ }
+}
+
+void ohNoSongcast(const vector<string>& slaves)
+{
+ for (auto& sl: slaves) {
+ cerr << "Songcast: resetting " << sl << endl;
+ SongcastState sstate;
+ getSongcastState(sl, sstate);
+
+ switch (sstate.state) {
+ case SongcastState::SCS_GENERROR:
+ case SongcastState::SCS_NOOH:
+ cerr << sl << sstate.reason << endl;
+ continue;
+ case SongcastState::SCS_NOTRECEIVER:
+ cerr << sl << ": not in receiver mode" << endl;
+ continue;
+ case SongcastState::SCS_STOPPED:
+ case SongcastState::SCS_PLAYING:
+ if (stopReceiver(sl, sstate)) {
+ cerr << sl << " back from receiver mode " << endl;
+ } else {
+ cerr << sstate.reason << endl;
+ }
+ }
+ }
+}
+
+static char *thisprog;
+static char usage [] =
+" -l list renderers with Songcast Receiver capability\n"
+" -s <master> <slave> [slave ...] : Set up the slaves renderers as Songcast"
+" Receivers and make them play from the same uri as the master\n"
+" -x <renderer> [renderer ...] Reset renderers from Songcast to Playlist\n"
+"\n"
+"Renderers may be designated by friendly name or UUID\n"
+" \n\n"
+;
+static void
+Usage(void)
+{
+ fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
+ exit(1);
+}
+static int op_flags;
+#define OPT_l 0x1
+#define OPT_s 0x2
+#define OPT_x 0x4
+
+int main(int argc, char *argv[])
+{
+ thisprog = argv[0];
+
+ int ret;
+ while ((ret = getopt(argc, argv, "lsx")) != -1) {
+ switch (ret) {
+ case 'l': if (op_flags) Usage(); op_flags |= OPT_l; break;
+ case 's': if (op_flags) Usage(); op_flags |= OPT_s; break;
+ case 'x': if (op_flags) Usage(); op_flags |= OPT_x; break;
+ default: Usage();
+ }
+ }
+
+ if (Logger::getTheLog("/tmp/upexplo.log") == 0) {
+ cerr << "Can't initialize log" << endl;
+ return 1;
+ }
+ Logger::getTheLog("")->setLogLevel(Logger::LLDEB1);
+
+ LibUPnP *mylib = LibUPnP::getLibUPnP();
+ if (!mylib) {
+ cerr << "Can't get LibUPnP" << endl;
+ return 1;
+ }
+ if (!mylib->ok()) {
+ cerr << "Lib init failed: " <<
+ mylib->errAsString("main", mylib->getInitError()) << endl;
+ return 1;
+ }
+ // mylib->setLogFileName("/tmp/libupnp.log");
+
+
+ if ((op_flags & OPT_l)) {
+ if (op_flags & (OPT_s|OPT_x)) {
+ Usage();
+ }
+ if (optind < argc)
+ Usage();
+ vector<SongcastState> vscs;
+ listReceivers(vscs);
+ for (auto& scs: vscs) {
+ switch (scs.state) {
+ case SongcastState::SCS_GENERROR: cout << "Error ";break;
+ case SongcastState::SCS_NOOH: cout << "Nooh ";break;
+ case SongcastState::SCS_NOTRECEIVER: cout << "Off ";break;
+ case SongcastState::SCS_STOPPED: cout << "Stop ";break;
+ case SongcastState::SCS_PLAYING: cout << "Play ";break;
+ }
+ cout << scs.nm << " ";
+ cout << scs.UDN << " ";
+ if (scs.state == SongcastState::SCS_PLAYING) {
+ cout << scs.uri;
+ } else if (scs.state == SongcastState::SCS_GENERROR) {
+ cout << scs.reason;
+ }
+ cout << endl;
+ }
+
+ } else if ((op_flags & OPT_s)) {
+ if (op_flags & (OPT_l|OPT_x)) {
+ Usage();
+ }
+ if (optind > argc - 2)
+ Usage();
+ string master = argv[optind++];
+ vector<string> slaves;
+ while (optind < argc) {
+ slaves.push_back(argv[optind++]);
+ }
+ ohSongcast(master, slaves);
+ } else if ((op_flags & OPT_x)) {
+ if (op_flags & (OPT_l|OPT_s)) {
+ Usage();
+ }
+ if (optind > argc - 1)
+ Usage();
+ vector<string> slaves;
+ while (optind < argc) {
+ slaves.push_back(argv[optind++]);
+ }
+ ohNoSongcast(slaves);
+ } else {
+ Usage();
+ }
+
+ return 0;
+}