Switch to unified view

a/scctl_src/scctl.cpp b/scctl_src/scctl.cpp
...
...
17
17
18
/* 
18
/* 
19
 * Songcast UPnP controller. 
19
 * Songcast UPnP controller. 
20
 *
20
 *
21
 * This can list the state of all Receivers, tell
21
 * This can list the state of all Receivers, tell
22
 * a Receiver to play the same URI as another one (master/slave,
22
 * a Receiver to play the same URI as another one (master/slave),
23
 * except that the slaves don't really follow the master state, they
23
 * except that the slaves don't really follow the master state, they
24
 * will just keep playing the same URI), or tell a Receiver to return
24
 * will just keep playing the same URI), or tell a Receiver to return
25
 * to Playlist operation.
25
 * to Playlist operation.
26
 * 
26
 * 
27
 * To avoid encurring a discovery timeout for each op, there is a
27
 * To avoid encurring a discovery timeout for each op, there is a
...
...
30
 *
30
 *
31
 * When executing any of the ops from the command line, the program
31
 * When executing any of the ops from the command line, the program
32
 * first tries to contact the server, and does things itself if no
32
 * first tries to contact the server, and does things itself if no
33
 * server is found (encurring 2-3 S of timeout in the latter case).
33
 * server is found (encurring 2-3 S of timeout in the latter case).
34
 */
34
 */
