|
a/src/cdplugins/tidal.cxx |
|
b/src/cdplugins/tidal.cxx |
|
... |
|
... |
36 |
using namespace std;
|
36 |
using namespace std;
|
37 |
using namespace std::placeholders;
|
37 |
using namespace std::placeholders;
|
38 |
using json = nlohmann::json;
|
38 |
using json = nlohmann::json;
|
39 |
using namespace UPnPProvider;
|
39 |
using namespace UPnPProvider;
|
40 |
|
40 |
|
|
|
41 |
class StreamHandle {
|
|
|
42 |
public:
|
|
|
43 |
StreamHandle() : rtmp(nullptr), http_handle(nullptr), offset(0) {
|
|
|
44 |
}
|
|
|
45 |
~StreamHandle() {
|
|
|
46 |
clear();
|
|
|
47 |
}
|
|
|
48 |
void clear() {
|
|
|
49 |
if (rtmp) {
|
|
|
50 |
RTMP_Close(rtmp);
|
|
|
51 |
RTMP_Free(rtmp);
|
|
|
52 |
}
|
|
|
53 |
if (http_handle) {
|
|
|
54 |
LOGDEB("StreamHandle:~: closing http handle\n");
|
|
|
55 |
UpnpCloseHttpGet(http_handle);
|
|
|
56 |
LOGDEB("StreamHandle:~: close done\n");
|
|
|
57 |
}
|
|
|
58 |
len = 0;
|
|
|
59 |
offset = 0;
|
|
|
60 |
media_url.clear();
|
|
|
61 |
path.clear();
|
|
|
62 |
}
|
|
|
63 |
string path;
|
|
|
64 |
string media_url;
|
|
|
65 |
RTMP *rtmp;
|
|
|
66 |
void *http_handle;
|
|
|
67 |
int len;
|
|
|
68 |
off_t offset;
|
|
|
69 |
time_t opentime;
|
|
|
70 |
};
|
|
|
71 |
|
41 |
class Tidal::Internal {
|
72 |
class Tidal::Internal {
|
42 |
public:
|
73 |
public:
|
43 |
Internal(const vector<string>& pth, const string& hp, const string& pp)
|
74 |
Internal(const vector<string>& pth, const string& hp, const string& pp)
|
44 |
: path(pth), httphp(hp), pathprefix(pp), lasttime(0) { }
|
75 |
: path(pth), httphp(hp), pathprefix(pp) { }
|
45 |
|
76 |
|
46 |
bool maybeStartCmd(const string&);
|
77 |
bool maybeStartCmd(const string&);
|
47 |
string get_media_url(const std::string& path);
|
78 |
string get_media_url(const std::string& path);
|
48 |
string get_mimetype(const std::string& path);
|
79 |
string get_mimetype(const std::string& path);
|
49 |
|
80 |
|
|
... |
|
... |
61 |
string pathprefix;
|
92 |
string pathprefix;
|
62 |
// mimetype is a constant for a given session, depend on quality
|
93 |
// mimetype is a constant for a given session, depend on quality
|
63 |
// choice only. Initialized once
|
94 |
// choice only. Initialized once
|
64 |
string mimetype;
|
95 |
string mimetype;
|
65 |
|
96 |
|
66 |
// Cached uri translation for the vdir: we do this in getinfo()
|
97 |
// Cached uri translation and stream: set in getinfo() and reused
|
67 |
// and reuse in open()
|
98 |
// in open()
|
68 |
string lastpath;
|
99 |
StreamHandle laststream;
|
69 |
string lastmediaurl;
|
|
|
70 |
time_t lasttime;
|
|
|
71 |
};
|
100 |
};
|
72 |
|
101 |
|
73 |
bool Tidal::Internal::maybeStartCmd(const string& who)
|
102 |
bool Tidal::Internal::maybeStartCmd(const string& who)
|
74 |
{
|
103 |
{
|
75 |
LOGDEB1("Tidal::maybeStartCmd for: " << who << endl);
|
104 |
LOGDEB1("Tidal::maybeStartCmd for: " << who << endl);
|
|
... |
|
... |
87 |
}
|
116 |
}
|
88 |
}
|
117 |
}
|
89 |
return true;
|
118 |
return true;
|
90 |
}
|
119 |
}
|
91 |
|
120 |
|
92 |
string Tidal::Internal::get_media_url(const std::string& path)
|
|
|
93 |
{
|
|
|
94 |
if (!maybeStartCmd("get_media_url")) {
|
|
|
95 |
return string();
|
|
|
96 |
}
|
|
|
97 |
if (lastpath.compare(path) || (time(0) - lasttime > 10)) {
|
|
|
98 |
unordered_map<string, string> res;
|
|
|
99 |
if (!cmd.callproc("trackuri", {{"path", path}}, res)) {
|
|
|
100 |
LOGERR("Tidal::get_media_url: slave failure\n");
|
|
|
101 |
return string();
|
|
|
102 |
}
|
|
|
103 |
|
|
|
104 |
auto it = res.find("media_url");
|
|
|
105 |
if (it == res.end()) {
|
|
|
106 |
LOGERR("Tidal::get_media_url: no media url in result\n");
|
|
|
107 |
return string();
|
|
|
108 |
}
|
|
|
109 |
lastmediaurl = it->second;
|
|
|
110 |
lastpath = path;
|
|
|
111 |
lasttime = time(0);
|
|
|
112 |
}
|
|
|
113 |
|
|
|
114 |
LOGDEB("Tidal: got media url [" << lastmediaurl << "]\n");
|
|
|
115 |
return lastmediaurl;
|
|
|
116 |
}
|
|
|
117 |
|
|
|
118 |
string Tidal::Internal::get_mimetype(const std::string& path)
|
121 |
string Tidal::Internal::get_mimetype(const std::string& path)
|
119 |
{
|
122 |
{
|
120 |
if (!maybeStartCmd("get_mimetype")) {
|
123 |
if (!maybeStartCmd("get_mimetype")) {
|
121 |
return string();
|
124 |
return string();
|
122 |
}
|
125 |
}
|
|
... |
|
... |
136 |
LOGDEB("Tidal: got mimetype [" << mimetype << "]\n");
|
139 |
LOGDEB("Tidal: got mimetype [" << mimetype << "]\n");
|
137 |
}
|
140 |
}
|
138 |
return mimetype;
|
141 |
return mimetype;
|
139 |
}
|
142 |
}
|
140 |
|
143 |
|
141 |
class StreamHandle {
|
144 |
string Tidal::Internal::get_media_url(const std::string& path)
|
142 |
public:
|
145 |
{
|
143 |
StreamHandle() : rtmp(nullptr), http_handle(nullptr) {
|
146 |
if (!maybeStartCmd("get_media_url")) {
|
|
|
147 |
return string();
|
144 |
}
|
148 |
}
|
145 |
~StreamHandle() {
|
149 |
time_t now = time(0);
|
146 |
if (rtmp) {
|
150 |
if (laststream.path.compare(path) || (now - laststream.opentime > 10)) {
|
147 |
RTMP_Close(rtmp);
|
151 |
unordered_map<string, string> res;
|
148 |
RTMP_Free(rtmp);
|
152 |
if (!cmd.callproc("trackuri", {{"path", path}}, res)) {
|
149 |
}
|
153 |
LOGERR("Tidal::get_media_url: slave failure\n");
|
150 |
if (http_handle) {
|
154 |
return string();
|
151 |
LOGDEB("StreamHandle:~: closing http handle\n");
|
|
|
152 |
UpnpCloseHttpGet(http_handle);
|
|
|
153 |
LOGDEB("StreamHandle:~: close done\n");
|
|
|
154 |
}
|
|
|
155 |
}
|
|
|
156 |
|
155 |
}
|
157 |
RTMP *rtmp;
|
156 |
|
158 |
void *http_handle;
|
157 |
auto it = res.find("media_url");
|
159 |
int len;
|
158 |
if (it == res.end()) {
|
|
|
159 |
LOGERR("Tidal::get_media_url: no media url in result\n");
|
|
|
160 |
return string();
|
|
|
161 |
}
|
|
|
162 |
laststream.clear();
|
|
|
163 |
laststream.path = path;
|
|
|
164 |
laststream.media_url = it->second;
|
|
|
165 |
laststream.opentime = now;
|
|
|
166 |
}
|
|
|
167 |
|
|
|
168 |
LOGDEB("Tidal: got media url [" << laststream.media_url << "]\n");
|
|
|
169 |
return laststream.media_url;
|
160 |
};
|
170 |
}
|
161 |
|
|
|
162 |
|
171 |
|
163 |
int Tidal::Internal::getinfo(const std::string& path, VirtualDir::FileInfo *inf)
|
172 |
int Tidal::Internal::getinfo(const std::string& path, VirtualDir::FileInfo *inf)
|
164 |
{
|
173 |
{
|
165 |
LOGDEB("Tidal::getinfo: " << path << endl);
|
174 |
LOGDEB("Tidal::getinfo: " << path << endl);
|
|
|
175 |
|
|
|
176 |
laststream.clear();
|
166 |
string media_url = get_media_url(path);
|
177 |
string media_url = get_media_url(path);
|
167 |
if (media_url.empty()) {
|
178 |
if (media_url.empty()) {
|
168 |
return -1;
|
179 |
return -1;
|
169 |
}
|
180 |
}
|
170 |
inf->file_length = -1;
|
181 |
inf->file_length = -1;
|
171 |
inf->last_modified = 0;
|
182 |
inf->last_modified = 0;
|
172 |
inf->mime = get_mimetype(path);
|
183 |
inf->mime = get_mimetype(path);
|
173 |
if (media_url.find("http") == 0) {
|
184 |
if (media_url.find("http") == 0) {
|
174 |
void *http_handle;
|
|
|
175 |
char *content_type;
|
185 |
char *content_type;
|
176 |
int content_length;
|
|
|
177 |
int httpstatus;
|
186 |
int httpstatus;
|
178 |
int code = UpnpOpenHttpGet(media_url.c_str(), &http_handle,
|
187 |
int code = UpnpOpenHttpGet(media_url.c_str(), &laststream.http_handle,
|
179 |
&content_type, &content_length,
|
188 |
&content_type, &laststream.len,
|
180 |
&httpstatus, 30);
|
189 |
&httpstatus, 30);
|
181 |
LOGDEB("Tidal::getinfo: UpnpOpenHttpGet: ret " << code <<
|
190 |
LOGDEB("Tidal::getinfo: UpnpOpenHttpGet: ret " << code <<
|
182 |
" mtype " << content_type << " length " << content_length <<
|
191 |
" mtype " << content_type << " length " << laststream.len <<
|
183 |
" HTTP status " << httpstatus << endl);
|
192 |
" HTTP status " << httpstatus << endl);
|
184 |
if (code) {
|
193 |
if (code) {
|
185 |
LOGERR("Tidal::getinfo: UpnpOpenHttpGet: ret " << code <<
|
194 |
LOGERR("Tidal::getinfo: UpnpOpenHttpGet: ret " << code <<
|
186 |
" mtype " << content_type << " length " << content_length <<
|
195 |
" mtype " << content_type << " length " << laststream.len <<
|
187 |
" HTTP status " << httpstatus << endl);
|
196 |
" HTTP status " << httpstatus << endl);
|
188 |
} else {
|
197 |
} else {
|
189 |
inf->file_length = content_length;
|
198 |
inf->file_length = laststream.len;
|
190 |
LOGDEB("Tidal:getinfo: got file length "<< inf->file_length <<endl);
|
199 |
LOGDEB("Tidal:getinfo: got file length "<< inf->file_length <<endl);
|
191 |
}
|
200 |
}
|
192 |
// Doc says to free this, but it causes malloc issues
|
|
|
193 |
//free(content_type);
|
|
|
194 |
StreamHandle hdl;
|
|
|
195 |
hdl.http_handle = http_handle;
|
|
|
196 |
// Let StreamHandle clean up
|
|
|
197 |
}
|
201 |
}
|
198 |
LOGDEB("Tidal::getinfo: returning\n");
|
202 |
LOGDEB("Tidal::getinfo: returning\n");
|
199 |
return 0;
|
203 |
return 0;
|
200 |
}
|
204 |
}
|
201 |
|
|
|
202 |
|
205 |
|
203 |
void *Tidal::Internal::open(const string& path)
|
206 |
void *Tidal::Internal::open(const string& path)
|
204 |
{
|
207 |
{
|
205 |
LOGDEB("Tidal::open: " << path << endl);
|
208 |
LOGDEB("Tidal::open: " << path << endl);
|
206 |
string media_url = get_media_url(path);
|
209 |
string media_url = get_media_url(path);
|
207 |
if (media_url.empty()) {
|
210 |
if (media_url.empty()) {
|
208 |
return nullptr;
|
211 |
return nullptr;
|
209 |
}
|
212 |
}
|
210 |
if (media_url.find("http") == 0) {
|
213 |
if (media_url.find("http") == 0) {
|
211 |
void *http_handle;
|
214 |
if (laststream.http_handle == nullptr) {
|
212 |
char *content_type;
|
215 |
char *content_type;
|
213 |
int content_length;
|
216 |
int httpstatus;
|
214 |
int httpstatus;
|
|
|
215 |
int code = UpnpOpenHttpGet(media_url.c_str(), &http_handle,
|
217 |
int code = UpnpOpenHttpGet(media_url.c_str(),
|
216 |
&content_type, &content_length,
|
218 |
&laststream.http_handle,
|
217 |
&httpstatus, 30);
|
219 |
&content_type, &laststream.len,
|
|
|
220 |
&httpstatus, 30);
|
218 |
LOGDEB("Tidal::open: UpnpOpenHttpGet: ret " << code <<
|
221 |
LOGDEB("Tidal::open: UpnpOpenHttpGet: ret " << code <<
|
219 |
" mtype " << content_type << " length " << content_length <<
|
222 |
" mtype " << content_type << " length " <<
|
|
|
223 |
laststream.len <<
|
220 |
" HTTP status " << httpstatus << endl);
|
224 |
" HTTP status " << httpstatus << endl);
|
221 |
if (code) {
|
225 |
if (code) {
|
222 |
LOGERR("Tidal::open: UpnpOpenHttpGet: ret " << code <<
|
226 |
LOGERR("Tidal::open: UpnpOpenHttpGet: ret " << code <<
|
223 |
" mtype " << content_type << " length " << content_length <<
|
227 |
" mtype " << content_type << " length " <<
|
|
|
228 |
laststream.len <<
|
224 |
" HTTP status " << httpstatus << endl);
|
229 |
" HTTP status " << httpstatus << endl);
|
225 |
return nullptr;
|
230 |
return nullptr;
|
226 |
}
|
231 |
}
|
|
|
232 |
}
|
227 |
// Doc says to free this, but it causes malloc issues
|
233 |
// Doc says to free content_type, but it causes malloc issues
|
228 |
//free(content_type);
|
234 |
//free(content_type);
|
229 |
StreamHandle *hdl = new StreamHandle;
|
235 |
StreamHandle *hdl = new StreamHandle;
|
230 |
hdl->http_handle = http_handle;
|
236 |
hdl->http_handle = laststream.http_handle;
|
231 |
hdl->len = content_length;
|
237 |
hdl->len = laststream.len;
|
|
|
238 |
hdl->media_url = media_url;
|
|
|
239 |
laststream.http_handle = nullptr;
|
|
|
240 |
laststream.clear();
|
232 |
return hdl;
|
241 |
return hdl;
|
233 |
} else {
|
242 |
} else {
|
234 |
RTMP *rtmp = RTMP_Alloc();
|
243 |
RTMP *rtmp = RTMP_Alloc();
|
235 |
RTMP_Init(rtmp);
|
244 |
RTMP_Init(rtmp);
|
236 |
|
245 |
|
|
... |
|
... |
265 |
if (!_hdl)
|
274 |
if (!_hdl)
|
266 |
return -1;
|
275 |
return -1;
|
267 |
|
276 |
|
268 |
// The pupnp http code has a default 1MB buffer size which is much
|
277 |
// The pupnp http code has a default 1MB buffer size which is much
|
269 |
// too big for us (too slow, esp. because tidal will stall).
|
278 |
// too big for us (too slow, esp. because tidal will stall).
|
270 |
if (cnt > 100 * 1024)
|
279 |
const int mybsize = 200 * 1024;
|
271 |
cnt = 100 * 1024;
|
280 |
if (cnt > mybsize)
|
|
|
281 |
cnt = mybsize;
|
272 |
|
282 |
|
273 |
StreamHandle *hdl = (StreamHandle *)_hdl;
|
283 |
StreamHandle *hdl = (StreamHandle *)_hdl;
|
274 |
|
284 |
|
275 |
if (hdl->rtmp) {
|
285 |
if (hdl->rtmp) {
|
276 |
RTMP *rtmp = hdl->rtmp;
|
286 |
RTMP *rtmp = hdl->rtmp;
|
|
... |
|
... |
281 |
if (didread <= 0)
|
291 |
if (didread <= 0)
|
282 |
break;
|
292 |
break;
|
283 |
totread += didread;
|
293 |
totread += didread;
|
284 |
}
|
294 |
}
|
285 |
LOGDEB("Tidal::read: total read: " << totread << endl);
|
295 |
LOGDEB("Tidal::read: total read: " << totread << endl);
|
|
|
296 |
hdl->offset += totread;
|
286 |
return totread > 0 ? totread : -1;
|
297 |
return totread > 0 ? totread : -1;
|
287 |
} else if (hdl->http_handle) {
|
298 |
} else if (hdl->http_handle) {
|
288 |
int code = UpnpReadHttpGet(hdl->http_handle, buf, &cnt, 30);
|
299 |
int code = UpnpReadHttpGet(hdl->http_handle, buf, &cnt, 30);
|
289 |
if (code) {
|
300 |
if (code) {
|
290 |
LOGERR("Tidal::read: UpnpReadHttpGet returned " << code << endl);
|
301 |
LOGERR("Tidal::read: UpnpReadHttpGet returned " << code << endl);
|
291 |
return -1;
|
302 |
return -1;
|
292 |
}
|
303 |
}
|
|
|
304 |
hdl->offset += cnt;
|
293 |
return int(cnt);
|
305 |
return int(cnt);
|
294 |
} else {
|
306 |
} else {
|
295 |
LOGERR("Tidal::read: neither rtmp nor http\n");
|
307 |
LOGERR("Tidal::read: neither rtmp nor http\n");
|
296 |
return -1;
|
308 |
return -1;
|
297 |
}
|
309 |
}
|
298 |
}
|
310 |
}
|
299 |
|
311 |
|
300 |
off_t Tidal::Internal::seek(void *hdl, off_t offs, int whence)
|
312 |
off_t Tidal::Internal::seek(void *_hdl, off_t offs, int whence)
|
301 |
{
|
313 |
{
|
302 |
LOGDEB("Tidal::seek\n");
|
314 |
StreamHandle *hdl = (StreamHandle *)_hdl;
|
|
|
315 |
LOGDEB("Tidal::seek: offs "<< offs << " whence " << whence << " current " <<
|
|
|
316 |
hdl->offset << endl);
|
|
|
317 |
if (whence == 0) {
|
|
|
318 |
hdl->offset = offs;
|
|
|
319 |
} else if (whence == 1) {
|
|
|
320 |
hdl->offset += offs;
|
|
|
321 |
} else if (whence == 2) {
|
|
|
322 |
hdl->offset = hdl->len + offs;
|
|
|
323 |
}
|
|
|
324 |
if (hdl->http_handle) {
|
|
|
325 |
UpnpCloseHttpGet(hdl->http_handle);
|
|
|
326 |
char *content_type;
|
|
|
327 |
int content_length;
|
|
|
328 |
int httpstatus;
|
|
|
329 |
int code = UpnpOpenHttpGetEx(hdl->media_url.c_str(), &hdl->http_handle,
|
|
|
330 |
&content_type, &content_length,
|
|
|
331 |
&httpstatus, hdl->offset, 2000000000, 30);
|
|
|
332 |
LOGERR("Tidal::seek to " << hdl->offset <<
|
|
|
333 |
" UpnpOpenHttpGetEx: ret " << code <<
|
|
|
334 |
" mtype " << content_type << " length " << content_length <<
|
|
|
335 |
" HTTP status " << httpstatus << endl);
|
|
|
336 |
if (code) {
|
|
|
337 |
LOGERR("Tidal::seek to " << hdl->offset <<
|
|
|
338 |
" UpnpOpenHttpGetEx: ret " << code <<
|
|
|
339 |
" mtype " << content_type << " length " << content_length <<
|
|
|
340 |
" HTTP status " << httpstatus << endl);
|
|
|
341 |
return -1;
|
|
|
342 |
}
|
|
|
343 |
}
|
|
|
344 |
|
303 |
return -1;
|
345 |
return 0;
|
304 |
}
|
346 |
}
|
305 |
|
347 |
|
306 |
void Tidal::Internal::close(void *_hdl)
|
348 |
void Tidal::Internal::close(void *_hdl)
|
307 |
{
|
349 |
{
|
308 |
LOGDEB("Tidal::close\n");
|
350 |
LOGDEB("Tidal::close\n");
|