1 /*
2  *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
3  *
4  *  SPDX-License-Identifier: GPL-2.0-or-later
5  *  See LICENSE.md for more information.
6  */
7 
8 #include "CatchupController.h"
9 
10 #include "Channels.h"
11 #include "Epg.h"
12 #include "Settings.h"
13 #include "data/Channel.h"
14 #include "utilities/Logger.h"
15 #include "utilities/TimeUtils.h"
16 #include "utilities/WebUtils.h"
17 
18 #include <iomanip>
19 #include <regex>
20 
21 #include <kodi/tools/StringUtils.h>
22 
23 using namespace kodi::tools;
24 using namespace iptvsimple;
25 using namespace iptvsimple::data;
26 using namespace iptvsimple::utilities;
27 
CatchupController(Epg & epg,std::mutex * mutex)28 CatchupController::CatchupController(Epg& epg, std::mutex* mutex)
29   : m_epg(epg), m_mutex(mutex) {}
30 
ProcessChannelForPlayback(const Channel & channel,std::map<std::string,std::string> & catchupProperties)31 void CatchupController::ProcessChannelForPlayback(const Channel& channel, std::map<std::string, std::string>& catchupProperties)
32 {
33   StreamType streamType = StreamTypeLookup(channel);
34 
35   // Anything from here is live!
36   m_playbackIsVideo = false; // TODO: possible time jitter on UI as this will effect get stream times
37 
38   if (!m_fromEpgTag || m_controlsLiveStream)
39   {
40     EpgEntry* liveEpgEntry = GetLiveEPGEntry(channel);
41     if (m_controlsLiveStream && liveEpgEntry && !Settings::GetInstance().CatchupOnlyOnFinishedProgrammes())
42     {
43       // Live timeshifting support with EPG entry
44       UpdateProgrammeFrom(*liveEpgEntry, channel.GetTvgShift());
45       m_catchupStartTime = liveEpgEntry->GetStartTime();
46       m_catchupEndTime = liveEpgEntry->GetEndTime();
47     }
48     else if (m_controlsLiveStream || !channel.IsCatchupSupported() ||
49              (!m_controlsLiveStream && channel.IsCatchupSupported()))
50     {
51       ClearProgramme();
52       m_programmeCatchupId.clear();
53       m_catchupStartTime = 0;
54       m_catchupEndTime = 0;
55     }
56     m_fromEpgTag = false;
57   }
58 
59   if (m_controlsLiveStream)
60   {
61     if (m_resetCatchupState)
62     {
63       m_resetCatchupState = false;
64       m_programmeCatchupId.clear();
65       if (channel.IsCatchupSupported())
66       {
67         m_timeshiftBufferOffset = channel.GetCatchupDaysInSeconds(); //offset from now to start of catchup window
68         m_timeshiftBufferStartTime = std::time(nullptr) - channel.GetCatchupDaysInSeconds(); // now - the window size
69       }
70       else
71       {
72         m_timeshiftBufferOffset = 0;
73         m_timeshiftBufferStartTime = 0;
74       }
75     }
76     else
77     {
78       EpgEntry* currentEpgEntry = GetEPGEntry(channel, m_timeshiftBufferStartTime + m_timeshiftBufferOffset);
79       if (currentEpgEntry)
80         UpdateProgrammeFrom(*currentEpgEntry, channel.GetTvgShift());
81     }
82 
83     m_catchupStartTime = m_timeshiftBufferStartTime;
84 
85     // TODO: Need a method of updating an inputstream if already running such as web call to stream etc.
86     // this will avoid inputstream restarts which are expensive, may be better placed in client.cpp
87     // this also mean knowing when a stream has stopped
88     SetCatchupInputStreamProperties(true, channel, catchupProperties, streamType);
89   }
90 }
91 
ProcessEPGTagForTimeshiftedPlayback(const kodi::addon::PVREPGTag & epgTag,const Channel & channel,std::map<std::string,std::string> & catchupProperties)92 void CatchupController::ProcessEPGTagForTimeshiftedPlayback(const kodi::addon::PVREPGTag& epgTag, const Channel& channel, std::map<std::string, std::string>& catchupProperties)
93 {
94   m_programmeCatchupId.clear();
95   EpgEntry* epgEntry = GetEPGEntry(channel, epgTag.GetStartTime());
96   if (epgEntry)
97     m_programmeCatchupId = epgEntry->GetCatchupId();
98 
99   StreamType streamType = StreamTypeLookup(channel, true);
100 
101   if (m_controlsLiveStream)
102   {
103     UpdateProgrammeFrom(epgTag, channel.GetTvgShift());
104     m_catchupStartTime = epgTag.GetStartTime();
105     m_catchupEndTime = epgTag.GetEndTime();
106 
107     time_t timeNow = time(0);
108     time_t programmeOffset = timeNow - m_catchupStartTime;
109     time_t timeshiftBufferDuration = std::max(programmeOffset, static_cast<time_t>(channel.GetCatchupDaysInSeconds()));
110     m_timeshiftBufferStartTime = timeNow - timeshiftBufferDuration;
111     m_catchupStartTime = m_timeshiftBufferStartTime;
112     m_catchupEndTime = timeNow;
113     m_timeshiftBufferOffset = timeshiftBufferDuration - programmeOffset;
114 
115     m_resetCatchupState = false;
116 
117     // TODO: Need a method of updating an inputstream if already running such as web call to stream etc.
118     // this will avoid inputstream restarts which are expensive, may be better placed in client.cpp
119     // this also mean knowing when a stream has stopped
120     SetCatchupInputStreamProperties(true, channel, catchupProperties, streamType);
121   }
122   else
123   {
124     UpdateProgrammeFrom(epgTag, channel.GetTvgShift());
125     m_catchupStartTime = epgTag.GetStartTime();
126     m_catchupEndTime = epgTag.GetEndTime();
127 
128     m_timeshiftBufferStartTime = 0;
129     m_timeshiftBufferOffset = 0;
130 
131     m_fromEpgTag = true;
132   }
133 }
134 
ProcessEPGTagForVideoPlayback(const kodi::addon::PVREPGTag & epgTag,const Channel & channel,std::map<std::string,std::string> & catchupProperties)135 void CatchupController::ProcessEPGTagForVideoPlayback(const kodi::addon::PVREPGTag& epgTag, const Channel& channel, std::map<std::string, std::string>& catchupProperties)
136 {
137   m_programmeCatchupId.clear();
138   EpgEntry* epgEntry = GetEPGEntry(channel, epgTag.GetStartTime());
139   if (epgEntry)
140     m_programmeCatchupId = epgEntry->GetCatchupId();
141 
142   StreamType streamType = StreamTypeLookup(channel, true);
143 
144   if (m_controlsLiveStream)
145   {
146     if (m_resetCatchupState)
147     {
148       UpdateProgrammeFrom(epgTag, channel.GetTvgShift());
149       m_catchupStartTime = epgTag.GetStartTime();
150       m_catchupEndTime = epgTag.GetEndTime();
151 
152       const time_t beginBuffer = Settings::GetInstance().GetCatchupWatchEpgBeginBufferSecs();
153       const time_t endBuffer = Settings::GetInstance().GetCatchupWatchEpgEndBufferSecs();
154       m_timeshiftBufferStartTime = m_catchupStartTime - beginBuffer;
155       m_catchupStartTime = m_timeshiftBufferStartTime;
156       m_catchupEndTime += endBuffer;
157       m_timeshiftBufferOffset = beginBuffer;
158 
159       m_resetCatchupState = false;
160     }
161 
162     // TODO: Need a method of updating an inputstream if already running such as web call to stream etc.
163     // this will avoid inputstream restarts which are expensive, may be better placed in client.cpp
164     // this also mean knowing when a stream has stopped
165     SetCatchupInputStreamProperties(false, channel, catchupProperties, streamType);
166   }
167   else
168   {
169     UpdateProgrammeFrom(epgTag, channel.GetTvgShift());
170     m_catchupStartTime = epgTag.GetStartTime();
171     m_catchupEndTime = epgTag.GetEndTime();
172 
173     m_timeshiftBufferStartTime = 0;
174     m_timeshiftBufferOffset = 0;
175     m_catchupStartTime = m_catchupStartTime - Settings::GetInstance().GetCatchupWatchEpgBeginBufferSecs();
176     m_catchupEndTime += Settings::GetInstance().GetCatchupWatchEpgEndBufferSecs();
177   }
178 
179   if (m_catchupStartTime > 0)
180     m_playbackIsVideo = true;
181 }
182 
SetCatchupInputStreamProperties(bool playbackAsLive,const Channel & channel,std::map<std::string,std::string> & catchupProperties,const StreamType & streamType)183 void CatchupController::SetCatchupInputStreamProperties(bool playbackAsLive, const Channel& channel, std::map<std::string, std::string>& catchupProperties, const StreamType& streamType)
184 {
185   catchupProperties.insert({PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE, playbackAsLive ? "true" : "false"});
186 
187   catchupProperties.insert({"inputstream.ffmpegdirect.is_realtime_stream",
188   	StringUtils::EqualsNoCase(channel.GetProperty(PVR_STREAM_PROPERTY_ISREALTIMESTREAM), "true") ? "true" : "false"});
189   catchupProperties.insert({"inputstream.ffmpegdirect.stream_mode", "catchup"});
190 
191   catchupProperties.insert({"inputstream.ffmpegdirect.default_url", channel.GetStreamURL()});
192   catchupProperties.insert({"inputstream.ffmpegdirect.playback_as_live", playbackAsLive ? "true" : "false"});
193   catchupProperties.insert({"inputstream.ffmpegdirect.catchup_url_format_string", GetCatchupUrlFormatString(channel)});
194   catchupProperties.insert({"inputstream.ffmpegdirect.catchup_buffer_start_time", std::to_string(m_catchupStartTime)});
195   catchupProperties.insert({"inputstream.ffmpegdirect.catchup_buffer_end_time", std::to_string(m_catchupEndTime)});
196   catchupProperties.insert({"inputstream.ffmpegdirect.catchup_buffer_offset", std::to_string(m_timeshiftBufferOffset)});
197   catchupProperties.insert({"inputstream.ffmpegdirect.timezone_shift", std::to_string(m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs())});
198   if (!m_programmeCatchupId.empty())
199     catchupProperties.insert({"inputstream.ffmpegdirect.programme_catchup_id", m_programmeCatchupId});
200   catchupProperties.insert({"inputstream.ffmpegdirect.catchup_terminates", channel.CatchupSourceTerminates() ? "true" : "false"});
201   catchupProperties.insert({"inputstream.ffmpegdirect.catchup_granularity", std::to_string(channel.GetCatchupGranularitySeconds())});
202 
203   // TODO: Should also send programme start and duration potentially
204   // When doing this don't forget to add Settings::GetInstance().GetCatchupWatchEpgBeginBufferSecs() + Settings::GetInstance().GetCatchupWatchEpgEndBufferSecs();
205   // if in video playback mode from epg, i.e. if (!Settings::GetInstance().CatchupPlayEpgAsLive() && m_playbackIsVideo)s
206 
207   Logger::Log(LEVEL_DEBUG, "default_url - %s", WebUtils::RedactUrl(channel.GetStreamURL()).c_str());
208   Logger::Log(LEVEL_DEBUG, "playback_as_live - %s", playbackAsLive ? "true" : "false");
209   Logger::Log(LEVEL_DEBUG, "catchup_url_format_string - %s", WebUtils::RedactUrl(GetCatchupUrlFormatString(channel)).c_str());
210   Logger::Log(LEVEL_DEBUG, "catchup_buffer_start_time - %s", std::to_string(m_catchupStartTime).c_str());
211   Logger::Log(LEVEL_DEBUG, "catchup_buffer_end_time - %s", std::to_string(m_catchupEndTime).c_str());
212   Logger::Log(LEVEL_DEBUG, "catchup_buffer_offset - %s", std::to_string(m_timeshiftBufferOffset).c_str());
213   Logger::Log(LEVEL_DEBUG, "timezone_shift - %s", std::to_string(m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs()).c_str());
214   Logger::Log(LEVEL_DEBUG, "programme_catchup_id - '%s'", m_programmeCatchupId.c_str());
215   Logger::Log(LEVEL_DEBUG, "catchup_terminates - %s", channel.CatchupSourceTerminates() ? "true" : "false");
216   Logger::Log(LEVEL_DEBUG, "catchup_granularity - %s", std::to_string(channel.GetCatchupGranularitySeconds()).c_str());
217   Logger::Log(LEVEL_DEBUG, "mimetype - '%s'", channel.HasMimeType() ? channel.GetProperty("mimetype").c_str() : StreamUtils::GetMimeType(streamType).c_str());
218 }
219 
StreamTypeLookup(const Channel & channel,bool fromEpg)220 StreamType CatchupController::StreamTypeLookup(const Channel& channel, bool fromEpg /* false */)
221 {
222   StreamType streamType = m_streamManager.StreamTypeLookup(channel, GetStreamTestUrl(channel, fromEpg), GetStreamKey(channel, fromEpg));
223 
224   m_controlsLiveStream = StreamUtils::GetEffectiveInputStreamName(streamType, channel) == "inputstream.ffmpegdirect" && channel.CatchupSupportsTimeshifting();
225 
226   return streamType;
227 }
228 
UpdateProgrammeFrom(const kodi::addon::PVREPGTag & epgTag,int tvgShift)229 void CatchupController::UpdateProgrammeFrom(const kodi::addon::PVREPGTag& epgTag, int tvgShift)
230 {
231   m_programmeStartTime = epgTag.GetStartTime();
232   m_programmeEndTime = epgTag.GetEndTime();
233   m_programmeTitle = epgTag.GetTitle();
234   m_programmeUniqueChannelId = epgTag.GetUniqueChannelId();
235   m_programmeChannelTvgShift = tvgShift;
236 }
237 
UpdateProgrammeFrom(const data::EpgEntry & epgEntry,int tvgShift)238 void CatchupController::UpdateProgrammeFrom(const data::EpgEntry& epgEntry, int tvgShift)
239 {
240   m_programmeStartTime = epgEntry.GetStartTime();
241   m_programmeEndTime = epgEntry.GetEndTime();
242   m_programmeTitle = epgEntry.GetTitle();
243   m_programmeUniqueChannelId = epgEntry.GetChannelId();
244   m_programmeChannelTvgShift = tvgShift;
245 }
246 
ClearProgramme()247 void CatchupController::ClearProgramme()
248 {
249   m_programmeStartTime = 0;
250   m_programmeEndTime = 0;
251   m_programmeTitle.clear();
252   m_programmeUniqueChannelId = 0;
253   m_programmeChannelTvgShift = 0;
254 }
255 
256 namespace
257 {
FormatUnits(const std::string & name,time_t tTime,std::string & urlFormatString)258 void FormatUnits(const std::string& name, time_t tTime, std::string &urlFormatString)
259 {
260   const std::regex timeSecondsRegex(".*(\\{" + name + ":(\\d+)\\}).*");
261   std::cmatch mr;
262   if (std::regex_match(urlFormatString.c_str(), mr, timeSecondsRegex) && mr.length() >= 3)
263   {
264     std::string timeSecondsExp = mr[1].first;
265     std::string second = mr[1].second;
266     if (second.length() > 0)
267       timeSecondsExp = timeSecondsExp.erase(timeSecondsExp.find(second));
268     std::string dividerStr = mr[2].first;
269     second = mr[2].second;
270     if (second.length() > 0)
271       dividerStr = dividerStr.erase(dividerStr.find(second));
272 
273     const time_t divider = stoi(dividerStr);
274     if (divider != 0)
275     {
276       time_t units = tTime / divider;
277       if (units < 0)
278         units = 0;
279       urlFormatString.replace(urlFormatString.find(timeSecondsExp), timeSecondsExp.length(), std::to_string(units));
280     }
281   }
282 }
283 
FormatTime(const char ch,const struct tm * pTime,std::string & urlFormatString)284 void FormatTime(const char ch, const struct tm *pTime, std::string &urlFormatString)
285 {
286   std::string str = {'{', ch, '}'};
287   size_t pos = urlFormatString.find(str);
288   while (pos != std::string::npos)
289   {
290     std::ostringstream os;
291     os << std::put_time(pTime, StringUtils::Format("%%%c", ch).c_str());
292     std::string timeString = os.str();
293 
294     if (timeString.size() > 0)
295       urlFormatString.replace(pos, str.size(), timeString);
296 
297     pos = urlFormatString.find(str);
298   }
299 }
300 
FormatTime(const std::string name,const struct tm * pTime,std::string & urlFormatString,bool hasVarPrefix)301 void FormatTime(const std::string name, const struct tm *pTime, std::string &urlFormatString, bool hasVarPrefix)
302 {
303   std::string qualifier = hasVarPrefix ? "$" : "";
304   qualifier += "{" + name + ":";
305   size_t found = urlFormatString.find(qualifier);
306   if (found != std::string::npos)
307   {
308     size_t foundStart = found + qualifier.size();
309     size_t foundEnd = urlFormatString.find("}", foundStart + 1);
310     if (foundEnd != std::string::npos)
311     {
312       std::string formatString = urlFormatString.substr(foundStart, foundEnd - foundStart);
313       const std::regex timeSpecifiers("([YmdHMS])");
314       formatString = std::regex_replace(formatString, timeSpecifiers, R"(%$&)");
315 
316       std::ostringstream os;
317       os << std::put_time(pTime, formatString.c_str());
318       std::string timeString = os.str();
319 
320       if (timeString.size() > 0)
321         urlFormatString.replace(found, foundEnd - found + 1, timeString);
322     }
323   }
324 }
325 
FormatUtc(const std::string & str,time_t tTime,std::string & urlFormatString)326 void FormatUtc(const std::string& str, time_t tTime, std::string &urlFormatString)
327 {
328   auto pos = urlFormatString.find(str);
329   if (pos != std::string::npos)
330   {
331     std::string utcTimeAsString = StringUtils::Format("%lu", tTime);
332     urlFormatString.replace(pos, str.size(), utcTimeAsString);
333   }
334 }
335 
FormatDateTime(time_t timeStart,time_t duration,const std::string & urlFormatString)336 std::string FormatDateTime(time_t timeStart, time_t duration, const std::string &urlFormatString)
337 {
338   std::string formattedUrl = urlFormatString;
339 
340   const time_t timeEnd = timeStart + duration;
341   const time_t timeNow = std::time(0);
342 
343   std::tm dateTimeStart = SafeLocaltime(timeStart);
344   std::tm dateTimeEnd = SafeLocaltime(timeEnd);
345   std::tm dateTimeNow = SafeLocaltime(timeNow);
346 
347   FormatTime('Y', &dateTimeStart, formattedUrl);
348   FormatTime('m', &dateTimeStart, formattedUrl);
349   FormatTime('d', &dateTimeStart, formattedUrl);
350   FormatTime('H', &dateTimeStart, formattedUrl);
351   FormatTime('M', &dateTimeStart, formattedUrl);
352   FormatTime('S', &dateTimeStart, formattedUrl);
353   FormatUtc("{utc}", timeStart, formattedUrl);
354   FormatUtc("${start}", timeStart, formattedUrl);
355   FormatUtc("{utcend}", timeStart + duration, formattedUrl);
356   FormatUtc("${end}", timeStart + duration, formattedUrl);
357   FormatUtc("{lutc}", timeNow, formattedUrl);
358   FormatUtc("${now}", timeNow, formattedUrl);
359   FormatUtc("${timestamp}", timeNow, formattedUrl);
360   FormatUtc("{duration}", duration, formattedUrl);
361   FormatUnits("duration", duration, formattedUrl);
362   FormatUtc("${offset}", timeNow - timeStart, formattedUrl);
363   FormatUnits("offset", timeNow - timeStart, formattedUrl);
364 
365   FormatTime("utc", &dateTimeStart, formattedUrl, false);
366   FormatTime("start", &dateTimeStart, formattedUrl, true);
367 
368   FormatTime("utcend", &dateTimeEnd, formattedUrl, false);
369   FormatTime("end", &dateTimeEnd, formattedUrl, true);
370 
371   FormatTime("lutc", &dateTimeNow, formattedUrl, false);
372   FormatTime("now", &dateTimeNow, formattedUrl, true);
373   FormatTime("timestamp", &dateTimeNow, formattedUrl, true);
374 
375   Logger::Log(LEVEL_DEBUG, "%s - \"%s\"", __FUNCTION__, WebUtils::RedactUrl(formattedUrl).c_str());
376 
377   return formattedUrl;
378 }
379 
FormatDateTimeNowOnly(const std::string & urlFormatString,int timezoneShiftSecs)380 std::string FormatDateTimeNowOnly(const std::string &urlFormatString, int timezoneShiftSecs)
381 {
382   std::string formattedUrl = urlFormatString;
383   const time_t timeNow = std::time(0) - timezoneShiftSecs;
384   std::tm dateTimeNow = SafeLocaltime(timeNow);
385 
386   FormatUtc("{lutc}", timeNow, formattedUrl);
387   FormatUtc("${now}", timeNow, formattedUrl);
388   FormatUtc("${timestamp}", timeNow, formattedUrl);
389   FormatTime("lutc", &dateTimeNow, formattedUrl, false);
390   FormatTime("now", &dateTimeNow, formattedUrl, true);
391   FormatTime("timestamp", &dateTimeNow, formattedUrl, true);
392 
393   Logger::Log(LEVEL_DEBUG, "%s - \"%s\"", __FUNCTION__, WebUtils::RedactUrl(formattedUrl).c_str());
394 
395   return formattedUrl;
396 }
397 
AppendQueryStringAndPreserveOptions(const std::string & url,const std::string & postfixQueryString)398 std::string AppendQueryStringAndPreserveOptions(const std::string &url, const std::string &postfixQueryString)
399 {
400   std::string urlFormatString;
401   if (!postfixQueryString.empty())
402   {
403     // preserve any kodi protocol options after "|"
404     size_t found = url.find_first_of('|');
405     if (found != std::string::npos)
406       urlFormatString = url.substr(0, found) + postfixQueryString + url.substr(found, url.length());
407     else
408       urlFormatString = url + postfixQueryString;
409   }
410   else
411   {
412     urlFormatString = url;
413   }
414 
415   return urlFormatString;
416 }
417 
BuildEpgTagUrl(time_t startTime,time_t duration,const Channel & channel,long long timeOffset,const std::string & programmeCatchupId,int timezoneShiftSecs)418 std::string BuildEpgTagUrl(time_t startTime, time_t duration, const Channel& channel, long long timeOffset, const std::string& programmeCatchupId, int timezoneShiftSecs)
419 {
420   std::string startTimeUrl;
421   time_t timeNow = std::time(nullptr);
422   time_t offset = startTime + timeOffset;
423 
424   if ((startTime > 0 && offset < (timeNow - 5)) || (channel.IgnoreCatchupDays() && !programmeCatchupId.empty()))
425     startTimeUrl = FormatDateTime(offset - timezoneShiftSecs, duration, channel.GetCatchupSource());
426   else
427     startTimeUrl = FormatDateTimeNowOnly(channel.GetStreamURL(), timezoneShiftSecs);
428 
429   static const std::regex CATCHUP_ID_REGEX("\\{catchup-id\\}");
430   if (!programmeCatchupId.empty())
431     startTimeUrl = std::regex_replace(startTimeUrl, CATCHUP_ID_REGEX, programmeCatchupId);
432 
433   Logger::Log(LEVEL_DEBUG, "%s - %s", __FUNCTION__, WebUtils::RedactUrl(startTimeUrl).c_str());
434 
435   return startTimeUrl;
436 }
437 
438 } // unnamed namespace
439 
GetCatchupUrlFormatString(const Channel & channel) const440 std::string CatchupController::GetCatchupUrlFormatString(const Channel& channel) const
441 {
442   if (m_catchupStartTime > 0)
443     return channel.GetCatchupSource();
444 
445   return "";
446 }
447 
GetCatchupUrl(const Channel & channel) const448 std::string CatchupController::GetCatchupUrl(const Channel& channel) const
449 {
450   if (m_catchupStartTime > 0)
451   {
452     time_t duration = 60 * 60; // default one hour
453 
454     // use the programme duration if it's valid
455     if (m_programmeStartTime > 0 && m_programmeStartTime < m_programmeEndTime)
456     {
457       duration = static_cast<time_t>(m_programmeEndTime - m_programmeStartTime);
458 
459       if (!Settings::GetInstance().CatchupPlayEpgAsLive() && m_playbackIsVideo)
460         duration += Settings::GetInstance().GetCatchupWatchEpgBeginBufferSecs() + Settings::GetInstance().GetCatchupWatchEpgEndBufferSecs();
461 
462       time_t timeNow = time(0);
463       // cap duration to timeNow
464       if (m_programmeStartTime + duration > timeNow)
465         duration = timeNow - m_programmeStartTime;
466     }
467 
468     return BuildEpgTagUrl(m_catchupStartTime, duration, channel, m_timeshiftBufferOffset, m_programmeCatchupId, m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs());
469   }
470 
471   return "";
472 }
473 
ProcessStreamUrl(const Channel & channel) const474 std::string CatchupController::ProcessStreamUrl(const Channel& channel) const
475 {
476   //We only process current time timestamps specifiers in this case
477   return FormatDateTimeNowOnly(channel.GetStreamURL(), m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs());
478 }
479 
GetStreamTestUrl(const Channel & channel,bool fromEpg) const480 std::string CatchupController::GetStreamTestUrl(const Channel& channel, bool fromEpg) const
481 {
482  if (m_catchupStartTime > 0 || fromEpg)
483     // Test URL from 2 hours ago for 1 hour duration.
484     return BuildEpgTagUrl(std::time(nullptr) - (2 * 60 * 60), 60 * 60, channel, 0, m_programmeCatchupId, m_epg.GetEPGTimezoneShiftSecs(channel) + channel.GetCatchupCorrectionSecs());
485   else
486     return ProcessStreamUrl(channel);
487 }
488 
GetStreamKey(const Channel & channel,bool fromEpg) const489 std::string CatchupController::GetStreamKey(const Channel& channel, bool fromEpg) const
490 {
491   // The streamKey is simply the channelId + StreamUrl or the catchup source
492   // Either can be used to uniquely identify the StreamType/MimeType pairing
493   if ((m_catchupStartTime > 0 || fromEpg) && m_timeshiftBufferOffset < (std::time(nullptr) - 5))
494     std::to_string(channel.GetUniqueId()) + "-" + channel.GetCatchupSource();
495 
496   return std::to_string(channel.GetUniqueId()) + "-" + channel.GetStreamURL();
497 }
498 
GetLiveEPGEntry(const Channel & myChannel)499 EpgEntry* CatchupController::GetLiveEPGEntry(const Channel& myChannel)
500 {
501   std::lock_guard<std::mutex> lock(*m_mutex);
502 
503   return m_epg.GetLiveEPGEntry(myChannel);
504 }
505 
GetEPGEntry(const Channel & myChannel,time_t lookupTime)506 EpgEntry* CatchupController::GetEPGEntry(const Channel& myChannel, time_t lookupTime)
507 {
508   std::lock_guard<std::mutex> lock(*m_mutex);
509 
510   return m_epg.GetEPGEntry(myChannel, lookupTime);
511 }
512