mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
Improved handling EPG data from the EIT tables
This commit is contained in:
parent
f672fe90c1
commit
b80c22e9c4
16
HISTORY
16
HISTORY
@ -9578,7 +9578,7 @@ Video Disk Recorder Revision History
|
|||||||
given (reported by Manuel Reimer).
|
given (reported by Manuel Reimer).
|
||||||
- Fixed handling $(PKG_CONFIG) in newplugin (thanks to Winfried Köhler).
|
- Fixed handling $(PKG_CONFIG) in newplugin (thanks to Winfried Köhler).
|
||||||
|
|
||||||
2021-03-17:
|
2021-04-04:
|
||||||
|
|
||||||
- Fixed strreplace() to handle NULL strings (reported by Jürgen Schneider).
|
- Fixed strreplace() to handle NULL strings (reported by Jürgen Schneider).
|
||||||
- Somewhere down the road the 'x' bit of Doxyfile.filter got lost, so the
|
- Somewhere down the road the 'x' bit of Doxyfile.filter got lost, so the
|
||||||
@ -9617,3 +9617,17 @@ Video Disk Recorder Revision History
|
|||||||
- Decreased the scrambling timeout for CAMs known to decrypt a certain channel, so
|
- Decreased the scrambling timeout for CAMs known to decrypt a certain channel, so
|
||||||
that it won't collide with MAXBROKENTIMEOUT in recorder.c.
|
that it won't collide with MAXBROKENTIMEOUT in recorder.c.
|
||||||
- Fixed scaling subtitles with anti-aliasing (thanks to Peter Bieringer).
|
- Fixed scaling subtitles with anti-aliasing (thanks to Peter Bieringer).
|
||||||
|
- Improved handling EPG data from the EIT tables:
|
||||||
|
+ Table 0x4F is now completely ignored.
|
||||||
|
+ Once a schedule has seen events from 0x5X, tables 0x6X are ignored for that
|
||||||
|
schedule.
|
||||||
|
+ When looking up an event in its schedule, the start time is used for tables 0x6X, and the
|
||||||
|
event id for tables 0x4E and 0x5X.
|
||||||
|
+ When hashing events by event id or start time, existing older entries in the hash
|
||||||
|
tables are now deleted before entering the new ones.
|
||||||
|
+ The function cSchedule::GetEvent() is now deprecated and may be removed in a future
|
||||||
|
version. Use GetEventById() and GetEventByTime() instead.
|
||||||
|
+ On channels that use proper event ids a change of the start time no longer
|
||||||
|
causes a new event to be created, but rather modifies the existing one. This
|
||||||
|
avoids possible interruptions in VPS recordings in case the event's start time
|
||||||
|
is changed while the recording is already going on.
|
||||||
|
98
eit.c
98
eit.c
@ -8,9 +8,26 @@
|
|||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
|
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
|
||||||
*
|
*
|
||||||
* $Id: eit.c 5.1 2021/03/16 15:10:54 kls Exp $
|
* $Id: eit.c 5.2 2021/04/04 11:06:30 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// The various ways in which broadcasters handle (or screw up) their EPG:
|
||||||
|
// - Some use the same version for all tables, and use unique event ids, which are the same in
|
||||||
|
// both the 0x5X and the 0x6X tables. And once an event has an id, it keeps it until it is
|
||||||
|
// no longer in the tables. Those are the good guys!
|
||||||
|
// - Some use separate versions for each table (0x50, 0x51, ...).
|
||||||
|
// - Some broadcast tables 0x5X and 0x6X, but use different event ids for the same event in both
|
||||||
|
// sets of tables, and sometimes even use different titles, short texts or descriptions.
|
||||||
|
// - Some broadcast the full EPG only on one transponder (tables 0x6X), and on the actual transponder
|
||||||
|
// they provide only the present/following information.
|
||||||
|
// - Some have overlapping events, especially when they mess up daylight saving time.
|
||||||
|
// - Some use all new event ids every time they update their tables.
|
||||||
|
// So, to bring order to chaos, VDR does as follows:
|
||||||
|
// - Completely ignore table 0x4F.
|
||||||
|
// - Once a schedule has seen events from 0x5X, tables 0x6X are ignored for that schedule.
|
||||||
|
// - When looking up an event in its schedule, the start time is used for tables 0x6X, and the
|
||||||
|
// event id for tables 0x4E and 0x5X.
|
||||||
|
|
||||||
#include "eit.h"
|
#include "eit.h"
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include "epg.h"
|
#include "epg.h"
|
||||||
@ -24,6 +41,13 @@
|
|||||||
|
|
||||||
// --- cEitTables ------------------------------------------------------------
|
// --- cEitTables ------------------------------------------------------------
|
||||||
|
|
||||||
|
cEitTables::cEitTables(void)
|
||||||
|
{
|
||||||
|
complete = false;
|
||||||
|
tableStart = 0;
|
||||||
|
tableEnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool cEitTables::Check(uchar TableId, uchar Version, int SectionNumber)
|
bool cEitTables::Check(uchar TableId, uchar Version, int SectionNumber)
|
||||||
{
|
{
|
||||||
int ti = Index(TableId);
|
int ti = Index(TableId);
|
||||||
@ -32,35 +56,38 @@ bool cEitTables::Check(uchar TableId, uchar Version, int SectionNumber)
|
|||||||
|
|
||||||
bool cEitTables::Processed(uchar TableId, uchar LastTableId, int SectionNumber, int LastSectionNumber, int SegmentLastSectionNumber)
|
bool cEitTables::Processed(uchar TableId, uchar LastTableId, int SectionNumber, int LastSectionNumber, int SegmentLastSectionNumber)
|
||||||
{
|
{
|
||||||
|
bool Result = false;
|
||||||
int ti = Index(TableId);
|
int ti = Index(TableId);
|
||||||
int LastIndex = Index(LastTableId);
|
int LastIndex = Index(LastTableId);
|
||||||
|
complete = false;
|
||||||
if (sectionSyncer[ti].Processed(SectionNumber, LastSectionNumber, SegmentLastSectionNumber)) {
|
if (sectionSyncer[ti].Processed(SectionNumber, LastSectionNumber, SegmentLastSectionNumber)) {
|
||||||
|
Result = true; // the table with TableId is complete
|
||||||
for (int i = 0; i <= LastIndex; i++) {
|
for (int i = 0; i <= LastIndex; i++) {
|
||||||
if (!sectionSyncer[i].Complete())
|
if (!sectionSyncer[i].Complete())
|
||||||
return false;
|
return Result;
|
||||||
}
|
}
|
||||||
return true; // all tables have been processed
|
complete = true; // all tables have been processed
|
||||||
}
|
}
|
||||||
return false;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cEIT ------------------------------------------------------------------
|
// --- cEIT ------------------------------------------------------------------
|
||||||
|
|
||||||
class cEIT : public SI::EIT {
|
class cEIT : public SI::EIT {
|
||||||
public:
|
public:
|
||||||
cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data);
|
cEIT(cEitTablesHash &EitTablesHash, int Source, u_char Tid, const u_char *Data);
|
||||||
};
|
};
|
||||||
|
|
||||||
cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data)
|
cEIT::cEIT(cEitTablesHash &EitTablesHash, int Source, u_char Tid, const u_char *Data)
|
||||||
:SI::EIT(Data, false)
|
:SI::EIT(Data, false)
|
||||||
{
|
{
|
||||||
if (!CheckCRCAndParse())
|
if (!CheckCRCAndParse())
|
||||||
return;
|
return;
|
||||||
int HashId = getServiceId();
|
int HashId = getServiceId();
|
||||||
cEitTables *EitTables = SectionSyncerHash.Get(HashId);
|
cEitTables *EitTables = EitTablesHash.Get(HashId);
|
||||||
if (!EitTables) {
|
if (!EitTables) {
|
||||||
EitTables = new cEitTables;
|
EitTables = new cEitTables;
|
||||||
SectionSyncerHash.Add(EitTables, HashId);
|
EitTablesHash.Add(EitTables, HashId);
|
||||||
}
|
}
|
||||||
bool Process = EitTables->Check(Tid, getVersionNumber(), getSectionNumber());
|
bool Process = EitTables->Check(Tid, getVersionNumber(), getSectionNumber());
|
||||||
if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
|
if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
|
||||||
@ -98,10 +125,16 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
|
|||||||
bool handledExternally = EpgHandlers.HandledExternally(Channel);
|
bool handledExternally = EpgHandlers.HandledExternally(Channel);
|
||||||
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
|
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
|
||||||
|
|
||||||
|
if (pSchedule->OnActualTp(Tid) && (Tid & 0xF0) == 0x60) {
|
||||||
|
SchedulesStateKey.Remove(false);
|
||||||
|
ChannelsStateKey.Remove(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool Empty = true;
|
bool Empty = true;
|
||||||
bool Modified = false;
|
bool Modified = false;
|
||||||
time_t LingerLimit = Now - Setup.EPGLinger * 60;
|
time_t LingerLimit = Now - Setup.EPGLinger * 60;
|
||||||
time_t SegmentStart = 0;
|
time_t SegmentStart = 0; // these are actually "section" start/end times
|
||||||
time_t SegmentEnd = 0;
|
time_t SegmentEnd = 0;
|
||||||
struct tm t = { 0 };
|
struct tm t = { 0 };
|
||||||
localtime_r(&Now, &t); // this initializes the time zone in 't'
|
localtime_r(&Now, &t); // this initializes the time zone in 't'
|
||||||
@ -122,9 +155,19 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
|
|||||||
if (!SegmentStart)
|
if (!SegmentStart)
|
||||||
SegmentStart = StartTime;
|
SegmentStart = StartTime;
|
||||||
SegmentEnd = StartTime + Duration;
|
SegmentEnd = StartTime + Duration;
|
||||||
|
if (Tid == 0x4E) {
|
||||||
|
if (getSectionNumber() == 0)
|
||||||
|
EitTables->SetTableStart(SegmentStart);
|
||||||
|
else
|
||||||
|
EitTables->SetTableEnd(SegmentEnd);
|
||||||
|
}
|
||||||
cEvent *newEvent = NULL;
|
cEvent *newEvent = NULL;
|
||||||
cEvent *rEvent = NULL;
|
cEvent *rEvent = NULL;
|
||||||
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
|
cEvent *pEvent = NULL;
|
||||||
|
if (Tid == 0x4E || (Tid & 0xF0) == 0x50)
|
||||||
|
pEvent = const_cast<cEvent *>(pSchedule->GetEventById(SiEitEvent.getEventId()));
|
||||||
|
else
|
||||||
|
pEvent = const_cast<cEvent *>(pSchedule->GetEventByTime(StartTime));
|
||||||
if (!pEvent || handledExternally) {
|
if (!pEvent || handledExternally) {
|
||||||
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
|
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
|
||||||
continue;
|
continue;
|
||||||
@ -140,10 +183,13 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
|
|||||||
// We have found an existing event, either through its event ID or its start time.
|
// We have found an existing event, either through its event ID or its start time.
|
||||||
pEvent->SetSeen();
|
pEvent->SetSeen();
|
||||||
uchar TableID = max(pEvent->TableID(), uchar(0x4E)); // for backwards compatibility, table ids less than 0x4E are treated as if they were "present"
|
uchar TableID = max(pEvent->TableID(), uchar(0x4E)); // for backwards compatibility, table ids less than 0x4E are treated as if they were "present"
|
||||||
// If the new event has a higher table ID, let's skip it.
|
// We never overwrite present/following with events from other tables:
|
||||||
// The lower the table ID, the more "current" the information.
|
if (TableID == 0x4E && Tid != 0x4E)
|
||||||
if (Tid > TableID)
|
|
||||||
continue;
|
continue;
|
||||||
|
if (pEvent->HasTimer()) {
|
||||||
|
if (pEvent->StartTime() != StartTime || pEvent->Duration() != Duration)
|
||||||
|
dsyslog("channel %d (%s) event %s times changed to %s-%s", Channel->Number(), Channel->Name(), *pEvent->ToDescr(), *TimeString(StartTime), *TimeString(StartTime + Duration));
|
||||||
|
}
|
||||||
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
|
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
|
||||||
EpgHandlers.SetStartTime(pEvent, StartTime);
|
EpgHandlers.SetStartTime(pEvent, StartTime);
|
||||||
EpgHandlers.SetDuration(pEvent, Duration);
|
EpgHandlers.SetDuration(pEvent, Duration);
|
||||||
@ -275,7 +321,7 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
|
|||||||
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
|
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
|
||||||
if (!rSchedule)
|
if (!rSchedule)
|
||||||
break;
|
break;
|
||||||
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
|
rEvent = (cEvent *)rSchedule->GetEventById(tsed->getReferenceEventId());
|
||||||
if (!rEvent)
|
if (!rEvent)
|
||||||
break;
|
break;
|
||||||
EpgHandlers.SetTitle(pEvent, rEvent->Title());
|
EpgHandlers.SetTitle(pEvent, rEvent->Title());
|
||||||
@ -375,13 +421,17 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
|
|||||||
pSchedule->ClrRunningStatus(Channel);
|
pSchedule->ClrRunningStatus(Channel);
|
||||||
pSchedule->SetPresentSeen();
|
pSchedule->SetPresentSeen();
|
||||||
}
|
}
|
||||||
if (Modified) {
|
if (Process) {
|
||||||
EpgHandlers.SortSchedule(pSchedule);
|
bool Complete = EitTables->Processed(Tid, getLastTableId(), getSectionNumber(), getLastSectionNumber(), getSegmentLastSectionNumber());
|
||||||
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
|
if (Modified && (Tid >= 0x50 || Complete)) { // we process the 0x5X tables segment by segment, but 0x4E only if we have received ALL its segments (0 and 1, i.e. "present" and "following")
|
||||||
pSchedule->SetModified();
|
if (Tid == 0x4E && getLastSectionNumber() == 1) {
|
||||||
|
SegmentStart = EitTables->TableStart();
|
||||||
|
SegmentEnd = EitTables->TableEnd();
|
||||||
|
}
|
||||||
|
EpgHandlers.SortSchedule(pSchedule);
|
||||||
|
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Process)
|
|
||||||
EitTables->Processed(Tid, getLastTableId(), getSectionNumber(), getLastSectionNumber(), getSegmentLastSectionNumber());
|
|
||||||
SchedulesStateKey.Remove(Modified);
|
SchedulesStateKey.Remove(Modified);
|
||||||
ChannelsStateKey.Remove(ChannelsModified);
|
ChannelsStateKey.Remove(ChannelsModified);
|
||||||
EpgHandlers.EndSegmentTransfer(Modified);
|
EpgHandlers.EndSegmentTransfer(Modified);
|
||||||
@ -443,7 +493,7 @@ time_t cEitFilter::disableUntil = 0;
|
|||||||
|
|
||||||
cEitFilter::cEitFilter(void)
|
cEitFilter::cEitFilter(void)
|
||||||
{
|
{
|
||||||
Set(0x12, 0x40, 0xC0); // event info now&next actual/other TS (0x4E/0x4F), future actual/other TS (0x5X/0x6X)
|
Set(0x12, 0x40, 0xC0); // event info present&following actual/other TS (0x4E/0x4F), future actual/other TS (0x5X/0x6X)
|
||||||
Set(0x14, 0x70); // TDT
|
Set(0x14, 0x70); // TDT
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +501,7 @@ void cEitFilter::SetStatus(bool On)
|
|||||||
{
|
{
|
||||||
cMutexLock MutexLock(&mutex);
|
cMutexLock MutexLock(&mutex);
|
||||||
cFilter::SetStatus(On);
|
cFilter::SetStatus(On);
|
||||||
sectionSyncerHash.Clear();
|
eitTablesHash.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cEitFilter::SetDisableUntil(time_t Time)
|
void cEitFilter::SetDisableUntil(time_t Time)
|
||||||
@ -470,8 +520,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
switch (Pid) {
|
switch (Pid) {
|
||||||
case 0x12: {
|
case 0x12: {
|
||||||
if (Tid >= 0x4E && Tid <= 0x6F)
|
if (Tid == 0x4E || Tid >= 0x50 && Tid <= 0x6F) // we ignore 0x4F, which only causes trouble
|
||||||
cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
|
cEIT EIT(eitTablesHash, Source(), Tid, Data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x14: {
|
case 0x14: {
|
||||||
|
20
eit.h
20
eit.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: eit.h 5.1 2021/03/16 15:10:54 kls Exp $
|
* $Id: eit.h 5.2 2021/04/04 11:06:30 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __EIT_H
|
#ifndef __EIT_H
|
||||||
@ -17,30 +17,38 @@
|
|||||||
|
|
||||||
// Event information (or EPG) is broadcast in tables 0x4E and 0x4F for "present/following" events on
|
// Event information (or EPG) is broadcast in tables 0x4E and 0x4F for "present/following" events on
|
||||||
// "this transponder" (0x4E) and "other transponders" (0x4F), as well as 0x50-0x5F ("all events on this
|
// "this transponder" (0x4E) and "other transponders" (0x4F), as well as 0x50-0x5F ("all events on this
|
||||||
// transponder) and 0x60-0x6F ("all events on other transponders). Since it's either "this" or "other",
|
// transponder") and 0x60-0x6F ("all events on other transponders"). Since it's either "this" or "other",
|
||||||
// we only use one section syncer for 0x4E/0x4F and 16 syncers for either 0x5X or 0x6X.
|
// we only use one section syncer for 0x4E/0x4F and 16 syncers for either 0x5X or 0x6X.
|
||||||
|
|
||||||
class cEitTables : public cListObject {
|
class cEitTables : public cListObject {
|
||||||
private:
|
private:
|
||||||
cSectionSyncerRandom sectionSyncer[NUM_EIT_TABLES]; // for tables 0x4E/0x4F and 0x50-0x5F/0x60-0x6F
|
cSectionSyncerRandom sectionSyncer[NUM_EIT_TABLES]; // for tables 0x4E/0x4F and 0x50-0x5F/0x60-0x6F
|
||||||
|
time_t tableStart; // only used for table 0x4E
|
||||||
|
time_t tableEnd;
|
||||||
bool complete;
|
bool complete;
|
||||||
int Index(uchar TableId) { return (TableId < 0x50) ? 0 : (TableId & 0x0F) + 1; }
|
int Index(uchar TableId) { return (TableId < 0x50) ? 0 : (TableId & 0x0F) + 1; }
|
||||||
public:
|
public:
|
||||||
cEitTables(void) { complete = false; }
|
cEitTables(void);
|
||||||
|
void SetTableStart(time_t t) { tableStart = t; }
|
||||||
|
void SetTableEnd(time_t t) { tableEnd = t; }
|
||||||
|
time_t TableStart(void) { return tableStart; }
|
||||||
|
time_t TableEnd(void) { return tableEnd; }
|
||||||
bool Check(uchar TableId, uchar Version, int SectionNumber);
|
bool Check(uchar TableId, uchar Version, int SectionNumber);
|
||||||
bool Processed(uchar TableId, uchar LastTableId, int SectionNumber, int LastSectionNumber, int SegmentLastSectionNumber = -1);
|
bool Processed(uchar TableId, uchar LastTableId, int SectionNumber, int LastSectionNumber, int SegmentLastSectionNumber = -1);
|
||||||
|
///< Returns true if all sections of the table with the given TableId have been processed.
|
||||||
bool Complete(void) { return complete; }
|
bool Complete(void) { return complete; }
|
||||||
|
///< Returns true if all sections of all tables have been processed.
|
||||||
};
|
};
|
||||||
|
|
||||||
class cSectionSyncerHash : public cHash<cEitTables> {
|
class cEitTablesHash : public cHash<cEitTables> {
|
||||||
public:
|
public:
|
||||||
cSectionSyncerHash(void) : cHash(HASHSIZE, true) {};
|
cEitTablesHash(void) : cHash(HASHSIZE, true) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class cEitFilter : public cFilter {
|
class cEitFilter : public cFilter {
|
||||||
private:
|
private:
|
||||||
cMutex mutex;
|
cMutex mutex;
|
||||||
cSectionSyncerHash sectionSyncerHash;
|
cEitTablesHash eitTablesHash;
|
||||||
static time_t disableUntil;
|
static time_t disableUntil;
|
||||||
protected:
|
protected:
|
||||||
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
||||||
|
49
epg.c
49
epg.c
@ -7,7 +7,7 @@
|
|||||||
* Original version (as used in VDR before 1.3.0) written by
|
* Original version (as used in VDR before 1.3.0) written by
|
||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
*
|
*
|
||||||
* $Id: epg.c 5.1 2021/01/04 09:05:26 kls Exp $
|
* $Id: epg.c 5.2 2021/04/04 11:06:30 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "epg.h"
|
#include "epg.h"
|
||||||
@ -547,7 +547,7 @@ bool cEvent::Read(FILE *f, cSchedule *Schedule, int &Line)
|
|||||||
unsigned int Version = 0xFF; // actual value is ignored
|
unsigned int Version = 0xFF; // actual value is ignored
|
||||||
int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
|
int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
|
||||||
if (n >= 3 && n <= 5) {
|
if (n >= 3 && n <= 5) {
|
||||||
Event = (cEvent *)Schedule->GetEvent(EventID, StartTime);
|
Event = (cEvent *)Schedule->GetEventByTime(StartTime);
|
||||||
cEvent *newEvent = NULL;
|
cEvent *newEvent = NULL;
|
||||||
if (Event)
|
if (Event)
|
||||||
DELETENULL(Event->components);
|
DELETENULL(Event->components);
|
||||||
@ -913,6 +913,7 @@ cSchedule::cSchedule(tChannelID ChannelID)
|
|||||||
numTimers = 0;
|
numTimers = 0;
|
||||||
hasRunning = false;
|
hasRunning = false;
|
||||||
modified = 0;
|
modified = 0;
|
||||||
|
onActualTp = false;
|
||||||
presentSeen = 0;
|
presentSeen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -930,6 +931,13 @@ void cSchedule::DecNumTimers(void) const
|
|||||||
numTimersMutex.Unlock();
|
numTimersMutex.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cSchedule::OnActualTp(uchar TableId)
|
||||||
|
{
|
||||||
|
if ((TableId & 0xF0) == 0x50)
|
||||||
|
onActualTp = true;
|
||||||
|
return onActualTp;
|
||||||
|
}
|
||||||
|
|
||||||
cEvent *cSchedule::AddEvent(cEvent *Event)
|
cEvent *cSchedule::AddEvent(cEvent *Event)
|
||||||
{
|
{
|
||||||
events.Add(Event);
|
events.Add(Event);
|
||||||
@ -942,15 +950,21 @@ void cSchedule::DelEvent(cEvent *Event)
|
|||||||
{
|
{
|
||||||
if (Event->schedule == this) {
|
if (Event->schedule == this) {
|
||||||
UnhashEvent(Event);
|
UnhashEvent(Event);
|
||||||
|
Event->schedule = NULL;
|
||||||
events.Del(Event);
|
events.Del(Event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedule::HashEvent(cEvent *Event)
|
void cSchedule::HashEvent(cEvent *Event)
|
||||||
{
|
{
|
||||||
|
if (cEvent *p = eventsHashID.Get(Event->EventID()))
|
||||||
|
eventsHashID.Del(p, p->EventID());
|
||||||
eventsHashID.Add(Event, Event->EventID());
|
eventsHashID.Add(Event, Event->EventID());
|
||||||
if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels
|
if (Event->StartTime() > 0) { // 'StartTime < 0' is apparently used with NVOD channels
|
||||||
|
if (cEvent *p = eventsHashStartTime.Get(Event->StartTime()))
|
||||||
|
eventsHashStartTime.Del(p, p->StartTime());
|
||||||
eventsHashStartTime.Add(Event, Event->StartTime());
|
eventsHashStartTime.Add(Event, Event->StartTime());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedule::UnhashEvent(cEvent *Event)
|
void cSchedule::UnhashEvent(cEvent *Event)
|
||||||
@ -990,6 +1004,7 @@ const cEvent *cSchedule::GetFollowingEvent(void) const
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEPRECATED_SCHEDULE_GET_EVENT
|
||||||
const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
|
const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
|
||||||
{
|
{
|
||||||
// Returns the event info with the given StartTime or, if no actual StartTime
|
// Returns the event info with the given StartTime or, if no actual StartTime
|
||||||
@ -999,6 +1014,19 @@ const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
|
|||||||
else
|
else
|
||||||
return eventsHashID.Get(EventID);
|
return eventsHashID.Get(EventID);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const cEvent *cSchedule::GetEventById(tEventID EventID) const
|
||||||
|
{
|
||||||
|
return eventsHashID.Get(EventID);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cEvent *cSchedule::GetEventByTime(time_t StartTime) const
|
||||||
|
{
|
||||||
|
if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels
|
||||||
|
return eventsHashStartTime.Get(StartTime);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
const cEvent *cSchedule::GetEventAround(time_t Time) const
|
const cEvent *cSchedule::GetEventAround(time_t Time) const
|
||||||
{
|
{
|
||||||
@ -1039,6 +1067,7 @@ void cSchedule::ClrRunningStatus(cChannel *Channel)
|
|||||||
if (p->RunningStatus() >= SI::RunningStatusPausing) {
|
if (p->RunningStatus() >= SI::RunningStatusPausing) {
|
||||||
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
|
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
|
||||||
hasRunning = false;
|
hasRunning = false;
|
||||||
|
SetModified();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1067,22 +1096,24 @@ void cSchedule::Sort(void)
|
|||||||
|
|
||||||
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
|
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
|
||||||
{
|
{
|
||||||
|
// Events are sorted by start time.
|
||||||
if (SegmentStart > 0 && SegmentEnd > 0) {
|
if (SegmentStart > 0 && SegmentEnd > 0) {
|
||||||
cEvent *p = events.First();
|
cEvent *p = events.First();
|
||||||
while (p) {
|
while (p) {
|
||||||
cEvent *n = events.Next(p);
|
cEvent *n = events.Next(p);
|
||||||
if (p->EndTime() > SegmentStart) {
|
if (p->StartTime() >= SegmentStart) {
|
||||||
if (p->StartTime() < SegmentEnd) {
|
if (p->StartTime() < SegmentEnd) {
|
||||||
// The event overlaps with the given time segment.
|
// The event starts within the given time segment.
|
||||||
if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
|
if ((p->TableID() > 0x4E || TableID == 0x4E) && (p->TableID() != TableID || p->Version() != Version)) {
|
||||||
// The segment overwrites all events from tables with higher ids, and
|
// The segment overwrites all events from tables with other ids, and
|
||||||
// within the same table id all events must have the same version.
|
// within the same table id all events must have the same version.
|
||||||
|
// Special consideration: table 0x4E can only be overwritten with the same id!
|
||||||
DelEvent(p);
|
DelEvent(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
p = n;
|
p = n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
epg.h
9
epg.h
@ -7,7 +7,7 @@
|
|||||||
* Original version (as used in VDR before 1.3.0) written by
|
* Original version (as used in VDR before 1.3.0) written by
|
||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
*
|
*
|
||||||
* $Id: epg.h 4.7 2017/05/28 12:59:20 kls Exp $
|
* $Id: epg.h 5.1 2021/04/04 11:06:30 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __EPG_H
|
#ifndef __EPG_H
|
||||||
@ -156,12 +156,14 @@ private:
|
|||||||
cHash<cEvent> eventsHashStartTime;
|
cHash<cEvent> eventsHashStartTime;
|
||||||
mutable u_int16_t numTimers;// The number of timers that use this schedule
|
mutable u_int16_t numTimers;// The number of timers that use this schedule
|
||||||
bool hasRunning;
|
bool hasRunning;
|
||||||
|
bool onActualTp;
|
||||||
int modified;
|
int modified;
|
||||||
time_t presentSeen;
|
time_t presentSeen;
|
||||||
public:
|
public:
|
||||||
cSchedule(tChannelID ChannelID);
|
cSchedule(tChannelID ChannelID);
|
||||||
tChannelID ChannelID(void) const { return channelID; }
|
tChannelID ChannelID(void) const { return channelID; }
|
||||||
bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
|
bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
|
||||||
|
bool OnActualTp(uchar TableId);
|
||||||
time_t PresentSeen(void) const { return presentSeen; }
|
time_t PresentSeen(void) const { return presentSeen; }
|
||||||
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
|
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
|
||||||
void SetModified(void) { modified++; }
|
void SetModified(void) { modified++; }
|
||||||
@ -183,7 +185,12 @@ public:
|
|||||||
const cList<cEvent> *Events(void) const { return &events; }
|
const cList<cEvent> *Events(void) const { return &events; }
|
||||||
const cEvent *GetPresentEvent(void) const;
|
const cEvent *GetPresentEvent(void) const;
|
||||||
const cEvent *GetFollowingEvent(void) const;
|
const cEvent *GetFollowingEvent(void) const;
|
||||||
|
#define DEPRECATED_SCHEDULE_GET_EVENT 1
|
||||||
|
#if DEPRECATED_SCHEDULE_GET_EVENT
|
||||||
const cEvent *GetEvent(tEventID EventID, time_t StartTime = 0) const;
|
const cEvent *GetEvent(tEventID EventID, time_t StartTime = 0) const;
|
||||||
|
#endif
|
||||||
|
const cEvent *GetEventById(tEventID EventID) const;
|
||||||
|
const cEvent *GetEventByTime(time_t StartTime) const;
|
||||||
const cEvent *GetEventAround(time_t Time) const;
|
const cEvent *GetEventAround(time_t Time) const;
|
||||||
void Dump(const cChannels *Channels, FILE *f, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0) const;
|
void Dump(const cChannels *Channels, FILE *f, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0) const;
|
||||||
static bool Read(FILE *f, cSchedules *Schedules);
|
static bool Read(FILE *f, cSchedules *Schedules);
|
||||||
|
Loading…
Reference in New Issue
Block a user