New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Send custom headers with segment downloads #25
Conversation
src/main.cpp
Outdated
@@ -35,6 +35,7 @@ | |||
|
|||
ADDON::CHelper_libXBMC_addon *xbmc = 0; | |||
std::uint16_t kodiDisplayWidth(0), kodiDisplayHeight(0); | |||
std::string pipe; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me it feels wrong to use header fields from manifest requestl for the underlying stream files.
Its great that it works for the example in issue #20, but there could be some other constellations in the future.
Additional Headers should be passed using a new listItem property and has to be kept in session or adaptive_tree_ instance. They should then be passed to AdaptvieStream::download as a parameter value.
Beside this singletons on global scope (pipe) are not longer supported in future binary addons because multiple instances of the addon can be opened (pls. see agile branch)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing. I've slowly managed to figure it out - I've got it to take the info from the listitem property, store in the session, pass the info to the adaptivetree_ instance so it can be passed to the download function in main.
Working now on how to parse the curl options string
Something like this? (that is forked from agile) I don't know how to compile the addons so it's really just to maybe help out @glennguy But is that what you had in mind @peak3d ? I'm still not sure what is best name for all the variables. |
@matthuisman yes, exactly this way, there are no kodi changes needed as you already fugured out. Edit: naming: I'm working internally with CURLOPTIONS (and not with the URL | headers convention) because the | convention could be removed in the future) From naming I would suppose something wich reflects this: inputstream.adaptive.segment_curl_option_header) I know its long name, but its at least readable. |
@peak3d
The range header and segment_curl_option_headers could be set in the AdaptiveStream::download_segment() function. Is there an existing method that takes a curl header string and converts to a map? If we don't want to append the string to the URL and let KODI handle it - then I guess we need a way of parsing it our-self? Unless curl add-header can accept that string of multiple headers? My initial idea was allowing multiple headers to be added using the & to separate them (like the | method). |
@peak3d Should we just have a string of header options that are delimited by '&' and the header values url encoded, just like kodi accepts after the pipe? Also, should there be a 'pre-approved' list of headers eg. as there is in the Kodi source here and here? |
@glennguy yes, pass headers somehow separated through the new listitem property, pass the string at initialization time to the AdaptiveTree. AdaptiveStream has access to the AdaptiveTree and can pass it as string (const std::string &) to the download override methode. In download you have to split (there is a split function in helpers.h/cpp) and loop over the list and call CURL_AddOption (making another split for key value there). Depending if you want to solve it by yourself or not, I have a little time tomorrow and can put it in if you want. Edit: for sure you can create a |
@peak3d Thanks for the help, I think I should be able to solve it now. I'll push some changes, later today hopefully. |
@peak3d @matthuisman Changes made - tested with the following .strm file:
I followed the example in wvdecrypter.cpp which made it quite easy in the end. |
src/main.cpp
Outdated
@@ -232,6 +219,15 @@ bool KodiAdaptiveStream::download(const char* urlPointer, const char* rangeHeade | |||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_HEADER, "Range", rangeHeader); | |||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_HEADER, "Connection", "keep-alive"); | |||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_PROTOCOL, "acceptencoding", "gzip, deflate"); | |||
if (strcmp(segmentHeaders, "") != 0) | |||
{ | |||
std::vector<std::string> header, headers = split(segmentHeaders, '&'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the string is encoded.. would you not be splitting on %26?
Maybe best to decode the entire string before this first split?
This would cover both the use of & or %26
Added benifit of not needing the decode in the for loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. '&' is used as a delimiter to separate the keys. The values are what is encoded.
CurlAddOption seems to take care of the decoding itself. The convention in the above example I posted is the same as what Kodi requires after the pipe from add-ons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. OK.
Sorry, i thought it was following the pipe convention (whole string URL encoded)
src/main.cpp
Outdated
: manifest_type_(manifestType) | ||
, mpdFileURL_(strURL) | ||
, license_key_(strLicKey) | ||
, license_type_(strLicType) | ||
, license_data_(strLicData) | ||
, segment_headers_(strSegmentHeaders) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to set the variable segment_headers_ at all? (It's not actually used anywhere.)
Could we not just pass strSegmentHeaders direct to adaptiveTree?
adaptiveTree_->segment_headers_ = strSegmentHeaders;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right you are. Removed.
Still seems a bit odd having to define the same headers 3x times... @peak3d Individual specific headers could still be used by the pipe method on the key (eg. form encoded) / mpd if needed.
|
the motivation to allow custom headers should not be to bypass geo restrictions... |
@JinRonin |
@JinRonin up until recently Netflix has filtered certain User-Agent headers. Seeing that large amount of work that has recently gone into adding support to play Netflix content, and once the kodi-agile fork is merged and Kodi 18 is released the assumed following large amount of users taking advantage of it, there's a high likelihood that that Kodi User-Agent headers will be filtered on Netflix's end. The stream file posted by @matthuisman in issue #20 has merely been a convenient way to test the implementation of this feature. I don't believe there has been any explicit or implicit statement at least from myself that the motivation behind this PR has been to bypass geoblocking. |
I often find users who are in the correct country but use a VPN. |
Off-topic. |
I'll look today over the changes, sry for late response... |
@matthuisman: stream-key is already in the license_key template, therefore only manifest / media has to be decided. Ok, lets start with an general Header key, wich is valid for both manifest + media (not license_key!!) But pls. transport the header keys as bespoken from session -> tree -> download() |
In the MPD download function, the URL might have headers in it (using |) Therefore, we would need to set our custom headers before the URL is parsed. DashTree and SmoothTree call download directly when getting the MPD file. I suggest we have an override download function in AdapativeTree.cpp that accepts just a url and then calls download with the url and streamheaders? Here is some changes to look at (I haven't done the h files) I also don't know how to compile the addons so it's just to maybe help out @glennguy :) I also added our headers before parsing the URL in the other download function. Thoughts? |
It was not expected that someone uses | for the mpd call, but for sure you are rugth, this is possible.
After that you have 2 parsed maps, one for tree, one for stream. download then ONLY works with the extra passed headers, the | separated has been removed earlier. |
@peak3d sounds good, thanks. I'll get some changes made tonight. |
@peak3d I think I've satisfied your comment above. mpd_url is always stripped of pipe and anything following it. If there is no stream_headers listitem set, then if there is pipe/headers then it is used for the manifest download only If there is a stream_headers listitem, then it is used for both manifest and media. I've left the headers in string format as it seemed a lot easier passing it around, and the parsing is done in the main/download functions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to be picky but there are some general principles wich should be respected:
- write functions if something is dome multiple times
- keep it simple / group similar things together
- try to choose a readable workflow.
I know that especially the third point depends on the person who looks on it, but on the last end I'm the person who maintains the code and I have to understand things also if I haven't looked on things some months.
src/common/AdaptiveStream.cpp
Outdated
@@ -96,7 +96,8 @@ bool AdaptiveStream::download_segment() | |||
rangeHeader = rangebuf; | |||
} | |||
|
|||
return download(strURL.c_str(), rangeHeader); | |||
media_headers_ = tree_.media_headers_; | |||
return download(strURL.c_str(), rangeHeader, media_headers_.c_str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need 2 parameters to pass headers,
We should use a std::map to keep things simple (I know I was against it but here it makles sense)
media_header_['Range'] = rangeHeader? rangeHeader : "";
src/common/AdaptiveTree.cpp
Outdated
@@ -48,6 +48,8 @@ namespace adaptive | |||
, download_speed_(0.0) | |||
, average_download_speed_(0.0f) | |||
, encryptionState_(ENCRYTIONSTATE_UNENCRYPTED) | |||
, manifest_headers_("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stl classes have their own constructor, no need to assign a value
src/common/AdaptiveTree.h
Outdated
@@ -192,6 +192,7 @@ namespace adaptive | |||
bool has_timeshift_buffer_; | |||
|
|||
uint32_t bandwidth_; | |||
std::string manifest_headers_, media_headers_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
media_headers don't have to be in tree after the last duiscussion (separate headers for mpd / media)
should be std::map
src/common/AdaptiveStream.h
Outdated
@@ -79,6 +79,7 @@ namespace adaptive | |||
const AdaptiveTree::Segment *current_seg_; | |||
//We assume that a single segment can build complete frames | |||
std::string segment_buffer_; | |||
std::string media_headers_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::map
src/common/AdaptiveTree.h
Outdated
@@ -223,7 +224,7 @@ namespace adaptive | |||
bool empty(){ return !current_period_ || current_period_->adaptationSets_.empty(); }; | |||
const AdaptationSet *GetAdaptationSet(unsigned int pos) const { return current_period_ && pos < current_period_->adaptationSets_.size() ? current_period_->adaptationSets_[pos] : 0; }; | |||
protected: | |||
virtual bool download(const char* url); | |||
virtual bool download(const char* url, const char* manifestHeaders); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const std::map &
src/main.cpp
Outdated
@@ -219,6 +229,15 @@ bool KodiAdaptiveStream::download(const char* url, const char* rangeHeader) | |||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_HEADER, "Range", rangeHeader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
obsolete if Range is in headerMap
src/main.cpp
Outdated
@@ -919,6 +938,10 @@ Session::Session(MANIFEST_TYPE manifestType, const char *strURL, const char *str | |||
b64_decode(strCert, sz, server_certificate_.UseData(), dstsz); | |||
server_certificate_.SetDataSize(dstsz); | |||
} | |||
|
|||
adaptiveTree_->manifest_headers_ = strManifestHeaders; | |||
adaptiveTree_->media_headers_ = strMediaHeaders; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
media_headers_ pls not in tree
media_heders should be passed to AdaptiveStream here:
https://github.com/peak3d/inputstream.adaptive/blob/agile/src/main.cpp#L1521
src/main.cpp
Outdated
@@ -1434,7 +1457,8 @@ extern "C" { | |||
{ | |||
xbmc->Log(ADDON::LOG_DEBUG, "Open()"); | |||
|
|||
const char *lt(""), *lk(""), *ld(""), *lsc(""); | |||
const char *lt(""), *lk(""), *ld(""), *lsc(""), *manh(""), *medh(""), *m_strURL; | |||
m_strURL = props.m_strURL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this an member? we don't need it permanent.
Should be local variable
src/main.cpp
Outdated
@@ -1468,6 +1501,15 @@ extern "C" { | |||
} | |||
} | |||
|
|||
if (strcmp(manh, "") == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very confusing for me, after 5 times reading I still don't understand why need 3 strings for this task.
IMO this is the place where:
- make a copy of props.m_strURL
- look if | is in string, if so:
- if inputstream.adaptive.stream_headers is not given, parse it into mahd
- remove the | part from the copy.
Parsing of a string for header parts should be done in a function (parseheader(std::map, const char *)
Doing the parsing stuff should be only done once (here!!), having it in download() means that we parse the same stuff every segment again, this is not very resource friendly.
src/main.cpp
Outdated
@@ -1434,7 +1457,8 @@ extern "C" { | |||
{ | |||
xbmc->Log(ADDON::LOG_DEBUG, "Open()"); | |||
|
|||
const char *lt(""), *lk(""), *ld(""), *lsc(""); | |||
const char *lt(""), *lk(""), *ld(""), *lsc(""), *manh(""), *medh(""), *m_strURL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
manh / medh are std::map<std::string, std::string>
@peak3d Thank you so much for the feedback, not being picky at all. My apologies that it seems as I don't really know what I'm doing with regards to a lot of the changes, I'm very much a beginner (less than a month) at c++. I am keen to contribute but please say if it is unhelpful at my skill/experience level. I think I've addressed the issues now. We're now using maps to pass the headers around, and there is a separate function in helpers.cpp to parse them. |
@glennguy way of programming is different from dev to dev, its like starting a friendship where each other has to figure out how the other one's character is. I really appreciate that you go the way with me, the future is important and the more people contribute the better. Pls. leave 2 download methds so far as we do not know what's happening in the future. I'll look over the changes today, keep head up :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nearly done, great work! Only the 2 minor cosmetics and we're done!
src/main.cpp
Outdated
{ | ||
// open the file | ||
void* file = xbmc->CURLCreate(url); | ||
if (!file) | ||
return false; | ||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_PROTOCOL, "seekable", "0"); | ||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_PROTOCOL, "acceptencoding", "gzip"); | ||
if (!manifestHeaders.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed to request for empty() here.
If you are lazy in writing you could also do:
for (const auto &entry : manifestHeaders)
{
src/main.cpp
Outdated
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_HEADER, "Connection", "keep-alive"); | ||
xbmc->CURLAddOption(file, XFILE::CURL_OPTION_PROTOCOL, "acceptencoding", "gzip, deflate"); | ||
if (!mediaHeaders.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls. see previous comment
Great work guys! I'm guessing this change will only be in KODI 18.0 builds as this is on agile branch? |
@peak3d all sorted, and changed to 'lazy' for loop. Seems a lot easier to read that way. Thanks for your patience and spoon feeding me some of it, it has helped me learn a lot. @matthuisman this PR is against the master branch so its for 17.1. I imagine it will get pulled into the agile branch but @peak3d will decide that. I see you mentioned you haven't got a build environment set up for this, let me know if you need some help - I can build for some platforms. |
Thanx @glennguy ! |
sorry for reviving an old thread, so do we just need to define the stream_headers and that will apply the headers to both media and mpd? I am trying out the X-Forwarded-For passed to the stream headers. |
Hi @peak3d
This fixes #20. I'm very new to c++ so I don't know if this implementation is a good way of going about it particularly because it relies on a global variable, but it works nonetheless.