Switch to unified view

a/src/cdplugins/tidal.cxx b/src/cdplugins/tidal.cxx
...
...
44
using json = nlohmann::json;
44
using json = nlohmann::json;
45
using namespace UPnPProvider;
45
using namespace UPnPProvider;
46
46
47
class StreamHandle {
47
class StreamHandle {
48
public:
48
public:
49
    StreamHandle() : avio(0), offset(0) {
49
    StreamHandle(Tidal::Internal *plg) : avio(0), offset(0) {
50
    }
50
    }
51
    ~StreamHandle() {
51
    ~StreamHandle() {
52
        clear();
52
        clear();
53
    }
53
    }
54
    void clear() {
54
    void clear() {
55
        plg = 0;
55
        if (avio)
56
        if (avio)
56
            avio_close(avio);
57
            avio_close(avio);
57
        path.clear();
58
        path.clear();
58
        media_url.clear();
59
        media_url.clear();
59
        len = 0;
60
        len = 0;
60
        offset = 0;
61
        offset = 0;
61
        opentime = 0;
62
        opentime = 0;
62
    }
63
    }
64
    Tidal::Internal *plg;
63
    AVIOContext *avio;
65
    AVIOContext *avio;
64
    string path;
66
    string path;
65
    string media_url;
67
    string media_url;
66
    int len;
68
    long long len;
67
    off_t offset;
69
    off_t offset;
68
    time_t opentime;
70
    time_t opentime;
69
};
71
};
70
72
71
class Tidal::Internal {
73
class Tidal::Internal {
72
public:
74
public:
73
    Internal(Tidal *tidal, const vector<string>& pth, const string& hst,
75
    Internal(Tidal *tidal, const vector<string>& pth, const string& hst,
74
             int prt, const string& pp)
76
             int prt, const string& pp)
75
    : plg(tidal), path(pth), host(hst), port(prt), pathprefix(pp), kbs(0),
77
    : plg(tidal), path(pth), host(hst), port(prt), pathprefix(pp), kbs(0),
78
          laststream(this),
76
          mhd(0) { }
79
          mhd(0) { }
77
80
78
    bool maybeStartCmd(const string&);
81
    bool maybeStartCmd(const string&);
79
    string get_media_url(const std::string& path);
82
    string get_media_url(const std::string& path);
80
83
...
...
108
    StreamHandle laststream;
111
    StreamHandle laststream;
109
    // When using microhttpd
112
    // When using microhttpd
110
    struct MHD_Daemon *mhd;
113
    struct MHD_Daemon *mhd;
111
};
114
};
112
115
113
// Microhttpd connection handler. This is only used when working with
116
// Parse range header. 
114
// HTTP/FLAC. We re-build the complete url + query string
117
static void parseRanges(const string& ranges, vector<pair<int,int> >& oranges)
118
{
119
    oranges.clear();
120
    string::size_type pos = ranges.find("bytes=");
121
    if (pos == string::npos) {
122
        return;
123
    }
124
    pos += 6;
125
    bool done = false;
126
    while(!done) {
127
        string::size_type dash = ranges.find('-', pos);
128
        string::size_type comma = ranges.find(',', pos);
129
        string firstPart = dash != string::npos ? 
130
            ranges.substr(pos, dash-pos) : "";
131
        int start = firstPart.empty() ? 0 : atoi(firstPart.c_str());
132
        string secondPart = dash != string::npos ? 
133
            ranges.substr(dash+1, comma != string::npos ? 
134
                          comma-dash-1 : string::npos) : "";
135
        int fin = secondPart.empty() ? -1 : atoi(firstPart.c_str());
136
        pair<int,int> nrange(start,fin);
137
        oranges.push_back(nrange);
138
        if (comma != string::npos) {
139
            pos = comma + 1;
140
        }
141
        done = comma == string::npos;
142
    }
143
}
144
145
static void ContentReaderFreeCallback(void *cls)
146
{
147
    StreamHandle *hdl = (StreamHandle*)cls;
148
    delete hdl;
149
}
150
151
static ssize_t
152
data_generator(void *cls, uint64_t pos, char *buf, size_t max)
153
{
154
    StreamHandle *hdl = (StreamHandle *)cls;
155
    LOGDEB1("data_generator: pos " << pos << " max " << max << endl);
156
    return hdl->plg->read(cls, buf, max);
157
}
158
159
static const char *ValueKindToCp(enum MHD_ValueKind kind)
160
{
161
    switch (kind) {
162
    case MHD_RESPONSE_HEADER_KIND: return "Response header";
163
    case MHD_HEADER_KIND: return "HTTP header";
164
    case MHD_COOKIE_KIND: return "Cookies";
165
    case MHD_POSTDATA_KIND: return "POST data";
166
    case MHD_GET_ARGUMENT_KIND: return "GET (URI) arguments";
167
    case MHD_FOOTER_KIND: return "HTTP footer";
168
    default: return "Unknown";
169
    }
170
}
171
172
static int print_out_key (void *cls, enum MHD_ValueKind kind, 
173
                          const char *key, const char *value)
