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