35
#include "libupnpp/config.h"
35
36
36
#include <stdio.h>
37
#include <stdio.h>
37
#include <stdlib.h>
38
#include <stdlib.h>
38
#include <unistd.h>
39
#include <unistd.h>
39
#include <getopt.h>
40
#include <getopt.h>
...
...
43
44
44
#include <string>
45
#include <string>
45
#include <iostream>
46
#include <iostream>
46
#include <vector>
47
#include <vector>
47
#include <algorithm>
48
#include <algorithm>
48
using namespace std;
49
49
50
#include "libupnpp/upnpplib.hxx"
50
#include "libupnpp/upnpplib.hxx"
51
#include "libupnpp/log.hxx"
51
#include "libupnpp/log.hxx"
52
#include "libupnpp/control/mediarenderer.hxx"
52
#include "libupnpp/control/mediarenderer.hxx"
53
#include "libupnpp/control/discovery.hxx"
53
#include "libupnpp/control/discovery.hxx"
54
#include "libupnpp/control/linnsongcast.hxx"
54
55
55
#include "../src/netcon.h"
56
#include "../src/netcon.h"
56
#include "../src/upmpdutils.hxx"
57
#include "../src/upmpdutils.hxx"
57
58
58
using namespace UPnPClient;
59
using namespace UPnPClient;
59
using namespace UPnPP;
60
using namespace UPnPP;
60
61
using namespace std;
61
UPnPDeviceDirectory *superdir;
62
using namespace Songcast;
62
63
MRDH getRenderer(const string& name)
64
{
65
    if (superdir == 0) {
66
        superdir = UPnPDeviceDirectory::getTheDir();
67
    }
68
69
    UPnPDeviceDesc ddesc;
70
    if (superdir->getDevByUDN(name, ddesc)) {
71
        return MRDH(new MediaRenderer(ddesc));
72
    } else if (superdir->getDevByFName(name, ddesc)) {
73
        return MRDH(new MediaRenderer(ddesc));
74
    } 
75
    cerr << "getDevByFname failed for " << name << endl;
76
    return MRDH();
77
}
78
79
struct SongcastState {
80
    string nm;
81
    string UDN;
82
    enum SCState {SCS_GENERROR, SCS_NOOH, SCS_NOTRECEIVER,
83
                  SCS_STOPPED, SCS_PLAYING};
84
    SCState state;
85
    string uri;
86
    string meta;
87
    int receiverSourceIndex;
88
    string reason;
89
90
    OHPRH prod;
91
    OHRCH rcv;
92
93
    SongcastState() 
94
        : state(SCS_GENERROR), receiverSourceIndex(-1) {
95
    }
96
97
    void reset() {
98
        nm.clear();
99
        state = SongcastState::SCS_GENERROR;
100
        receiverSourceIndex = -1;
101
        reason.clear();
102
        uri.clear();
103
        meta.clear();
104
        prod.reset();
105
        rcv.reset();
106
    }
107
};
108
109
void getSongcastState(const string& nm, SongcastState& st, bool live = true)
110
{
111
    st.reset();
112
    st.nm = nm;
113
114
    MRDH rdr = getRenderer(nm);
115
    if (!rdr) {
116
        st.reason = nm + " not a media renderer?";
117
        return;
118
    }
119
    st.nm = rdr->desc()->friendlyName;
120
    st.UDN = rdr->desc()->UDN;
121
122
    OHPRH prod = rdr->ohpr();
123
    if (!prod) {
124
        st.state = SongcastState::SCS_NOOH;
125
        st.reason =  nm + ": device has no OHProduct service";
126
        return;
127
    }
128
    int index;
129
    if (prod->sourceIndex(&index)) {
130
        st.reason = nm + " : sourceIndex failed";
131
        return;
132
    }
133
134
    vector<OHProduct::Source> sources;
135
    if (prod->getSources(sources) || sources.size() == 0) {
136
        st.reason = nm + ": getSources failed";
137
        return;
138
    }
139
    unsigned int rcvi = 0;
140
    for (; rcvi < sources.size(); rcvi++) {
141
        if (!sources[rcvi].name.compare("Receiver"))
142
            break;
143
    }
144
    if (rcvi == sources.size()) {
145
        st.state = SongcastState::SCS_NOOH;
146
        st.reason = nm +  " has no Receiver service";
147
        return;
148
    }
149
    st.receiverSourceIndex = int(rcvi);
150
151
    if (index < 0 || index >= int(sources.size())) {
152
        st.reason = nm +  ": bad index " + SoapHelp::i2s(index) +
153
            " not inside sources of size " +  SoapHelp::i2s(sources.size());
154
        return;
155
    }
156
157
    // Looks like the device has a receiver service. We may have to return a 
158
    // handle for it.
159
    OHRCH rcv = rdr->ohrc();
160
161
    string sname = sources[index].name;
162
    if (sname.compare("Receiver")) {
163
        st.state = SongcastState::SCS_NOTRECEIVER;
164
        st.reason = nm +  " not in receiver mode ";
165
        goto out;
166
    }
167
168
    if (!rcv) {
169
        st.reason = nm +  ": no receiver service??";
170
        goto out;
171
    }
172
    if (rcv->sender(st.uri, st.meta)) {
173
        st.reason = nm +  ": Receiver::Sender failed";
174
        goto out;
175
    }
176
177
    OHPlaylist::TPState tpst;
178
    if (rcv->transportState(&tpst)) {
179
        st.reason = nm +  ": Receiver::transportState() failed";
180
        goto out;
181
    }
182
183
    if (tpst == OHPlaylist::TPS_Playing) {
184
        st.state = SongcastState::SCS_PLAYING;
185
    } else {
186
        st.state = SongcastState::SCS_STOPPED;
187
    }
188
out:
189
    if (live) {
190
        st.prod = prod;
191
        st.rcv = rcv;
192
    }
193
        
194
    return;
195
}
196
197
void listReceivers(vector<SongcastState>& vscs)
198
{
199
    vector<UPnPDeviceDesc> vdds;
200
    if (!MediaRenderer::getDeviceDescs(vdds)) {
201
        cerr << "listReceivers::getDeviceDescs failed" << endl;
202
        return;
203
    }
204
205
    for (auto& entry : vdds) {
206
        SongcastState st;
207
        getSongcastState(entry.UDN, st, false);
208
        if (st.state == SongcastState::SCS_NOTRECEIVER || 
209
            st.state == SongcastState::SCS_PLAYING ||
210
            st.state == SongcastState::SCS_STOPPED) {
211
            vscs.push_back(st);
212
        }
213
    }
214
}
215
216
bool setReceiverPlaying(const string& nm, SongcastState& st, 
217
                        const string& uri, const string& meta)
