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

Catchup id live urls #831

Merged
merged 8 commits into from
Feb 13, 2024
2 changes: 1 addition & 1 deletion pvr.iptvsimple/addon.xml.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="pvr.iptvsimple"
version="21.7.2"
version="21.8.0"
name="IPTV Simple Client"
provider-name="nightik and Ross Nicholson">
<requires>@ADDON_DEPENDS@
Expand Down
9 changes: 9 additions & 0 deletions pvr.iptvsimple/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
v2.8.0
- Support catchup-id for live URLs where possible
- Support the Y, m, d, H, M, S specifiers for live URLs, useful for plugins and debugging
- Enable Play from EPG in Live TV mode setting for Catchup VOD
- Only set connection-timeout for connection manager if it's not NFS
- Support the duration specifier for live URLs, useful for plugins and debugging
- Support the all specifier for live URLs, useful for plugins and debugging
- Fix timezone shift not applied for start time for live URLs

v21.7.2
- Only reset the catchup state if not playing a timeshifted EPG tag

Expand Down
2 changes: 1 addition & 1 deletion src/IptvSimple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ PVR_ERROR IptvSimple::GetEPGTagStreamProperties(const kodi::addon::PVREPGTag& ta
Logger::Log(LEVEL_DEBUG, "%s - GetPlayEpgAsLive is %s", __FUNCTION__, m_settings->CatchupPlayEpgAsLive() ? "enabled" : "disabled");

std::map<std::string, std::string> catchupProperties;
if (m_settings->CatchupPlayEpgAsLive() && m_currentChannel.CatchupSupportsTimeshifting())
if (m_settings->CatchupPlayEpgAsLive() && (m_currentChannel.CatchupSupportsTimeshifting() || m_currentChannel.GetCatchupMode() == CatchupMode::VOD))
{
m_catchupController.ProcessEPGTagForTimeshiftedPlayback(tag, m_currentChannel, catchupProperties);
}
Expand Down
68 changes: 64 additions & 4 deletions src/iptvsimple/CatchupController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ void CatchupController::ProcessChannelForPlayback(const Channel& channel, std::m
// Anything from here is live!
m_playbackIsVideo = false; // TODO: possible time jitter on UI as this will effect get stream times

//Always get the live EPG entry
EpgEntry* liveEpgEntry = GetLiveEPGEntry(channel);

if (!m_fromTimeshiftedEpgTagCall)
{
EpgEntry* liveEpgEntry = GetLiveEPGEntry(channel);
if (m_controlsLiveStream && liveEpgEntry && !m_settings->CatchupOnlyOnFinishedProgrammes())
{
// Live timeshifting support with EPG entry
Expand All @@ -55,6 +57,13 @@ void CatchupController::ProcessChannelForPlayback(const Channel& channel, std::m
m_programmeCatchupId.clear();
m_catchupStartTime = 0;
m_catchupEndTime = 0;

// Not from timeshifted EPG so safe to set the catchup ID here
if (!m_controlsLiveStream && liveEpgEntry)
{
m_programmeCatchupId = liveEpgEntry->GetCatchupId();
UpdateProgrammeFrom(*liveEpgEntry, channel.GetTvgShift());
}
}
}

Expand Down Expand Up @@ -132,6 +141,9 @@ void CatchupController::ProcessEPGTagForTimeshiftedPlayback(const kodi::addon::P

m_timeshiftBufferStartTime = 0;
m_timeshiftBufferOffset = 0;

if (m_settings->CatchupPlayEpgAsLive())
catchupProperties.insert({PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE, "true"});
}

m_fromTimeshiftedEpgTagCall = true;
Expand Down Expand Up @@ -393,9 +405,11 @@ std::string FormatDateTime(time_t timeStart, time_t duration, const std::string
return formattedUrl;
}

std::string FormatDateTimeNowOnly(const std::string &urlFormatString, int timezoneShiftSecs)
std::string FormatDateTimeNowOnly(const std::string &urlFormatString, int timezoneShiftSecs, int timeStart, int duration)
{
std::string formattedUrl = urlFormatString;

timeStart -= timezoneShiftSecs;
const time_t timeNow = std::time(0) - timezoneShiftSecs;
std::tm dateTimeNow = SafeLocaltime(timeNow);

Expand All @@ -406,6 +420,46 @@ std::string FormatDateTimeNowOnly(const std::string &urlFormatString, int timezo
FormatTime("now", &dateTimeNow, formattedUrl, true);
FormatTime("timestamp", &dateTimeNow, formattedUrl, true);

// If we have the start time for a programme also process those specifiers
// These can be useful for plugins that don't call ffmpegdirect and instead
// play EPG as live for catchup="vod"
if (timeStart > 0)
{
std::tm dateTimeStart = SafeLocaltime(timeStart);

const time_t timeEnd = timeStart + duration;
std::tm dateTimeEnd = SafeLocaltime(timeEnd);

FormatTime('Y', &dateTimeStart, formattedUrl);
FormatTime('m', &dateTimeStart, formattedUrl);
FormatTime('d', &dateTimeStart, formattedUrl);
FormatTime('H', &dateTimeStart, formattedUrl);
FormatTime('M', &dateTimeStart, formattedUrl);
FormatTime('S', &dateTimeStart, formattedUrl);
FormatUtc("{utc}", timeStart, formattedUrl);
FormatUtc("${start}", timeStart, formattedUrl);
FormatUtc("{utcend}", timeStart + duration, formattedUrl);
FormatUtc("${end}", timeStart + duration, formattedUrl);
FormatUtc("{lutc}", timeNow, formattedUrl);
FormatUtc("${now}", timeNow, formattedUrl);
FormatUtc("${timestamp}", timeNow, formattedUrl);
FormatUtc("${duration}", duration, formattedUrl);
FormatUtc("{duration}", duration, formattedUrl);
FormatUnits("duration", duration, formattedUrl);
FormatUtc("${offset}", timeNow - timeStart, formattedUrl);
FormatUnits("offset", timeNow - timeStart, formattedUrl);

FormatTime("utc", &dateTimeStart, formattedUrl, false);
FormatTime("start", &dateTimeStart, formattedUrl, true);

FormatTime("utcend", &dateTimeEnd, formattedUrl, false);
FormatTime("end", &dateTimeEnd, formattedUrl, true);

FormatTime("lutc", &dateTimeNow, formattedUrl, false);
FormatTime("now", &dateTimeNow, formattedUrl, true);
FormatTime("timestamp", &dateTimeNow, formattedUrl, true);
}

Logger::Log(LEVEL_DEBUG, "%s - \"%s\"", __FUNCTION__, WebUtils::RedactUrl(formattedUrl).c_str());

return formattedUrl;
Expand Down Expand Up @@ -440,7 +494,7 @@ std::string BuildEpgTagUrl(time_t startTime, time_t duration, const Channel& cha
if ((startTime > 0 && offset < (timeNow - 5)) || (channel.IgnoreCatchupDays() && !programmeCatchupId.empty()))
startTimeUrl = FormatDateTime(offset - timezoneShiftSecs, duration, channel.GetCatchupSource());
else
startTimeUrl = FormatDateTimeNowOnly(channel.GetStreamURL(), timezoneShiftSecs);
startTimeUrl = FormatDateTimeNowOnly(channel.GetStreamURL(), timezoneShiftSecs, startTime, duration);

static const std::regex CATCHUP_ID_REGEX("\\{catchup-id\\}");
if (!programmeCatchupId.empty())
Expand Down Expand Up @@ -490,7 +544,13 @@ std::string CatchupController::GetCatchupUrl(const Channel& channel) const
std::string CatchupController::ProcessStreamUrl(const Channel& channel) const
{
//We only process current time timestamps specifiers in this case
return FormatDateTimeNowOnly(channel.GetStreamURL(), m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs());
std::string processedUrl = FormatDateTimeNowOnly(channel.GetStreamURL(), m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs(), m_programmeStartTime, m_programmeEndTime - m_programmeStartTime);

static const std::regex CATCHUP_ID_REGEX("\\{catchup-id\\}");
if (!m_programmeCatchupId.empty())
processedUrl = std::regex_replace(processedUrl, CATCHUP_ID_REGEX, m_programmeCatchupId);

return processedUrl;
}

std::string CatchupController::GetStreamTestUrl(const Channel& channel, bool fromEpg) const
Expand Down
9 changes: 7 additions & 2 deletions src/iptvsimple/utilities/WebUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ bool WebUtils::IsHttpUrl(const std::string& url)
return StringUtils::StartsWith(url, HTTP_PREFIX) || StringUtils::StartsWith(url, HTTPS_PREFIX);
}

bool WebUtils::IsNfsUrl(const std::string& url)
{
return StringUtils::StartsWith(url, NFS_PREFIX);
}

std::string WebUtils::RedactUrl(const std::string& url)
{
std::string redactedUrl = url;
Expand Down Expand Up @@ -145,8 +150,8 @@ bool WebUtils::Check(const std::string& strURL, int connectionTimeoutSecs, bool
return false;
}

fileHandle.CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "connection-timeout",
std::to_string(connectionTimeoutSecs));
if (!IsNfsUrl(strURL))
fileHandle.CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "connection-timeout", std::to_string(connectionTimeoutSecs));

if (!fileHandle.CURLOpen(ADDON_READ_NO_CACHE))
{
Expand Down
2 changes: 2 additions & 0 deletions src/iptvsimple/utilities/WebUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace iptvsimple
{
static const std::string HTTP_PREFIX = "http://";
static const std::string HTTPS_PREFIX = "https://";
static const std::string NFS_PREFIX = "nfs://";
static const std::string UDP_MULTICAST_PREFIX = "udp://@";
static const std::string RTP_MULTICAST_PREFIX = "rtp://@";

Expand All @@ -26,6 +27,7 @@ namespace iptvsimple
static bool IsEncoded(const std::string& value);
static std::string ReadFileContentsStartOnly(const std::string& url, int* httpCode);
static bool IsHttpUrl(const std::string& url);
static bool IsNfsUrl(const std::string& url);
static std::string RedactUrl(const std::string& url);
static bool Check(const std::string& url, int connectionTimeoutSecs, bool isLocalPath = false);
};
Expand Down