174
{
175
    LOGDEB(ValueKindToCp(kind) << ": " << key << " -> " << value << endl);
176
    return MHD_YES;
177
}
178
179
// Microhttpd connection handler. We re-build the complete url + query
115
// (&trackid=value), use this to retrieve a Tidal HTTP URL, and redirect to it.
180
// string (&trackid=value), use this to retrieve a Tidal URL, and
181
// either redirect to it if it is http or manage the reading.
116
static int answer_to_connection(void *cls, struct MHD_Connection *connection, 
182
static int answer_to_connection(void *cls, struct MHD_Connection *connection, 
117
                                const char *url, 
183
                                const char *url, 
118
                                const char *method, const char *version, 
184
                                const char *method, const char *version, 
119
                                const char *upload_data, 
185
                                const char *upload_data, 
120
                                size_t *upload_data_size, void **con_cls)
186
                                size_t *upload_data_size, void **con_cls)
...
...
146
    if (media_url.empty()) {
212
    if (media_url.empty()) {
147
        LOGERR("answer_to_connection: no media_uri for: " << url << endl);
213
        LOGERR("answer_to_connection: no media_uri for: " << url << endl);
148
        return MHD_NO;
214
        return MHD_NO;
149
    }
215
    }
150
216
217
    if (media_url.find("http") == 0) {
151
    LOGDEB("Tidal: redirecting to " << media_url << endl);
218
        LOGDEB("Tidal: redirecting to " << media_url << endl);
152
219
153
    static char data[] = "<html><body></body></html>";
220
        static char data[] = "<html><body></body></html>";
221
        struct MHD_Response *response =
222
            MHD_create_response_from_buffer(strlen(data), data,
223
                                            MHD_RESPMEM_PERSISTENT);
224
        if (response == NULL) {
225
            LOGERR("answer_to_connection: could not create response" << endl);
226
            return MHD_NO;
227
        }
228
        MHD_add_response_header (response, "Location", media_url.c_str());
229
        int ret = MHD_queue_response(connection, 302, response);
230
        MHD_destroy_response(response);
231
        return ret;
232
    }
233
234
    // rtmp stream, read and send.
235
    MHD_get_connection_values(connection, MHD_HEADER_KIND, &print_out_key, 0);
236
    const char* rangeh = MHD_lookup_connection_value(connection, 
237
                                                     MHD_HEADER_KIND, "range");
238
    vector<pair<int,int> > ranges;
239
    if (rangeh) {
240
        LOGDEB("answer_to_connection: got ranges\n");
241
        parseRanges(rangeh, ranges);
242
    }
243
244
    // open will ccall get_media_url again but this is ok because the
245
    // value is cached
246
    StreamHandle *hdl = (StreamHandle*)me->open(path);
247
248
    long long size = hdl->len;
249
    LOGDEB("answer_to_connection: stream size: " << size << endl);
250
    
251
    if (ranges.size()) {
252
        if (ranges[0].second != -1) {
253
            size = ranges[0].second - ranges[0].first + 1;
254
        }
255
        me->seek(hdl, ranges[0].first, 0);
256
    }
257
154
    struct MHD_Response *response =
258
    struct MHD_Response *response = 
155
        MHD_create_response_from_buffer(strlen(data), data,
259
        MHD_create_response_from_callback(size, 100*1024, &data_generator, 
156
                                        MHD_RESPMEM_PERSISTENT);
260
                                          hdl, ContentReaderFreeCallback);