218
{
219
    if (!st.rcv || !st.prod) {
220
        st.reason = nm + " : null handle ??";
221
        return false;
222
    }
223
224
    if (st.rcv->setSender(uri, meta)) {
225
        st.reason = nm + " Receiver::setSender() failed";
226
        return false;
227
    }
228
    if (st.prod->setSourceIndex(st.receiverSourceIndex)) {
229
        st.reason = nm + " : can't set source index to " +
230
            SoapHelp::i2s(st.receiverSourceIndex);
231
        return false;
232
    }
233
    if (st.rcv->play()) {
234
        st.reason = nm + " Receiver::play() failed";
235
        return false;
236
    }
237
    return true;
238
}
239
240
bool stopReceiver(const string& nm, SongcastState st)
241
{
242
    if (!st.rcv || !st.prod) {
243
        st.reason = nm + " : null handle ??";
244
        return false;
245
    }
246
    if (st.rcv->stop()) {
247
        st.reason = nm + " Receiver::play() failed";
248
        return false;
249
    }
250
    if (st.prod->setSourceIndex(0)) {
251
        st.reason = nm + " : can't set source index to " +
252
            SoapHelp::i2s(st.receiverSourceIndex);
253
        return false;
254
    }
255
    return true;
256
}
257
258
void ohSongcast(const string& masterName, const vector<string>& slaves)
259
{
260
    SongcastState mstate;
261
    getSongcastState(masterName, mstate);
262
    if (mstate.state != SongcastState::SCS_PLAYING) {
263
        cerr << "Required master not in Receiver Playing mode" << endl;
264
        return;
265
    }
266
267
    // Note: sequence sent from windows songcast when setting up a receiver:
268
    //   Product::SetSourceIndex / Receiver::SetSender / Receiver::Play
269
    // When stopping:
270
    //   Receiver::Stop / Product::SetStandby
271
    for (auto& sl: slaves) {
272
        cerr << "Setting up " << sl << endl;
273
        SongcastState sstate;
274
        getSongcastState(sl, sstate);
275
276
        switch (sstate.state) {
277
        case SongcastState::SCS_GENERROR:
278
        case SongcastState::SCS_NOOH:
279
            cerr << sl << sstate.reason << endl;
280
            continue;
281
        case SongcastState::SCS_STOPPED:
282
        case SongcastState::SCS_PLAYING:
283
            cerr << sl << ": already in receiver mode" << endl;
284
            continue;
285
        case SongcastState::SCS_NOTRECEIVER: 
286
            if (setReceiverPlaying(sl, sstate, mstate.uri, mstate.meta)) {
287
                cerr << sl << " set up for playing " << mstate.uri << endl;
288
            } else {
289
                cerr << sstate.reason << endl;
290
            }
291
        }
292
    }
293
}
294
295
void ohNoSongcast(const vector<string>& slaves)
296
{
297
    for (auto& sl: slaves) {
298
        cerr << "Songcast: resetting " << sl << endl;
299
        SongcastState sstate;
300
        getSongcastState(sl, sstate);
301
302
        switch (sstate.state) {
303
        case SongcastState::SCS_GENERROR:
304
        case SongcastState::SCS_NOOH:
305
            cerr << sl << sstate.reason << endl;
306
            continue;
307
        case SongcastState::SCS_NOTRECEIVER: 
308
            cerr << sl << ": not in receiver mode" << endl;
309
            continue;
310
        case SongcastState::SCS_STOPPED:
311
        case SongcastState::SCS_PLAYING:
312
            if (stopReceiver(sl, sstate)) {
313
                cerr << sl << " back from receiver mode " << endl;
314
            } else {
315
                cerr << sstate.reason << endl;
316
            }
317
        }
318
    }
319
}
320
63
321
string showReceivers()
64
string showReceivers()
322
{
65
{
323
    vector<SongcastState> vscs;
66
    vector<ReceiverState> vscs;
324
    listReceivers(vscs);
67
    listReceivers(vscs);
325
    ostringstream out;
68
    ostringstream out;
326
69
327
    for (auto& scs: vscs) {
70
    for (auto& scs: vscs) {
328
        switch (scs.state) {
71
        switch (scs.state) {
329
        case SongcastState::SCS_GENERROR:    out << "Error ";break;
72
        case ReceiverState::SCRS_GENERROR:    out << "Error ";break;
330
        case SongcastState::SCS_NOOH:        out << "Nooh  ";break;
73
        case ReceiverState::SCRS_NOOH:        out << "Nooh  ";break;
331
        case SongcastState::SCS_NOTRECEIVER: out << "Off   ";break;
74
        case ReceiverState::SCRS_NOTRECEIVER: out << "Off   ";break;
332
        case SongcastState::SCS_STOPPED:     out << "Stop  ";break;
75
        case ReceiverState::SCRS_STOPPED:     out << "Stop  ";break;
333
        case SongcastState::SCS_PLAYING:     out << "Play  ";break;
76
        case ReceiverState::SCRS_PLAYING:     out << "Play  ";break;
334
        }
77
        }
335
        out << scs.nm << " ";
78
        out << scs.nm << " ";
336
        out << scs.UDN << " ";
79
        out << scs.UDN << " ";
337
        if (scs.state == SongcastState::SCS_PLAYING) {
80
        if (scs.state == ReceiverState::SCRS_PLAYING) {
338
            out << scs.uri;
81
            out << scs.uri;
339
        } else if (scs.state == SongcastState::SCS_GENERROR) {
82
        } else if (scs.state == ReceiverState::SCRS_GENERROR) {
340
            out << scs.reason;
83
            out << scs.reason;
341
        }
84
        }
85
        out << endl;
86
    }
87
    return out.str();
88
}
89
90
string showSenders()
91
{
92
    vector<SenderState> vscs;
93
    listSenders(vscs);
94
    ostringstream out;
95
96
    for (auto& scs: vscs) {
97
        out << scs.nm << " ";
98
        out << scs.UDN << " ";
99
        out << scs.reason << " ";
100
        out << scs.uri;
342
        out << endl;
101
        out << endl;
343
    }
102
    }
344
    return out.str();
103
    return out.str();
345
}
104
}
346
105
347
static char *thisprog;
106
static char *thisprog;
348
static char usage [] =
107
static char usage [] =
349
" -l List renderers with Songcast Receiver capability\n"
108
" -l List renderers with Songcast Receiver capability\n"
109
" -L List Songcast Senders\n"
350
" -s <master> <slave> [slave ...] : Set up the slaves renderers as Songcast\n"
110
" -s <master> <slave> [slave ...] : Set up the slaves renderers as Songcast\n"
351
"    Receivers and make them play from the same uri as the master\n"
111
"    Receivers and make them play from the same uri as the master receiver\n"
352
" -x <renderer> [renderer ...] Reset renderers from Songcast to Playlist\n"
112
" -x <renderer> [renderer ...] Reset renderers from Songcast to Playlist\n"
353
" -S Run as server\n"
113
" -S Run as server\n"
354
" -f If no server is found, scctl will fork one after performing the\n"
114
" -f If no server is found, scctl will fork one after performing the\n"
355
"    requested command, so that the next execution will not have to wait for\n"
115
"    requested command, so that the next execution will not have to wait for\n"
356
"    the discovery timeout.n"
116
"    the discovery timeout.n"
117
" -r <sender> <renderer> <renderer> : set up the renderers in Receiver mode\n"
118
"    playing data from the sender. This is like -s but we get the uri from \n"
119
"    the sender instead of a sibling receiver\n"
357
" -h This help.\n"
120
" -h This help.\n"
358
"\n"
121
"\n"
359
"Renderers may be designated by friendly name or UUID\n"
122
"Renderers may be designated by friendly name or UUID\n"
360
"\n"
123
"\n"
361
;
124
;
...
...
372
#define OPT_x    0x4
135
#define OPT_x    0x4
373
#define OPT_S    0x8
136
#define OPT_S    0x8
374
#define OPT_h    0x10
137
#define OPT_h    0x10
375
#define OPT_f    0x20
138
#define OPT_f    0x20
376
#define OPT_p    0x40
139
#define OPT_p    0x40
140
#define OPT_r    0x80
141
#define OPT_L    0x100
377
142
378
int runserver();
143
int runserver();
379
bool tryserver(int flags, int argc, char *argv[]);
144
bool tryserver(int flags, int argc, char *argv[]);
380
145
381
int main(int argc, char *argv[])
146
int main(int argc, char *argv[])
382
{
147
{
383
    thisprog = argv[0];
148
    thisprog = argv[0];
384
149
385
    int ret;
150
    int ret;
386
    while ((ret = getopt(argc, argv, "fhlsSx")) != -1) {
151
    while ((ret = getopt(argc, argv, "fhLlrsSx")) != -1) {
387
        switch (ret) {
152
        switch (ret) {
388
        case 'f': op_flags |= OPT_f; break;
153
        case 'f': op_flags |= OPT_f; break;
389
        case 'h': Usage(stdout); break;
154
        case 'h': Usage(stdout); break;
390
        case 'l': op_flags |= OPT_l; break;
155
        case 'l':
391
        case 's': op_flags |= OPT_s; break;
156
            if (op_flags & ~OPT_f)
392
        case 'S': op_flags |= OPT_S; break;
157
                Usage();
393
        case 'x': op_flags |= OPT_x; break;
158
            op_flags |= OPT_l;
159
            break;
160
        case 'L':
161
            if (op_flags & ~OPT_f)
162
                Usage();
163
            op_flags |= OPT_L;
164
            break;
165
        case 'r':
166
            if (op_flags & ~OPT_f)
167
                Usage();
168
            op_flags |= OPT_r;
169
            break;
170
        case 's':
171
            if (op_flags & ~OPT_f)
172
                Usage();
173
            op_flags |= OPT_s;
174
            break;
175
        case 'S':
176
            if (op_flags & ~OPT_f)
177
                Usage();
178
            op_flags |= OPT_S;
179
            break;
180
        case 'x':
181
            if (op_flags & ~OPT_f)
182
                Usage();
183
            op_flags |= OPT_x;
184
            break;
394
        default: Usage();
185
        default: Usage();
395
        }
186
        }
396
    }
187
    }
397
    //fprintf(stderr, "argc %d optind %d flgs: 0x%x\n", argc, optind, op_flags);
188
    //fprintf(stderr, "argc %d optind %d flgs: 0x%x\n", argc, optind, op_flags);
398
189
...
...
416
            mylib->errAsString("main", mylib->getInitError()) << endl;
207
            mylib->errAsString("main", mylib->getInitError()) << endl;
417
        return 1;
208
        return 1;
418
    }
209
    }
