Skip to content
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

Support per-stream proxies in HLS streams (attempt 5) #9421

Merged
merged 8 commits into from Mar 22, 2016
37 changes: 34 additions & 3 deletions xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp
Expand Up @@ -20,6 +20,7 @@

#include "DVDDemuxFFmpeg.h"

#include <sstream>
#include <utility>

#include "commons/Exception.h"
Expand Down Expand Up @@ -230,9 +231,9 @@ bool CDVDDemuxFFmpeg::Open(CDVDInputStream* pInput, bool streaminfo, bool filein
{
// special stream type that makes avformat handle file opening
// allows internal ffmpeg protocols to be used
CURL url = m_pInput->GetURL();
AVDictionary *options = GetFFMpegOptionsFromInput();

AVDictionary *options = GetFFMpegOptionsFromURL(url);
CURL url = m_pInput->GetURL();

int result=-1;
if (url.IsProtocol("mms"))
Expand Down Expand Up @@ -599,9 +600,12 @@ void CDVDDemuxFFmpeg::SetSpeed(int iSpeed)
}
}

AVDictionary *CDVDDemuxFFmpeg::GetFFMpegOptionsFromURL(const CURL &url)
AVDictionary *CDVDDemuxFFmpeg::GetFFMpegOptionsFromInput()
{
const CDVDInputStreamFFmpeg *const input =
dynamic_cast<CDVDInputStreamFFmpeg*>(m_pInput);

const CURL url = m_pInput->GetURL();
AVDictionary *options = NULL;

if (url.IsProtocol("http") || url.IsProtocol("https"))
Expand Down Expand Up @@ -638,6 +642,33 @@ AVDictionary *CDVDDemuxFFmpeg::GetFFMpegOptionsFromURL(const CURL &url)
av_dict_set(&options, "cookies", cookies.c_str(), 0);

}

if (input)
{
const std::string host = input->GetProxyHost();
if (!host.empty() && input->GetProxyType() == "http")
{
std::ostringstream urlStream;

const uint16_t port = input->GetProxyPort();
const std::string user = input->GetProxyUser();
const std::string password = input->GetProxyPassword();

urlStream << "http://";

if (!user.empty()) {
urlStream << user;
if (!password.empty())
urlStream << ":" << password;
urlStream << "@";
}

urlStream << host << ':' << port;

av_dict_set(&options, "http_proxy", urlStream.str().c_str(), 0);
}
}

return options;
}

Expand Down
2 changes: 1 addition & 1 deletion xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h
Expand Up @@ -133,7 +133,7 @@ class CDVDDemuxFFmpeg : public CDVDDemux
bool IsVideoReady();
void ResetVideoStreams();

AVDictionary *GetFFMpegOptionsFromURL(const CURL &url);
AVDictionary *GetFFMpegOptionsFromInput();
double ConvertTimestamp(int64_t pts, int den, int num);
void UpdateCurrentPTS();
bool IsProgramChange();
Expand Down
143 changes: 138 additions & 5 deletions xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp
Expand Up @@ -19,12 +19,18 @@
*/

#include "DVDInputStreamFFmpeg.h"

#include "filesystem/CurlFile.h"
#include "playlists/PlayListM3U.h"
#include "settings/Settings.h"
#include "utils/log.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"

#include <limits.h>

using namespace XFILE;
using PLAYLIST::CPlayListM3U;

CDVDInputStreamFFmpeg::CDVDInputStreamFFmpeg(CFileItem& fileitem)
: CDVDInputStream(DVDSTREAM_TYPE_FFMPEG, fileitem)
Expand All @@ -50,18 +56,19 @@ bool CDVDInputStreamFFmpeg::IsEOF()

bool CDVDInputStreamFFmpeg::Open()
{
std::string selected;
if (m_item.IsInternetStream() && (m_item.IsType(".m3u8") || m_item.GetMimeType() == "application/vnd.apple.mpegurl"))
{
// get the available bandwidth and determine the most appropriate stream
int bandwidth = CSettings::GetInstance().GetInt(CSettings::SETTING_NETWORK_BANDWIDTH);
if(bandwidth <= 0)
bandwidth = INT_MAX;
selected = PLAYLIST::CPlayListM3U::GetBestBandwidthStream(m_item.GetPath(), bandwidth);
if (selected.compare(m_item.GetPath()) != 0)
const CURL playlist_url = m_item.GetURL();
const CURL selected = GetM3UBestBandwidthStream(playlist_url, bandwidth);
if (selected.Get() != playlist_url.Get())
{
CLog::Log(LOGINFO, "CDVDInputStreamFFmpeg: Auto-selecting %s based on configured bandwidth.", selected.c_str());
m_item.SetPath(selected.c_str());
CLog::Log(LOGINFO, "CDVDInputStreamFFmpeg: Auto-selecting %s based on configured bandwidth.",
selected.Get().c_str());
m_item.SetURL(selected);
}
}

Expand Down Expand Up @@ -109,3 +116,129 @@ int64_t CDVDInputStreamFFmpeg::Seek(int64_t offset, int whence)
return -1;
}