157
    if (response == NULL) {
261
    if (response == NULL) {
158
        LOGERR("answer_to_connection: could not create response" << endl);
262
        LOGERR("httpgate: answer: could not create response" << endl);
159
        return MHD_NO;
263
        return MHD_NO;
160
    }
264
    }
265
    MHD_add_response_header (response, "Content-Type", "application/flv");
266
    if (size > 0) {
267
        string ssize;
268
        lltodecstr(size, ssize);
161
    MHD_add_response_header (response, "Location", media_url.c_str());
269
        MHD_add_response_header (response, "Content-Length", ssize.c_str());
270
    }
271
    MHD_add_response_header (response, "Accept-Ranges", "bytes");
162
    int ret = MHD_queue_response(connection, 302, response);
272
    int ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
163
    MHD_destroy_response(response);
273
    MHD_destroy_response(response);
164
    return ret;
274
    return ret;
165
}
275
}
166
276
167
static int accept_policy(void *, const struct sockaddr* sa, socklen_t addrlen)
277
static int accept_policy(void *, const struct sockaddr* sa, socklen_t addrlen)
...
...
183
    if (!conf || !conf->get("tidalquality", tidalquality)) {
293
    if (!conf || !conf->get("tidalquality", tidalquality)) {
184
        LOGERR("Tidal: can't get parameter 'tidalquality' from config\n");
294
        LOGERR("Tidal: can't get parameter 'tidalquality' from config\n");
185
        return false;
295
        return false;
186
    }
296
    }
187
297
188
    bool using_miniserver = tidalquality.compare("lossless");
189
    
298
    
190
    if (using_miniserver) {
299
    if (tidalquality.compare("lossless")) {
191
        av_register_all();
300
        av_register_all();
192
        avformat_network_init();
301
        avformat_network_init();
193
        VirtualDir::FileOps ops;
302
    }
194
        ops.getinfo = bind(&Tidal::Internal::getinfo, this, _1, _2);
195
        ops.open = bind(&Tidal::Internal::open, this, _1);
196
        ops.read = bind(&Tidal::Internal::read, this, _1, _2, _3);
197
        ops.seek = bind(&Tidal::Internal::seek, this, _1, _2, _3);
198
        ops.close = bind(&Tidal::Internal::close, this, _1);
199
        plg->m_services->setfileops(plg, plg->m_services->getpathprefix(plg),
200
                                    ops);
201
    } else {
202
        port = 49149;
303
    port = 49149;
203
        string sport;
304
    string sport;
204
        if (conf->get("tidalmicrohttpport", sport)) {
305
    if (conf->get("tidalmicrohttpport", sport)) {
205
            port = atoi(sport.c_str());
306
        port = atoi(sport.c_str());
206
        }
307
    }
207
        mhd = MHD_start_daemon(
308
    mhd = MHD_start_daemon(
208
            MHD_USE_THREAD_PER_CONNECTION,
309
        MHD_USE_THREAD_PER_CONNECTION,
209
            //MHD_USE_SELECT_INTERNALLY, 
310
        //MHD_USE_SELECT_INTERNALLY, 
210
            port, 
311
        port, 
211
            /* Accept policy callback and arg */
312
        /* Accept policy callback and arg */
212
            accept_policy, NULL, 
313
        accept_policy, NULL, 
213
            /* handler and arg */
314
        /* handler and arg */
214
            &answer_to_connection, this, 
315
        &answer_to_connection, this, 
215
            MHD_OPTION_END);
316
        MHD_OPTION_END);
216
        if (nullptr == mhd) {
317
    if (nullptr == mhd) {
217
            LOGERR("Tidal: MHD_start_daemon failed\n");
318
        LOGERR("Tidal: MHD_start_daemon failed\n");
218
            return false;
319
        return false;
219
        }
220
    }
320
    }
221
        
321
        
222
    string pythonpath = string("PYTHONPATH=") +
322
    string pythonpath = string("PYTHONPATH=") +
223
        path_cat(g_datadir, "cdplugins/pycommon");
323
        path_cat(g_datadir, "cdplugins/pycommon");
224
    string configname = string("UPMPD_CONFIG=") + g_configfilename;
324
    string configname = string("UPMPD_CONFIG=") + g_configfilename;