419
    // mylib->setLogFileName("/tmp/libupnp.log");
210
    // mylib->setLogFileName("/tmp/libupnp.log");
420
211
421
212
    vector<string> args;
213
    while (optind < argc) {
214
        args.push_back(argv[optind++]);
215
    }
216
    
422
    if ((op_flags & OPT_l)) {
217
    if ((op_flags & OPT_l)) {
423
        if (op_flags & (OPT_s|OPT_x)) {
218
        if (args.size())
424
            Usage();
425
        }
426
        if (optind < argc)
427
            Usage();
219
            Usage();
428
        string out = showReceivers();
220
        string out = showReceivers();
429
        cout << out;
221
        cout << out;
222
    } else if ((op_flags & OPT_L)) {
223
        if (args.size())
224
            Usage();
225
        string out = showSenders();
226
        cout << out;
227
    } else if ((op_flags & OPT_r)) {
228
        if (args.size() < 2)
229
            Usage();
230
        setReceiversFromSender(args[0], vector<string>(args.begin() + 1,
231
                                                       args.end()));
430
    } else if ((op_flags & OPT_s)) {
232
    } else if ((op_flags & OPT_s)) {
431
        if (op_flags & (OPT_l|OPT_x)) {
233
        if (args.size() < 2)
432
            Usage();
234
            Usage();
433
        }
235
        setReceiversFromReceiver(args[0], vector<string>(args.begin()+1,
434
        if (optind > argc - 2)
236
                                                         args.end()));
237
    } else if ((op_flags & OPT_x)) {
238
        if (args.size() < 1)
435
            Usage();
239
            Usage();
436
        string master = argv[optind++];
240
        stopReceivers(args);
437
        vector<string> slaves;
438
        while (optind < argc) {
439
            slaves.push_back(argv[optind++]);
440
        }
441
        ohSongcast(master, slaves);
442
    } else if ((op_flags & OPT_x)) {
443
        if (op_flags & (OPT_l|OPT_s)) {
444
            Usage();
445
        }
446
        if (optind > argc - 1)
447
            Usage();
448
        vector<string> slaves;
449
        while (optind < argc) {
450
            slaves.push_back(argv[optind++]);
451
        }
452
        ohNoSongcast(slaves);
453
    } else if ((op_flags & OPT_S)) {
241
    } else if ((op_flags & OPT_S)) {
454
        exit(runserver());
242
        exit(runserver());
455
    } else {
243
    } else {
456
        Usage();
244
        Usage();
457
    }
245
    }
