diff --git a/pvr.iptvsimple/addon.xml.in b/pvr.iptvsimple/addon.xml.in index 57c9df9d..56d53e1b 100644 --- a/pvr.iptvsimple/addon.xml.in +++ b/pvr.iptvsimple/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.iptvsimple/changelog.txt b/pvr.iptvsimple/changelog.txt index 630fe87e..a1b65c90 100644 --- a/pvr.iptvsimple/changelog.txt +++ b/pvr.iptvsimple/changelog.txt @@ -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 diff --git a/src/IptvSimple.cpp b/src/IptvSimple.cpp index 0e8983e4..28b69707 100644 --- a/src/IptvSimple.cpp +++ b/src/IptvSimple.cpp @@ -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 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); } diff --git a/src/iptvsimple/CatchupController.cpp b/src/iptvsimple/CatchupController.cpp index 62b0484d..2c04a279 100644 --- a/src/iptvsimple/CatchupController.cpp +++ b/src/iptvsimple/CatchupController.cpp @@ -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 @@ -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()); + } } } @@ -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; @@ -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); @@ -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; @@ -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()) @@ -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 diff --git a/src/iptvsimple/utilities/WebUtils.cpp b/src/iptvsimple/utilities/WebUtils.cpp index bef63527..165e139c 100644 --- a/src/iptvsimple/utilities/WebUtils.cpp +++ b/src/iptvsimple/utilities/WebUtils.cpp @@ -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; @@ -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)) { diff --git a/src/iptvsimple/utilities/WebUtils.h b/src/iptvsimple/utilities/WebUtils.h index 1b358f2f..22ec49a5 100644 --- a/src/iptvsimple/utilities/WebUtils.h +++ b/src/iptvsimple/utilities/WebUtils.h @@ -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://@"; @@ -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); };