Parent: [d35ae6] (diff)

Download this file

rdcvolume.cpp    227 lines (197 with data), 6.6 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// This libupnpp sample control program connects to a media renderer,
// designated by its friendly name or uuid, and lets you adjust the
// volume from the keyboard. It uses the MediaRenderer and
// RenderingControl device and service classes from libupnpp.
#include <termios.h>
#include <string>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include "libupnpp/upnpplib.hxx"
#include "libupnpp/control/discovery.hxx"
#include "libupnpp/control/mediarenderer.hxx"
using namespace std;
using namespace UPnPClient;
using namespace UPnPP;
// Event reporter. We are not really using it here, apart from printing
// stuff to the console.
class MyReporter : public VarEventReporter {
public:
MyReporter(const string& nm, RDCH rdc)
: m_nm(nm), m_srv(rdc)
{
m_srv->installReporter(this);
}
virtual ~MyReporter() {
m_srv->installReporter(0);
}
virtual void changed(const char *nm, int value)
{
cerr << m_nm << " : Changed: " << nm << " (int): " << value << endl;
}
virtual void changed(const char *nm, const char *value)
{
cerr << m_nm << " : Changed: " << nm << " (str): " << value << endl;
}
private:
string m_nm;
RDCH m_srv;
};
//
// Device discovery part. We can't just connect to the device, UPnP
// does not work like this. The lib is going to broadcast a request
// for devices to signal their presence, withing a fixed time window
// (a few seconds). We could just wait for the full window and then
// connect, but here, we are doing the fancy thing, setting up a
// callback which will be called as each new device manifests itself,
// so that we can connect asap. The callback is called from a
// different thread, so we need locking.
std::mutex discolock;
std::condition_variable discocv;
// Using shared variables, but the callback is an std::function, so
// there are other possibilities.
UPnPDeviceDesc o_devicedesc;
string o_name;
static bool discoCB(const UPnPDeviceDesc& device, const UPnPServiceDesc&)
{
std::unique_lock<std::mutex> lock(discolock);
//cerr << "discoCB: got " << device.friendlyName << endl;
if (!device.UDN.compare(o_name) || !device.friendlyName.compare(o_name)) {
//cerr << "discoCB: FOUND\n";
o_devicedesc = device;
discocv.notify_all();
}
return true;
}
MRDH getRenderer(const string& name)
{
// Add a discovery callback, and remember about it in case we're
// called several times (not the case in this program).
o_name = name;
static int cbindex = -1;
if (cbindex == -1) {
cbindex = UPnPDeviceDirectory::addCallback(discoCB);
}
// Initialize and get a discovery directory handle. This must be
// done *after* the callback is set up, else we may miss devices.
static UPnPDeviceDirectory *superdir;
if (superdir == 0) {
superdir = UPnPDeviceDirectory::getTheDir();
if (superdir == 0) {
cerr << "Discovery init failed\n";
return MRDH();
}
}
// Until the initial delay is through, use the reporter to test
// devices as they come, so that we may respond asap
for (;;) {
std::unique_lock<std::mutex> lock(discolock);
#if FUTURE
// Older versions of the lib don't have this.
int ms = superdir->getRemainingDelayMs();
#else
int ms = superdir->getRemainingDelay() * 1000;
#endif
if (ms > 0) {
discocv.wait_for(lock, std::chrono::milliseconds(ms));
if (!o_devicedesc.UDN.compare(name) ||
!o_devicedesc.friendlyName.compare(name)) {
//cerr << "getRenderer: early wakeup\n";
return MRDH(new MediaRenderer(o_devicedesc));
}
} else {
// Initial delay done. We'll try one last time to ask the
// directory about our device
break;
}
}
// Try one last time just in case.
if (superdir->getDevByUDN(name, o_devicedesc)) {
return MRDH(new MediaRenderer(o_devicedesc));
} else if (superdir->getDevByFName(name, o_devicedesc)) {
return MRDH(new MediaRenderer(o_devicedesc));
}
cerr << "Can't connect to " << name << endl;
return MRDH();
}
// nothing to see here: character reading, one at a time.
int mygetch()
{
struct termios oldt, newt;
int ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
int main(int argc, char **argv)
{
argv++;argc--;
if (argc != 1) {
cerr << "Usage: rdcvolume rendererNameOrUid\n";
return 1;
}
string devname(*argv++);
argc--;
// Initialize libupnpp logging
Logger::getTheLog("stderr")->setLogLevel(Logger::LLDEB1);
// Explicitely initialize libupnpp so that we can display a
// possible error
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;
}
// Connect to the device
MRDH rdr = getRenderer(devname);
if (!rdr) {
cerr << "Renderer " << devname << " not found" << endl;
return 1;
}
// The MediaRender class has magic to create the well-know service
// class instances, here RenderingControl
RDCH rdc = rdr->rdc();
if (!rdc) {
cerr << "Device " << devname <<
" has no RenderingControl service" << endl;
return 1;
}
// Create the event-reporting object. Not used here actually, but
// it will print volume change events.
new MyReporter(devname, rdc);
cout << "q = quit, 'u' = up, 'd' = down\n";
for (;;) {
int vol = rdc->getVolume();
cout << "Volume now " << vol << endl;
int key = mygetch();
if (key == 'q') {
cout << "QUIT\n";
break;
} else if (key == 'u') {
vol += 5;
if (vol > 100) {
vol = 100;
}
} else if (key == 'd') {
vol -= 5;
if (vol < 0) {
vol = 0;
}
} else {
cout << "Bad key: " << (char)key << endl;
continue;
}
if (rdc->setVolume(vol)) {
cerr << "setVolume(" << vol << ") failed\n";
}
}
return 0;
}