std::string CDVDInputStreamFFmpeg::GetProxyType() const
{
return m_item.HasProperty("proxy.type") ?
m_item.GetProperty("proxy.type").asString() : std::string();
}

std::string CDVDInputStreamFFmpeg::GetProxyHost() const
{
return m_item.HasProperty("proxy.host") ?
m_item.GetProperty("proxy.host").asString() : std::string();
}

uint16_t CDVDInputStreamFFmpeg::GetProxyPort() const
{
if (m_item.HasProperty("proxy.port"))
return m_item.GetProperty("proxy.port").asInteger();

// Select the standard port
const std::string value = GetProxyType();
if (value == "socks4" && value == "socks4a" &&

This comment was marked as spam.

value == "socks5" && value == "socks5-remote")
return 1080;
else
return 3128;
}

std::string CDVDInputStreamFFmpeg::GetProxyUser() const
{
return m_item.HasProperty("proxy.user") ?
m_item.GetProperty("proxy.user").asString() : std::string();
}

std::string CDVDInputStreamFFmpeg::GetProxyPassword() const
{
return m_item.HasProperty("proxy.password") ?
m_item.GetProperty("proxy.password").asString() : std::string();
}

CURL CDVDInputStreamFFmpeg::GetM3UBestBandwidthStream(const CURL &url, size_t bandwidth)
{
typedef CPlayListM3U M3U;
using std::string;
using std::map;

// we may be passed a playlist that does not contain playlists of different
// bitrates (eg: this playlist is really the HLS video). So, default the
// return to the filename so it can be played
char szLine[4096];
string strLine;
size_t maxBandwidth = 0;

CCurlFile file;

// set the proxy configuration
const string host = GetProxyHost();
if (!host.empty())
file.SetProxy(GetProxyType(), host, GetProxyPort(),
GetProxyUser(), GetProxyPassword());

// open the file, and if it fails, return
if (!file.Open(url))
{
file.Close();
return url;
}

// and set the fallback value
CURL subStreamUrl(url);

// determine the base
CURL basePlaylistUrl(URIUtils::GetParentPath(url.Get()));
basePlaylistUrl.SetOptions("");
basePlaylistUrl.SetProtocolOptions("");
const string basePart = basePlaylistUrl.Get();

// convert bandwidth specified in kbps to bps used by the m3u8
bandwidth *= 1000;

while (file.ReadString(szLine, 1024))
{
// read and trim a line
strLine = szLine;
StringUtils::Trim(strLine);

// skip the first line
if (strLine == M3U::StartMarker)
continue;
else if (StringUtils::StartsWith(strLine, M3U::StreamMarker))
{
// parse the line so we can pull out the bandwidth
const map< string, string > params = M3U::ParseStreamLine(strLine);
const map< string, string >::const_iterator it = params.find(M3U::BandwidthMarker);

if (it != params.end())
{
const size_t streamBandwidth = atoi(it->second.c_str());
if ((maxBandwidth < streamBandwidth) && (streamBandwidth <= bandwidth))
{
// read the next line
if (!file.ReadString(szLine, 1024))
continue;

strLine = szLine;
StringUtils::Trim(strLine);

// this line was empty
if (strLine.empty())
continue;

// store the max bandwidth
maxBandwidth = streamBandwidth;

// if the path is absolute just use it
if (CURL::IsFullPath(strLine))
subStreamUrl = CURL(strLine);
else
subStreamUrl = CURL(basePart + strLine);
}
}
}
}

// if any protocol options were set, restore them
subStreamUrl.SetProtocolOptions(url.GetProtocolOptions());
return subStreamUrl;
}
9 changes: 9 additions & 0 deletions xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamFFmpeg.h
Expand Up @@ -42,6 +42,15 @@ class CDVDInputStreamFFmpeg
bool CanSeek() { return m_can_seek; }
bool CanPause() { return m_can_pause; }

std::string GetProxyType() const;
std::string GetProxyHost() const;
uint16_t GetProxyPort() const;
std::string GetProxyUser() const;
std::string GetProxyPassword() const;

private:
CURL GetM3UBestBandwidthStream(const CURL &url, size_t bandwidth);

