|
a/src/ohradio.cxx |
|
b/src/ohradio.cxx |
|
... |
|
... |
21 |
|
21 |
|
22 |
#include <upnp/upnp.h>
|
22 |
#include <upnp/upnp.h>
|
23 |
|
23 |
|
24 |
#include <functional>
|
24 |
#include <functional>
|
25 |
#include <iostream>
|
25 |
#include <iostream>
|
|
|
26 |
#include <sstream>
|
26 |
#include <string>
|
27 |
#include <string>
|
27 |
#include <utility>
|
28 |
#include <utility>
|
28 |
#include <vector>
|
29 |
#include <vector>
|
|
|
30 |
#include <json/json.h>
|
29 |
|
31 |
|
30 |
#include "libupnpp/base64.hxx"
|
32 |
#include "libupnpp/base64.hxx"
|
31 |
#include "libupnpp/log.hxx"
|
33 |
#include "libupnpp/log.hxx"
|
32 |
#include "libupnpp/soaphelp.hxx"
|
34 |
#include "libupnpp/soaphelp.hxx"
|
33 |
#include "libupnpp/upnpavutils.hxx"
|
35 |
#include "libupnpp/upnpavutils.hxx"
|
|
... |
|
... |
48 |
|
50 |
|
49 |
static const string sTpProduct("urn:av-openhome-org:service:Radio:1");
|
51 |
static const string sTpProduct("urn:av-openhome-org:service:Radio:1");
|
50 |
static const string sIdProduct("urn:av-openhome-org:serviceId:Radio");
|
52 |
static const string sIdProduct("urn:av-openhome-org:serviceId:Radio");
|
51 |
|
53 |
|
52 |
struct RadioMeta {
|
54 |
struct RadioMeta {
|
53 |
RadioMeta(const string& t, const string& u, const string& au = string(),
|
55 |
RadioMeta(const string& t, const string& u, const string& au,
|
54 |
const string& as = string())
|
56 |
const string& as, const string& ms)
|
55 |
: title(t), uri(u), artUri(au), dynArtUri(au) {
|
57 |
: title(t), uri(u), artUri(au), dynArtUri(au) {
|
56 |
if (!as.empty()) {
|
58 |
if (!as.empty()) {
|
57 |
stringToStrings(as, artScript);
|
59 |
stringToStrings(as, artScript);
|
58 |
}
|
60 |
}
|
|
|
61 |
if (!ms.empty()) {
|
|
|
62 |
stringToStrings(ms, metaScript);
|
|
|
63 |
}
|
59 |
}
|
64 |
}
|
60 |
string title;
|
65 |
string title;
|
61 |
string uri;
|
66 |
string uri;
|
62 |
string artUri;
|
67 |
string artUri;
|
|
|
68 |
// Script to retrieve current art
|
63 |
vector<string> artScript;
|
69 |
vector<string> artScript;
|
|
|
70 |
// Script to retrieve all metadata
|
|
|
71 |
vector<string> metaScript;
|
|
|
72 |
// Time after which we should re-fire the metadata script
|
|
|
73 |
time_t nextMetaScriptExecTime{0};
|
64 |
string dynArtUri;
|
74 |
string dynArtUri;
|
|
|
75 |
string dynTitle;
|
|
|
76 |
string dynArtist;
|
65 |
};
|
77 |
};
|
66 |
|
78 |
|
67 |
static vector<RadioMeta> o_radios;
|
79 |
static vector<RadioMeta> o_radios;
|
68 |
|
80 |
|
69 |
OHRadio::OHRadio(UpMpd *dev)
|
81 |
OHRadio::OHRadio(UpMpd *dev)
|
|
... |
|
... |
119 |
static void getRadiosFromConf(ConfSimple* conf)
|
131 |
static void getRadiosFromConf(ConfSimple* conf)
|
120 |
{
|
132 |
{
|
121 |
vector<string> allsubk = conf->getSubKeys_unsorted();
|
133 |
vector<string> allsubk = conf->getSubKeys_unsorted();
|
122 |
for (auto it = allsubk.begin(); it != allsubk.end(); it++) {
|
134 |
for (auto it = allsubk.begin(); it != allsubk.end(); it++) {
|
123 |
if (it->find("radio ") == 0) {
|
135 |
if (it->find("radio ") == 0) {
|
124 |
string uri, artUri, artScript;
|
136 |
string uri, artUri, artScript, metaScript;
|
125 |
string title = it->substr(6);
|
137 |
string title = it->substr(6);
|
126 |
bool ok = conf->get("url", uri, *it);
|
138 |
bool ok = conf->get("url", uri, *it);
|
127 |
conf->get("artUrl", artUri, *it);
|
139 |
conf->get("artUrl", artUri, *it);
|
128 |
conf->get("artScript", artScript, *it);
|
140 |
conf->get("artScript", artScript, *it);
|
129 |
trimstring(artScript, " \t\n\r");
|
141 |
trimstring(artScript, " \t\n\r");
|
|
|
142 |
conf->get("metaScript", metaScript, *it);
|
|
|
143 |
trimstring(metaScript, " \t\n\r");
|
130 |
if (ok && !uri.empty()) {
|
144 |
if (ok && !uri.empty()) {
|
131 |
o_radios.push_back(RadioMeta(title, uri, artUri, artScript));
|
145 |
o_radios.push_back(RadioMeta(title, uri, artUri, artScript,
|
|
|
146 |
metaScript));
|
132 |
LOGDEB1("OHRadio::readRadios:RADIO: [" << title << "] uri ["
|
147 |
LOGDEB0("OHRadio::readRadios:RADIO: [" << title << "] uri ["
|
133 |
<< uri << "] artUri [" << artUri << "]\n");
|
148 |
<< uri << "] artUri [" << artUri << "]\n");
|
134 |
}
|
149 |
}
|
135 |
}
|
150 |
}
|
136 |
}
|
151 |
}
|
137 |
}
|
152 |
}
|
138 |
|
153 |
|
139 |
bool OHRadio::readRadios()
|
154 |
bool OHRadio::readRadios()
|
140 |
{
|
155 |
{
|
141 |
// Id 0 means no selection
|
156 |
// Id 0 means no selection
|
142 |
o_radios.push_back(RadioMeta("Unknown radio", "", ""));
|
157 |
o_radios.push_back(RadioMeta("Unknown radio", "", "", "", ""));
|
143 |
|
158 |
|
144 |
std::unique_lock<std::mutex>(g_configlock);
|
159 |
std::unique_lock<std::mutex>(g_configlock);
|
145 |
getRadiosFromConf(g_config);
|
160 |
getRadiosFromConf(g_config);
|
146 |
// Also if radiolist is defined, get from there
|
161 |
// Also if radiolist is defined, get from there
|
147 |
string radiolistfn;
|
162 |
string radiolistfn;
|
|
... |
|
... |
198 |
MpdStatus mpds = m_dev->getMpdStatusNoUpdate();
|
213 |
MpdStatus mpds = m_dev->getMpdStatusNoUpdate();
|
199 |
|
214 |
|
200 |
st["ChannelsMax"] = SoapHelp::i2s(o_radios.size());
|
215 |
st["ChannelsMax"] = SoapHelp::i2s(o_radios.size());
|
201 |
st["Id"] = SoapHelp::i2s(m_id);
|
216 |
st["Id"] = SoapHelp::i2s(m_id);
|
202 |
makeIdArray(st["IdArray"]);
|
217 |
makeIdArray(st["IdArray"]);
|
|
|
218 |
|
203 |
if (m_active && m_id >= 0 && m_id < o_radios.size()) {
|
219 |
if (m_active && m_id >= 0 && m_id < o_radios.size()) {
|
204 |
if (mpds.currentsong.album.empty()) {
|
220 |
if (mpds.currentsong.album.empty()) {
|
205 |
mpds.currentsong.album = o_radios[m_id].title;
|
221 |
mpds.currentsong.album = o_radios[m_id].title;
|
206 |
}
|
222 |
}
|
207 |
|
223 |
|
208 |
// Some radios provide a url to the art for the current song. Possibly
|
|
|
209 |
// execute script to retrieve it
|
|
|
210 |
RadioMeta& radio = o_radios[m_id];
|
224 |
RadioMeta& radio = o_radios[m_id];
|
211 |
LOGDEB2("OHRadio::makestate: artScript: " << radio.artScript << endl);
|
225 |
|
|
|
226 |
// Some radios do not insert icy metadata in the stream, but rather
|
|
|
227 |
// provide a script to retrieve it.
|
|
|
228 |
if (mpds.currentsong.title.empty() && mpds.currentsong.artist.empty()
|
|
|
229 |
&& radio.metaScript.size()) {
|
|
|
230 |
if (time(0) > radio.nextMetaScriptExecTime) {
|
|
|
231 |
string data;
|
|
|
232 |
if (ExecCmd::backtick(radio.metaScript, data)) {
|
|
|
233 |
LOGDEB0("OHRadio::makestate: metaScript got: [" << data <<
|
|
|
234 |
"]\n");
|
|
|
235 |
// The data is in JSON format
|
|
|
236 |
try {
|
|
|
237 |
Json::Value decoded;
|
|
|
238 |
istringstream input(data);
|
|
|
239 |
input >> decoded;
|
|
|
240 |
radio.dynTitle = decoded.get("title", "").asString();
|
|
|
241 |
radio.dynArtist = decoded.get("artist", "").asString();
|
|
|
242 |
radio.dynArtUri = decoded.get("artUrl", "").asString();
|
|
|
243 |
radio.nextMetaScriptExecTime = time(0) +
|
|
|
244 |
decoded.get("reload", "10").asInt();
|
|
|
245 |
} catch (...) {
|
|
|
246 |
LOGERR("OHRadio::makestate: Json decode failed for [" <<
|
|
|
247 |
data << "]");
|
|
|
248 |
radio.nextMetaScriptExecTime = time(0) + 10;
|
|
|
249 |
}
|
|
|
250 |
}
|
|
|
251 |
}
|
|
|
252 |
mpds.currentsong.title = radio.dynTitle;
|
|
|
253 |
mpds.currentsong.artist = radio.dynArtist;
|
|
|
254 |
}
|
|
|
255 |
|
|
|
256 |
// Some radios provide a url to the art for the current song.
|
|
|
257 |
// Execute script to retrieve it if the current title+artist changed
|
212 |
if (radio.artScript.size()) {
|
258 |
if (radio.artScript.size()) {
|
213 |
string nsong(mpds.currentsong.title + mpds.currentsong.artist);
|
259 |
string nsong(mpds.currentsong.title + mpds.currentsong.artist);
|
214 |
if (nsong.compare(m_currentsong)) {
|
260 |
if (nsong.compare(m_currentsong)) {
|
215 |
m_currentsong = nsong;
|
261 |
m_currentsong = nsong;
|
216 |
string uri;
|
262 |
string uri;
|
217 |
radio.dynArtUri.clear();
|
263 |
radio.dynArtUri.clear();
|
218 |
if (ExecCmd::backtick(radio.artScript, uri)) {
|
264 |
if (ExecCmd::backtick(radio.artScript, uri)) {
|
219 |
trimstring(uri, " \t\r\n");
|
265 |
trimstring(uri, " \t\r\n");
|
220 |
LOGDEB("OHRadio::makestate: artScript got: [" << uri <<
|
266 |
LOGDEB0("OHRadio::makestate: artScript got: [" << uri <<
|
221 |
"]\n");
|
267 |
"]\n");
|
222 |
radio.dynArtUri = uri;
|
268 |
radio.dynArtUri = uri;
|
223 |
}
|
269 |
}
|
224 |
}
|
270 |
}
|
225 |
}
|
271 |
}
|
226 |
mpds.currentsong.artUri = radio.dynArtUri.empty() ? radio.artUri :
|
272 |
mpds.currentsong.artUri = radio.dynArtUri.empty() ? radio.artUri :
|
|
... |
|
... |
253 |
if (m_id > o_radios.size() || o_radios[m_id].uri.empty()) {
|
299 |
if (m_id > o_radios.size() || o_radios[m_id].uri.empty()) {
|
254 |
LOGERR("OHRadio::setPlaying: called with bad id (" << m_id <<
|
300 |
LOGERR("OHRadio::setPlaying: called with bad id (" << m_id <<
|
255 |
") or empty preset uri [" << o_radios[m_id].uri << "]\n");
|
301 |
") or empty preset uri [" << o_radios[m_id].uri << "]\n");
|
256 |
return UPNP_E_INTERNAL_ERROR;
|
302 |
return UPNP_E_INTERNAL_ERROR;
|
257 |
}
|
303 |
}
|
|
|
304 |
|
|
|
305 |
RadioMeta& radio = o_radios[m_id];
|
|
|
306 |
radio.nextMetaScriptExecTime = 0;
|
258 |
|
307 |
|
259 |
string cmdpath = path_cat(g_datadir, "rdpl2stream");
|
308 |
string cmdpath = path_cat(g_datadir, "rdpl2stream");
|
260 |
cmdpath = path_cat(cmdpath, "fetchStream.py");
|
309 |
cmdpath = path_cat(cmdpath, "fetchStream.py");
|
261 |
|
310 |
|
262 |
// Execute the playlist parser
|
311 |
// Execute the playlist parser
|
263 |
ExecCmd cmd;
|
312 |
ExecCmd cmd;
|
264 |
vector<string> args;
|
313 |
vector<string> args;
|
265 |
args.push_back(o_radios[m_id].uri);
|
314 |
args.push_back(radio.uri);
|
266 |
LOGDEB("OHRadio::setPlaying: exec: " << cmdpath << " " << args[0] << endl);
|
315 |
LOGDEB("OHRadio::setPlaying: exec: " << cmdpath << " " << args[0] << endl);
|
267 |
if (cmd.startExec(cmdpath, args, false, true) < 0) {
|
316 |
if (cmd.startExec(cmdpath, args, false, true) < 0) {
|
268 |
LOGDEB("OHRadio::setPlaying: startExec failed for " <<
|
317 |
LOGDEB("OHRadio::setPlaying: startExec failed for " <<
|
269 |
cmdpath << " " << args[0] << endl);
|
318 |
cmdpath << " " << args[0] << endl);
|
270 |
return UPNP_E_INTERNAL_ERROR;
|
319 |
return UPNP_E_INTERNAL_ERROR;
|
|
... |
|
... |
283 |
}
|
332 |
}
|
284 |
|
333 |
|
285 |
// Send url to mpd
|
334 |
// Send url to mpd
|
286 |
m_dev->m_mpdcli->clearQueue();
|
335 |
m_dev->m_mpdcli->clearQueue();
|
287 |
UpSong song;
|
336 |
UpSong song;
|
288 |
song.album = o_radios[m_id].title;
|
337 |
song.album = radio.title;
|
289 |
song.uri = o_radios[m_id].uri;
|
338 |
song.uri = radio.uri;
|
290 |
if (m_dev->m_mpdcli->insert(audiourl, 0, song) < 0) {
|
339 |
if (m_dev->m_mpdcli->insert(audiourl, 0, song) < 0) {
|
291 |
LOGDEB("OHRadio::setPlaying: mpd insert failed\n");
|
340 |
LOGDEB("OHRadio::setPlaying: mpd insert failed\n");
|
292 |
return UPNP_E_INTERNAL_ERROR;
|
341 |
return UPNP_E_INTERNAL_ERROR;
|
293 |
}
|
342 |
}
|
294 |
m_dev->m_mpdcli->single(true);
|
343 |
m_dev->m_mpdcli->single(true);
|