...
...
329
    auto result = avio_open(&avio, media_url.c_str(), AVIO_FLAG_READ);
429
    auto result = avio_open(&avio, media_url.c_str(), AVIO_FLAG_READ);
330
    if (result != 0) {
430
    if (result != 0) {
331
            LOGERR("Tidal: avio_open failed for [" << media_url << "]\n");
431
            LOGERR("Tidal: avio_open failed for [" << media_url << "]\n");
332
            return nullptr;
432
            return nullptr;
333
    }
433
    }
334
    StreamHandle *hdl = new StreamHandle;
434
    StreamHandle *hdl = new StreamHandle(this);
335
        hdl->path = path;
435
        hdl->path = path;
336
        hdl->media_url = media_url;
436
        hdl->media_url = media_url;
337
    hdl->avio = avio;
437
    hdl->avio = avio;
438
        LOGDEB("Tidal::open: avio_size returns " << avio_size(avio) << endl);
439
        hdl->len = avio_size(avio);
440
        if (hdl->len < 0)
441
            hdl->len = -1;
338
        hdl->opentime = time(0);
442
        hdl->opentime = time(0);
339
    return hdl;
443
    return hdl;
340
    } else {
444
    } else {
341
        // This should not happen.
445
        // This should not happen.
342
        LOGERR("Tidal::open: called for http stream ??\n");
446
        LOGERR("Tidal::open: called for http stream ??\n");
...
...
344
    }
448
    }
345
}
449
}
346
450
347
int Tidal::Internal::read(void *_hdl, char* buf, size_t cnt)
451
int Tidal::Internal::read(void *_hdl, char* buf, size_t cnt)
348
{
452
{
349
    LOGDEB("Tidal::read: " << cnt << endl);
453
    LOGDEB1("Tidal::read: " << cnt << endl);
350
    if (!_hdl)
454
    if (!_hdl)
351
    return -1;
455
    return -1;
352
456
353
    // The pupnp http code has a default 1MB buffer size which is much
457
    // The pupnp http code has a default 1MB buffer size which is much
354
    // too big for us (too slow, esp. because tidal will stall).
458
    // too big for us (too slow, esp. because tidal will stall).
...
...
362
    auto totread = avio_read(hdl->avio, (unsigned char *)buf, cnt);
466
    auto totread = avio_read(hdl->avio, (unsigned char *)buf, cnt);
363
    if (totread <= 0) {
467
    if (totread <= 0) {
364
            LOGERR("Tidal: avio_read(" << cnt << ") failed\n");
468
            LOGERR("Tidal: avio_read(" << cnt << ") failed\n");
365
            return -1;
469
            return -1;
366
    }
470
    }
367
    LOGDEB("Tidal::read: total read: " << totread << endl);
471
    LOGDEB1("Tidal::read: total read: " << totread << endl);
368
        hdl->offset += totread;
472
        hdl->offset += totread;
369
    return int(totread);
473
    return int(totread);
370
    } else {
474
    } else {
371
    LOGERR("Tidal::read: no handle\n");
475
    LOGERR("Tidal::read: no handle\n");
372
    return -1;
476
    return -1;
...
...
467
{
571
{
468
    LOGDEB("Tidal::browse\n");
572
    LOGDEB("Tidal::browse\n");
469
    if (!m->maybeStartCmd("browse")) {
573
    if (!m->maybeStartCmd("browse")) {
470
    return -1;
574
    return -1;
471
    }
575
    }
576
    string sbflg;
577
    switch (flg) {
578
    case CDPlugin::BFMeta:
579
        sbflg = "meta";
580
        break;
581
    case CDPlugin::BFChildren:
582
    default:
583
        sbflg = "children";
584
        break;
585
    }
586
472
    unordered_map<string, string> res;
587
    unordered_map<string, string> res;
473
    if (!m->cmd.callproc("browse", {{"objid", objid}}, res)) {
588
    if (!m->cmd.callproc("browse", {{"objid", objid}, {"flag", sbflg}}, res)) {
474
    LOGERR("Tidal::browse: slave failure\n");
589
    LOGERR("Tidal::browse: slave failure\n");
475
    return -1;
590
    return -1;
476
    }
591
    }
477
592
478
    auto it = res.find("entries");
593
    auto it = res.find("entries");