Switch to unified view

a b/scctl_src/scctl.cpp
1
/* Copyright (C) 2015 J.F.Dockes
2
 *       This program is free software; you can redistribute it and/or modify
3
 *       it under the terms of the GNU General Public License as published by
4
 *       the Free Software Foundation; either version 2 of the License, or
5
 *       (at your option) any later version.
6
 *
7
 *       This program is distributed in the hope that it will be useful,
8
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *       GNU General Public License for more details.
11
 *
12
 *       You should have received a copy of the GNU General Public License
13
 *       along with this program; if not, write to the
14
 *       Free Software Foundation, Inc.,
15
 *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
18
///// Songcast UPnP Controller
19
20
#include <stdio.h>
21
#include <stdlib.h>
22
#include <unistd.h>
23
#include <getopt.h>
24
25
#include <string>
26
#include <iostream>
27
#include <vector>
28
#include <algorithm>
29
using namespace std;
30
31
#include "libupnpp/upnpplib.hxx"
32
#include "libupnpp/log.hxx"
33
#include "libupnpp/upnpputils.hxx"
34
#include "libupnpp/ptmutex.hxx"
35
#include "libupnpp/control/service.hxx"
36
#include "libupnpp/control/mediarenderer.hxx"
37
#include "libupnpp/control/renderingcontrol.hxx"
38
#include "libupnpp/control/discovery.hxx"
39
40
using namespace UPnPClient;
41
using namespace UPnPP;
42
43
UPnPDeviceDirectory *superdir;
44
45
MRDH getRenderer(const string& name)
46
{
47
    if (superdir == 0) {
48
        superdir = UPnPDeviceDirectory::getTheDir();
49
    }
50
51
    UPnPDeviceDesc ddesc;
52
    if (superdir->getDevByUDN(name, ddesc)) {
53
        return MRDH(new MediaRenderer(ddesc));
54
    } else if (superdir->getDevByFName(name, ddesc)) {
55
        return MRDH(new MediaRenderer(ddesc));
56
    } 
57
    cerr << "getDevByFname failed for " << name << endl;
58
    return MRDH();
59
}
60
61
struct SongcastState {
62
    string nm;
63
    string UDN;
64
    enum SCState {SCS_GENERROR, SCS_NOOH, SCS_NOTRECEIVER,
65
                  SCS_STOPPED, SCS_PLAYING};
66
    SCState state;
67
    string uri;
68
    string meta;
69
    int receiverSourceIndex;
70
    string reason;
71
72
    OHPRH prod;
73
    OHRCH rcv;
74
75
    SongcastState() 
76
        : state(SCS_GENERROR), receiverSourceIndex(-1) {
77
    }
78
79
    void reset() {
80
        nm.clear();
81
        state = SongcastState::SCS_GENERROR;
82
        receiverSourceIndex = -1;
83
        reason.clear();
84
        uri.clear();
85
        meta.clear();
86
        prod.reset();
87
        rcv.reset();
88
    }
89
};
90
91
void getSongcastState(const string& nm, SongcastState& st, bool live = true)
92
{
93
    st.reset();
94
    st.nm = nm;
95
96
    MRDH rdr = getRenderer(nm);
97
    if (!rdr) {
98
        st.reason = nm + " not a media renderer?";
99
        return;
100
    }
101
    st.nm = rdr->desc()->friendlyName;
102
    st.UDN = rdr->desc()->UDN;
103
104
    OHPRH prod = rdr->ohpr();
105
    if (!prod) {
106
        st.state = SongcastState::SCS_NOOH;
107
        st.reason =  nm + ": device has no OHProduct service";
108
        return;
109
    }
110
    int index;
111
    if (prod->sourceIndex(&index)) {
112
        st.reason = nm + " : sourceIndex failed";
113
        return;
114
    }
115
116
    vector<OHProduct::Source> sources;
117
    if (prod->getSources(sources) || sources.size() == 0) {
118
        st.reason = nm + ": getSources failed";
119
        return;
120
    }
121
    unsigned int rcvi = 0;
122
    for (; rcvi < sources.size(); rcvi++) {
123
        if (!sources[rcvi].name.compare("Receiver"))
124
            break;
125
    }
126
    if (rcvi == sources.size()) {
127
        st.state = SongcastState::SCS_NOOH;
128
        st.reason = nm +  " has no Receiver service";
129
        return;
130
    }
131
    st.receiverSourceIndex = int(rcvi);
132
133
    if (index < 0 || index >= int(sources.size())) {
134
        st.reason = nm +  ": bad index " + SoapHelp::i2s(index) +
135
            " not inside sources of size " +  SoapHelp::i2s(sources.size());
136
        return;
137
    }
138
139
    // Looks like the device has a receiver service. We may have to return a 
140
    // handle for it.
141
    OHRCH rcv = rdr->ohrc();
142
143
    string sname = sources[index].name;
144
    if (sname.compare("Receiver")) {
145
        st.state = SongcastState::SCS_NOTRECEIVER;
146
        st.reason = nm +  " not in receiver mode ";
147
        goto out;
148
    }
149
150
    if (!rcv) {
151
        st.reason = nm +  ": no receiver service??";
152
        goto out;
153
    }
154
    if (rcv->sender(st.uri, st.meta)) {
155
        st.reason = nm +  ": Receiver::Sender failed";
156
        goto out;
157
    }
158
159
    OHPlaylist::TPState tpst;
160
    if (rcv->transportState(&tpst)) {
161
        st.reason = nm +  ": Receiver::transportState() failed";
162
        goto out;
163
    }
164
165
    if (tpst == OHPlaylist::TPS_Playing) {
166
        st.state = SongcastState::SCS_PLAYING;
167
    } else {
168
        st.state = SongcastState::SCS_STOPPED;
169
    }
170
out:
171
    if (live) {
172
        st.prod = prod;
173
        st.rcv = rcv;
174
    }
175
        
176
    return;
177
}
178
179
void listReceivers(vector<SongcastState>& vscs)
180
{
181
    vector<UPnPDeviceDesc> vdds;
182
    if (!MediaRenderer::getDeviceDescs(vdds)) {
183
        cerr << "listReceivers::getDeviceDescs failed" << endl;
184
        return;
185
    }
186
187
    for (auto& entry : vdds) {
188
        SongcastState st;
189
        getSongcastState(entry.UDN, st, false);
190
        if (st.state == SongcastState::SCS_NOTRECEIVER || 
191
            st.state == SongcastState::SCS_PLAYING ||
192
            st.state == SongcastState::SCS_STOPPED) {
193
            vscs.push_back(st);
194
        }
195
    }
196
}
197
198
bool setReceiverPlaying(const string& nm, SongcastState& st, 
199
                        const string& uri, const string& meta)
200
{
201
    if (!st.rcv || !st.prod) {
202
        st.reason = nm + " : null handle ??";
203
        return false;
204
    }
205
    if (st.prod->setSourceIndex(st.receiverSourceIndex)) {
206
        st.reason = nm + " : can't set source index to " +
207
            SoapHelp::i2s(st.receiverSourceIndex);
208
        return false;
209
    }
210
    if (st.rcv->setSender(uri, meta)) {
211
        st.reason = nm + " Receiver::setSender() failed";
212
        return false;
213
    }
214
    if (st.rcv->play()) {
215
        st.reason = nm + " Receiver::play() failed";
216
        return false;
217
    }
218
    return true;
219
}
220
221
bool stopReceiver(const string& nm, SongcastState st)
222
{
223
    if (!st.rcv || !st.prod) {
224
        st.reason = nm + " : null handle ??";
225
        return false;
226
    }
227
    if (st.rcv->stop()) {
228
        st.reason = nm + " Receiver::play() failed";
229
        return false;
230
    }
231
    if (st.prod->setSourceIndex(0)) {
232
        st.reason = nm + " : can't set source index to " +
233
            SoapHelp::i2s(st.receiverSourceIndex);
234
        return false;
235
    }
236
    return true;
237
}
238
239
void ohSongcast(const string& masterName, const vector<string>& slaves)
240
{
241
    SongcastState mstate;
242
    getSongcastState(masterName, mstate);
243
    if (mstate.state != SongcastState::SCS_PLAYING) {
244
        cerr << "Required master not in Receiver Playing mode" << endl;
245
        return;
246
    }
247
248
    // Note: sequence sent from windows songcast when setting up a receiver:
249
    //   Product::SetSourceIndex / Receiver::SetSender / Receiver::Play
250
    // When stopping:
251
    //   Receiver::Stop / Product::SetStandby
252
    for (auto& sl: slaves) {
253
        cerr << "Setting up " << sl << endl;
254
        SongcastState sstate;
255
        getSongcastState(sl, sstate);
256
257
        switch (sstate.state) {
258
        case SongcastState::SCS_GENERROR:
259
        case SongcastState::SCS_NOOH:
260
            cerr << sl << sstate.reason << endl;
261
            continue;
262
        case SongcastState::SCS_STOPPED:
263
        case SongcastState::SCS_PLAYING:
264
            cerr << sl << ": already in receiver mode" << endl;
265
            continue;
266
        case SongcastState::SCS_NOTRECEIVER: 
267
            if (setReceiverPlaying(sl, sstate, mstate.uri, mstate.meta)) {
268
                cerr << sl << " set up for playing " << mstate.uri << endl;
269
            } else {
270
                cerr << sstate.reason << endl;
271
            }
272
        }
273
    }
274
}
275
276
void ohNoSongcast(const vector<string>& slaves)
277
{
278
    for (auto& sl: slaves) {
279
        cerr << "Songcast: resetting " << sl << endl;
280
        SongcastState sstate;
281
        getSongcastState(sl, sstate);
282
283
        switch (sstate.state) {
284
        case SongcastState::SCS_GENERROR:
285
        case SongcastState::SCS_NOOH:
286
            cerr << sl << sstate.reason << endl;
287
            continue;
288
        case SongcastState::SCS_NOTRECEIVER: 
289
            cerr << sl << ": not in receiver mode" << endl;
290
            continue;
291
        case SongcastState::SCS_STOPPED:
292
        case SongcastState::SCS_PLAYING:
293
            if (stopReceiver(sl, sstate)) {
294
                cerr << sl << " back from receiver mode " << endl;
295
            } else {
296
                cerr << sstate.reason << endl;
297
            }
298
        }
299
    }
300
}
301
302
static char *thisprog;
303
static char usage [] =
304
" -l list renderers with Songcast Receiver capability\n"
305
" -s <master> <slave> [slave ...] : Set up the slaves renderers as Songcast"
306
"    Receivers and make them play from the same uri as the master\n"
307
" -x <renderer> [renderer ...] Reset renderers from Songcast to Playlist\n"
308
"\n"
309
"Renderers may be designated by friendly name or UUID\n"
310
"  \n\n"
311
;
312
static void
313
Usage(void)
314
{
315
    fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
316
    exit(1);
317
}
318
static int   op_flags;
319
#define OPT_l    0x1
320
#define OPT_s    0x2
321
#define OPT_x    0x4
322
323
int main(int argc, char *argv[])
324
{
325
    thisprog = argv[0];
326
327
    int ret;
328
    while ((ret = getopt(argc, argv, "lsx")) != -1) {
329
        switch (ret) {
330
        case 'l': if (op_flags) Usage(); op_flags |= OPT_l; break;
331
        case 's': if (op_flags) Usage(); op_flags |= OPT_s; break;
332
        case 'x': if (op_flags) Usage(); op_flags |= OPT_x; break;
333
        default: Usage();
334
        }
335
    }
336
337
    if (Logger::getTheLog("/tmp/upexplo.log") == 0) {
338
        cerr << "Can't initialize log" << endl;
339
        return 1;
340
    }
341
    Logger::getTheLog("")->setLogLevel(Logger::LLDEB1);
342
343
    LibUPnP *mylib = LibUPnP::getLibUPnP();
344
    if (!mylib) {
345
        cerr << "Can't get LibUPnP" << endl;
346
        return 1;
347
    }
348
    if (!mylib->ok()) {
349
        cerr << "Lib init failed: " <<
350
            mylib->errAsString("main", mylib->getInitError()) << endl;
351
        return 1;
352
    }
353
    // mylib->setLogFileName("/tmp/libupnp.log");
354
355
356
    if ((op_flags & OPT_l)) {
357
        if (op_flags & (OPT_s|OPT_x)) {
358
            Usage();
359
        }
360
        if (optind < argc)
361
            Usage();
362
        vector<SongcastState> vscs;
363
        listReceivers(vscs);
364
        for (auto& scs: vscs) {
365
            switch (scs.state) {
366
            case SongcastState::SCS_GENERROR:    cout << "Error ";break;
367
            case SongcastState::SCS_NOOH:        cout << "Nooh  ";break;
368
            case SongcastState::SCS_NOTRECEIVER: cout << "Off   ";break;
369
            case SongcastState::SCS_STOPPED:     cout << "Stop  ";break;
370
            case SongcastState::SCS_PLAYING:     cout << "Play  ";break;
371
            }
372
            cout << scs.nm << " ";
373
            cout << scs.UDN << " ";
374
            if (scs.state == SongcastState::SCS_PLAYING) {
375
                cout << scs.uri;
376
            } else if (scs.state == SongcastState::SCS_GENERROR) {
377
                cout << scs.reason;
378
            }
379
            cout << endl;
380
        }
381
        
382
    } else if ((op_flags & OPT_s)) {
383
        if (op_flags & (OPT_l|OPT_x)) {
384
            Usage();
385
        }
386
        if (optind > argc - 2)
387
            Usage();
388
        string master = argv[optind++];
389
        vector<string> slaves;
390
        while (optind < argc) {
391
            slaves.push_back(argv[optind++]);
392
        }
393
        ohSongcast(master, slaves);
394
    } else if ((op_flags & OPT_x)) {
395
        if (op_flags & (OPT_l|OPT_s)) {
396
            Usage();
397
        }
398
        if (optind > argc - 1)
399
            Usage();
400
        vector<string> slaves;
401
        while (optind < argc) {
402
            slaves.push_back(argv[optind++]);
403
        }
404
        ohNoSongcast(slaves);
405
    } else {
406
        Usage();
407
    }
408
409
    return 0;
410
}