...
...
591
    if (opflags & OPT_p) {
379
    if (opflags & OPT_p) {
592
        // ping
380
        // ping
593
        out = "Ok\n";
381
        out = "Ok\n";
594
    } else if (opflags & OPT_l) {
382
    } else if (opflags & OPT_l) {
595
        out = showReceivers();
383
        out = showReceivers();
384
    } else if (opflags & OPT_r) {
385
        if (toks.size() < 3)
386
            return 1;
387
        vector<string>::iterator beg = toks.begin();
388
        beg++;
389
        string sender = *beg;
390
        beg++;
391
        vector<string> receivers(beg, toks.end());
392
        setReceiversFromSender(sender, receivers);
596
    } else if (opflags & OPT_s) {
393
    } else if (opflags & OPT_s) {
597
        if (toks.size() < 3)
394
        if (toks.size() < 3)
598
            return 1;
395
            return 1;
599
        vector<string>::iterator beg = toks.begin();
396
        vector<string>::iterator beg = toks.begin();
600
        beg++;
397
        beg++;
601
        string master = *beg;
398
        string master = *beg;
602
        beg++;
399
        beg++;
603
        vector<string> slaves(beg, toks.end());
400
        vector<string> slaves(beg, toks.end());
604
        ohSongcast(master, slaves);
401
        setReceiversFromReceiver(master, slaves);
605
    } else if (opflags & OPT_x) {
402
    } else if (opflags & OPT_x) {
606
        if (toks.size() < 2)
403
        if (toks.size() < 2)
607
            return 1;
404
            return 1;
608
        vector<string>::iterator beg = toks.begin();
405
        vector<string>::iterator beg = toks.begin();
609
        beg++;
406
        beg++;
610
        vector<string> slaves(beg, toks.end());
407
        vector<string> slaves(beg, toks.end());
611
        ohNoSongcast(slaves);
408
        stopReceivers(slaves);
612
    } else {
409
    } else {
613
        LOGERR("scctl: server: bad cmd:" << toks[0] << endl);
410
        LOGERR("scctl: server: bad cmd:" << toks[0] << endl);
614
        return 1;
411
        return 1;
615
    }
412
    }
616
413