protected:
bool m_can_pause;
bool m_can_seek;
Expand Down
63 changes: 45 additions & 18 deletions xbmc/filesystem/CurlFile.cpp
Expand Up @@ -406,6 +406,8 @@ CCurlFile::~CCurlFile()

CCurlFile::CCurlFile()
: m_writeOffset(0)
, m_proxytype(PROXY_HTTP)
, m_proxyport(3128)
, m_overflowBuffer(NULL)
, m_overflowSize(0)
{
Expand All @@ -428,7 +430,6 @@ CCurlFile::CCurlFile()
m_password = "";
m_httpauth = "";
m_cipherlist = "";
m_proxytype = PROXY_HTTP;
m_state = new CReadState();
m_oldState = NULL;
m_skipshout = false;
Expand Down Expand Up @@ -607,13 +608,18 @@ void CCurlFile::SetCommonOptions(CReadState* state)
if (g_advancedSettings.m_curlDisableIPV6)
g_curlInterface.easy_setopt(h, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

if (m_proxy.length() > 0)
if (!m_proxyhost.empty())
{
g_curlInterface.easy_setopt(h, CURLOPT_PROXY, m_proxy.c_str());
g_curlInterface.easy_setopt(h, CURLOPT_PROXYTYPE, proxyType2CUrlProxyType[m_proxytype]);
if (m_proxyuserpass.length() > 0)
g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, m_proxyuserpass.c_str());

const std::string hostport = m_proxyhost +
StringUtils::Format(":%d", m_proxyport);
g_curlInterface.easy_setopt(h, CURLOPT_PROXY, hostport.c_str());

const std::string userpass =
m_proxyuser + std::string(":") + m_proxypassword;
if (!userpass.empty())
g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, userpass.c_str());
}
if (m_customrequest.length() > 0)
g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
Expand Down Expand Up @@ -754,20 +760,19 @@ void CCurlFile::ParseAndCorrectUrl(CURL &url2)
else if( url2.IsProtocol("http")
|| url2.IsProtocol("https"))
{
if (CSettings::GetInstance().GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY)
&& !CSettings::GetInstance().GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER).empty()
&& CSettings::GetInstance().GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT) > 0
&& m_proxy.empty())
const CSettings &s = CSettings::GetInstance();
if (m_proxyhost.empty()
&& s.GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY)
&& !s.GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER).empty()
&& s.GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT) > 0)
{
m_proxy = CSettings::GetInstance().GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER);
m_proxy += StringUtils::Format(":%d", CSettings::GetInstance().GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT));
if (CSettings::GetInstance().GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME).length() > 0 && m_proxyuserpass.empty())
{
m_proxyuserpass = CSettings::GetInstance().GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME);
m_proxyuserpass += ":" + CSettings::GetInstance().GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD);
}
m_proxytype = (ProxyType)CSettings::GetInstance().GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE);
CLog::Log(LOGDEBUG, "Using proxy %s, type %d", m_proxy.c_str(), proxyType2CUrlProxyType[m_proxytype]);
m_proxytype = (ProxyType)s.GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE);
m_proxyhost = s.GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER);
m_proxyport = s.GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT);
m_proxyuser = s.GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME);
m_proxypassword = s.GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD);
CLog::Log(LOGDEBUG, "Using proxy %s, type %d", m_proxyhost.c_str(),
proxyType2CUrlProxyType[m_proxytype]);
}

// get username and password
Expand Down Expand Up @@ -925,6 +930,28 @@ void CCurlFile::Reset()
m_state->m_cancelled = false;
}

void CCurlFile::SetProxy(const std::string &type, const std::string &host,
uint16_t port, const std::string &user, const std::string &password)
{
m_proxytype = CCurlFile::PROXY_HTTP;
if (type == "http")
m_proxytype = CCurlFile::PROXY_HTTP;
else if (type == "socks4")
m_proxytype = CCurlFile::PROXY_SOCKS4;
else if (type == "socks4a")
m_proxytype = CCurlFile::PROXY_SOCKS4A;
else if (type == "socks5")
m_proxytype = CCurlFile::PROXY_SOCKS5;
else if (type == "socks5-remote")
m_proxytype = CCurlFile::PROXY_SOCKS5_REMOTE;
else
CLog::Log(LOGERROR, "Invalid proxy type \"%s\"", type.c_str());
m_proxyhost = host;
m_proxyport = port;
m_proxyuser = user;
m_proxypassword = password;
}

bool CCurlFile::Open(const CURL& url)
{
if (!g_curlInterface.IsLoaded())
Expand Down