Switch to side-by-side view

--- a/src/ohradio.cxx
+++ b/src/ohradio.cxx
@@ -23,9 +23,11 @@
 
 #include <functional>
 #include <iostream>
+#include <sstream>
 #include <string>
 #include <utility>
 #include <vector>
+#include <json/json.h>
 
 #include "libupnpp/base64.hxx"
 #include "libupnpp/log.hxx"
@@ -50,18 +52,28 @@
 static const string sIdProduct("urn:av-openhome-org:serviceId:Radio");
 
 struct RadioMeta {
-    RadioMeta(const string& t, const string& u, const string& au = string(),
-              const string& as = string())
+    RadioMeta(const string& t, const string& u, const string& au,
+              const string& as, const string& ms)
         : title(t), uri(u), artUri(au), dynArtUri(au) {
         if (!as.empty()) {
             stringToStrings(as, artScript);
         }
+        if (!ms.empty()) {
+            stringToStrings(ms, metaScript);
+        }
     }
     string title;
     string uri;
     string artUri;
-    vector<string> artScript;
+    // Script to retrieve current art
+    vector<string> artScript; 
+    // Script to retrieve all metadata
+    vector<string> metaScript; 
+    // Time after which we should re-fire the metadata script
+    time_t nextMetaScriptExecTime{0}; 
     string dynArtUri;
+    string dynTitle;
+    string dynArtist;
 };
 
 static vector<RadioMeta> o_radios;
@@ -121,15 +133,18 @@
     vector<string> allsubk = conf->getSubKeys_unsorted();
     for (auto it = allsubk.begin(); it != allsubk.end(); it++) {
         if (it->find("radio ") == 0) {
-            string uri, artUri, artScript;
+            string uri, artUri, artScript, metaScript;
             string title = it->substr(6);
             bool ok = conf->get("url", uri, *it);
             conf->get("artUrl", artUri, *it);
             conf->get("artScript", artScript, *it);
             trimstring(artScript, " \t\n\r");
+            conf->get("metaScript", metaScript, *it);
+            trimstring(metaScript, " \t\n\r");
             if (ok && !uri.empty()) {
-                o_radios.push_back(RadioMeta(title, uri, artUri, artScript));
-                LOGDEB1("OHRadio::readRadios:RADIO: [" << title << "] uri ["
+                o_radios.push_back(RadioMeta(title, uri, artUri, artScript,
+                                             metaScript));
+                LOGDEB0("OHRadio::readRadios:RADIO: [" << title << "] uri ["
                         << uri << "] artUri [" << artUri << "]\n");
             }
         }
@@ -139,7 +154,7 @@
 bool OHRadio::readRadios()
 {
     // Id 0 means no selection
-    o_radios.push_back(RadioMeta("Unknown radio", "", ""));
+    o_radios.push_back(RadioMeta("Unknown radio", "", "", "", ""));
     
     std::unique_lock<std::mutex>(g_configlock);
     getRadiosFromConf(g_config);
@@ -200,15 +215,46 @@
     st["ChannelsMax"] = SoapHelp::i2s(o_radios.size());
     st["Id"] = SoapHelp::i2s(m_id);
     makeIdArray(st["IdArray"]);
+
     if (m_active && m_id >= 0 && m_id < o_radios.size()) {
         if (mpds.currentsong.album.empty()) {
             mpds.currentsong.album = o_radios[m_id].title;
         }
 
-        // Some radios provide a url to the art for the current song. Possibly
-        // execute script to retrieve it
         RadioMeta& radio = o_radios[m_id];
-        LOGDEB2("OHRadio::makestate: artScript: " << radio.artScript << endl);
+
+        // Some radios do not insert icy metadata in the stream, but rather
+        // provide a script to retrieve it.
+        if (mpds.currentsong.title.empty() && mpds.currentsong.artist.empty()
+            && radio.metaScript.size()) {
+            if (time(0) > radio.nextMetaScriptExecTime) {
+                string data;
+                if (ExecCmd::backtick(radio.metaScript, data)) {
+                    LOGDEB0("OHRadio::makestate: metaScript got: [" << data <<
+                            "]\n");
+                    // The data is in JSON format
+                    try {
+                        Json::Value decoded;
+                        istringstream input(data);
+                        input >> decoded;
+                        radio.dynTitle = decoded.get("title", "").asString();
+                        radio.dynArtist = decoded.get("artist", "").asString();
+                        radio.dynArtUri = decoded.get("artUrl", "").asString();
+                        radio.nextMetaScriptExecTime = time(0) +
+                            decoded.get("reload", "10").asInt();
+                    } catch (...) {
+                        LOGERR("OHRadio::makestate: Json decode failed for [" <<
+                               data << "]");
+                        radio.nextMetaScriptExecTime = time(0) + 10;
+                    }
+                }
+            }
+            mpds.currentsong.title = radio.dynTitle;
+            mpds.currentsong.artist = radio.dynArtist;
+        }
+
+        // Some radios provide a url to the art for the current song. 
+        // Execute script to retrieve it if the current title+artist changed
         if (radio.artScript.size()) {
             string nsong(mpds.currentsong.title + mpds.currentsong.artist);
             if (nsong.compare(m_currentsong)) {
@@ -217,8 +263,8 @@
                 radio.dynArtUri.clear();
                 if (ExecCmd::backtick(radio.artScript, uri)) {
                     trimstring(uri, " \t\r\n");
-                    LOGDEB("OHRadio::makestate: artScript got: [" << uri <<
-                           "]\n");
+                    LOGDEB0("OHRadio::makestate: artScript got: [" << uri <<
+                            "]\n");
                     radio.dynArtUri = uri;
                 }
             }
@@ -255,6 +301,9 @@
                ") or empty preset uri [" << o_radios[m_id].uri << "]\n");
         return UPNP_E_INTERNAL_ERROR;
     }
+
+    RadioMeta& radio = o_radios[m_id];
+    radio.nextMetaScriptExecTime = 0;
     
     string cmdpath = path_cat(g_datadir, "rdpl2stream");
     cmdpath = path_cat(cmdpath, "fetchStream.py");
@@ -262,7 +311,7 @@
     // Execute the playlist parser
     ExecCmd cmd;
     vector<string> args;
-    args.push_back(o_radios[m_id].uri);
+    args.push_back(radio.uri);
     LOGDEB("OHRadio::setPlaying: exec: " << cmdpath << " " << args[0] << endl);
     if (cmd.startExec(cmdpath, args, false, true) < 0) {
         LOGDEB("OHRadio::setPlaying: startExec failed for " <<
@@ -285,8 +334,8 @@
     // Send url to mpd
     m_dev->m_mpdcli->clearQueue();
     UpSong song;
-    song.album = o_radios[m_id].title;
-    song.uri = o_radios[m_id].uri;
+    song.album = radio.title;
+    song.uri = radio.uri;
     if (m_dev->m_mpdcli->insert(audiourl, 0, song) < 0) {
         LOGDEB("OHRadio::setPlaying: mpd insert failed\n");
         return UPNP_E_INTERNAL_ERROR;