/* * setup.c: IPTV plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * * $Id: setup.c,v 1.44 2007/11/04 15:23:16 rahrenbe Exp $ */ #include #include #include #include #include "common.h" #include "config.h" #include "device.h" #include "setup.h" #ifndef trVDR #define trVDR(s) tr(s) #endif // --- cIptvMenuEditChannel -------------------------------------------------- class cIptvMenuEditChannel : public cOsdMenu { private: enum { eProtocolUDP, eProtocolHTTP, eProtocolFILE, eProtocolEXT, eProtocolCount }; struct tIptvChannel { int frequency, source, protocol, parameter, vpid, ppid, tpid, sid, nid, tid, rid; int apid[MAXAPIDS + 1], dpid[MAXDPIDS + 1], spid[MAXSPIDS + 1], caids[MAXCAIDS + 1]; char name[256], location[256]; } data; cChannel *channel; const char *protocols[eProtocolCount]; void Setup(void); cString GetIptvSettings(const char *Param, int *Parameter, int *Protocol); void GetChannelData(cChannel *Channel); void SetChannelData(cChannel *Channel); public: cIptvMenuEditChannel(cChannel *Channel, bool New = false); virtual eOSState ProcessKey(eKeys Key); }; cIptvMenuEditChannel::cIptvMenuEditChannel(cChannel *Channel, bool New) :cOsdMenu(trVDR("Edit channel"), 16) { protocols[eProtocolUDP] = tr("UDP"); protocols[eProtocolHTTP] = tr("HTTP"); protocols[eProtocolFILE] = tr("FILE"); protocols[eProtocolEXT] = tr("EXT"); channel = Channel; GetChannelData(channel); if (New) { channel = NULL; data.nid = 0; data.tid = 0; data.rid = 0; } Setup(); } cString cIptvMenuEditChannel::GetIptvSettings(const char *Param, int *Parameter, int *Protocol) { char *tag = NULL; char *proto = NULL; char *loc = NULL; if (sscanf(Param, "%a[^|]|%a[^|]|%a[^|]|%u", &tag, &proto, &loc, Parameter) == 4) { cString tagstr(tag, true); cString protostr(proto, true); cString locstr(loc, true); // check if IPTV tag if (strncasecmp(*tagstr, "IPTV", 4) == 0) { // check if protocol is supported and update the pointer if (strncasecmp(*protostr, "UDP", 3) == 0) *Protocol = eProtocolUDP; else if (strncasecmp(*protostr, "HTTP", 4) == 0) *Protocol = eProtocolHTTP; else if (strncasecmp(*protostr, "FILE", 4) == 0) *Protocol = eProtocolFILE; else if (strncasecmp(*protostr, "EXT", 3) == 0) *Protocol = eProtocolEXT; else return NULL; // return location return locstr; } } return NULL; } void cIptvMenuEditChannel::GetChannelData(cChannel *Channel) { if (Channel) { int parameter, protocol; data.frequency = Channel->Frequency(); data.source = Channel->Source(); data.vpid = Channel->Vpid(); data.ppid = Channel->Ppid(); data.tpid = Channel->Tpid(); for (unsigned int i = 0; i < sizeof(data.apid); ++i) data.apid[i] = Channel->Apid(i); for (unsigned int i = 0; i < sizeof(data.dpid); ++i) data.dpid[i] = Channel->Dpid(i); for (unsigned int i = 0; i < sizeof(data.spid); ++i) data.spid[i] = Channel->Spid(i); for (unsigned int i = 0; i < sizeof(data.caids); ++i) data.caids[i] = Channel->Ca(i); data.sid = Channel->Sid(); data.nid = Channel->Nid(); data.tid = Channel->Tid(); data.rid = Channel->Rid(); strn0cpy(data.name, Channel->Name(), sizeof(data.name)); strn0cpy(data.location, *GetIptvSettings(Channel->PluginParam(), ¶meter, &protocol), sizeof(data.location)); data.protocol = protocol; data.parameter = parameter; } else { data.frequency = 1; data.source = cSource::FromData(cSource::stPlug); data.vpid = 0; data.ppid = 0; data.tpid = 0; for (unsigned int i = 0; i < sizeof(data.apid); ++i) data.apid[i] = 0; for (unsigned int i = 0; i < sizeof(data.dpid); ++i) data.dpid[i] = 0; for (unsigned int i = 0; i < sizeof(data.spid); ++i) data.spid[i] = 0; for (unsigned int i = 0; i < sizeof(data.caids); ++i) data.caids[i] = 0; data.sid = 1; data.nid = 0; data.tid = 0; data.rid = 0; strn0cpy(data.name, "IPTV", sizeof(data.name)); strn0cpy(data.location, "127.0.0.1", sizeof(data.location)); data.protocol = eProtocolUDP; data.parameter = 1234; } } void cIptvMenuEditChannel::SetChannelData(cChannel *Channel) { if (Channel) { cString param; char alangs[MAXAPIDS][MAXLANGCODE2] = { "" }; char dlangs[MAXDPIDS][MAXLANGCODE2] = { "" }; switch (data.protocol) { case eProtocolEXT: param = cString::sprintf("IPTV|EXT|%s|%d", data.location, data.parameter); break; case eProtocolFILE: param = cString::sprintf("IPTV|FILE|%s|%d", data.location, data.parameter); break; case eProtocolHTTP: param = cString::sprintf("IPTV|HTTP|%s|%d", data.location, data.parameter); break; default: case eProtocolUDP: param = cString::sprintf("IPTV|UDP|%s|%d", data.location, data.parameter); break; } #if defined(APIVERSNUM) && APIVERSNUM < 10510 Channel->SetPids(data.vpid, data.ppid, data.apid, alangs, data.dpid, dlangs, data.tpid); #else char slangs[MAXSPIDS][MAXLANGCODE2] = { "" }; Channel->SetPids(data.vpid, data.ppid, data.apid, alangs, data.dpid, dlangs, data.spid, slangs, data.tpid); #endif Channel->SetCaIds(data.caids); Channel->SetId(data.nid, data.tid, data.sid, data.rid); Channel->SetName(data.name, "", "IPTV"); Channel->SetPlugTransponderData(cSource::stPlug, data.frequency, param); } } void cIptvMenuEditChannel::Setup(void) { int current = Current(); Clear(); // IPTV specific settings Add(new cMenuEditStraItem(tr("Protocol"), &data.protocol, eProtocolCount, protocols)); switch (data.protocol) { case eProtocolFILE: #if defined(APIVERSNUM) && APIVERSNUM < 10511 Add(new cMenuEditStrItem(trVDR("File"), data.location, sizeof(data.location), trVDR(FileNameChars))); #else Add(new cMenuEditStrItem(trVDR("File"), data.location, sizeof(data.location))); #endif Add(new cMenuEditIntItem(tr("Delay (ms)"), &data.parameter, 0, 0xFFFF)); break; case eProtocolEXT: #if defined(APIVERSNUM) && APIVERSNUM < 10511 Add(new cMenuEditStrItem(tr("Script"), data.location, sizeof(data.location), trVDR(FileNameChars))); #else Add(new cMenuEditStrItem(tr("Script"), data.location, sizeof(data.location))); #endif Add(new cMenuEditIntItem(tr("Parameter"), &data.parameter, 0, 0xFFFF)); break; case eProtocolHTTP: case eProtocolUDP: default: #if defined(APIVERSNUM) && APIVERSNUM < 10511 Add(new cMenuEditStrItem(tr("Address"), data.location, sizeof(data.location), trVDR(FileNameChars))); #else Add(new cMenuEditStrItem(tr("Address"), data.location, sizeof(data.location))); #endif Add(new cMenuEditIntItem(tr("Port"), &data.parameter, 0, 0xFFFF)); break; } // Normal settings #if defined(APIVERSNUM) && APIVERSNUM < 10511 Add(new cMenuEditStrItem(trVDR("Name"), data.name, sizeof(data.name), trVDR(FileNameChars))); #else Add(new cMenuEditStrItem(trVDR("Name"), data.name, sizeof(data.name))); #endif Add(new cMenuEditIntItem(trVDR("Frequency"), &data.frequency)); Add(new cMenuEditIntItem(trVDR("Vpid"), &data.vpid, 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("Ppid"), &data.ppid, 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("Apid1"), &data.apid[0], 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("Apid2"), &data.apid[1], 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("Dpid1"), &data.dpid[0], 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("Dpid2"), &data.dpid[1], 0, 0x1FFF)); #if defined(APIVERSNUM) && APIVERSNUM >= 10510 Add(new cMenuEditIntItem(trVDR("Spid1"), &data.spid[0], 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("Spid2"), &data.spid[1], 0, 0x1FFF)); #endif Add(new cMenuEditIntItem(trVDR("Tpid"), &data.tpid, 0, 0x1FFF)); Add(new cMenuEditIntItem(trVDR("CA"), &data.caids[0], 0, 0xFFFF)); Add(new cMenuEditIntItem(trVDR("Sid"), &data.sid, 1, 0xFFFF)); Add(new cMenuEditIntItem(tr ("Nid"), &data.nid, 0, 0xFFFF)); Add(new cMenuEditIntItem(tr ("Tid"), &data.tid, 0, 0xFFFF)); Add(new cMenuEditIntItem(tr ("Rid"), &data.rid, 0, 0x1FFF)); SetCurrent(Get(current)); Display(); } eOSState cIptvMenuEditChannel::ProcessKey(eKeys Key) { int oldProtocol = data.protocol; eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { if (Key == kOk) { cChannel newchannel; SetChannelData(&newchannel); bool uniquityFailed = false; bool firstIncrement = true; // Search for identical channels as these will be ignored by vdr for (cChannel *iteratorChannel = Channels.First(); iteratorChannel; iteratorChannel = Channels.Next(iteratorChannel)) { // This is one of the channels cause the uniquity check to fail if (!iteratorChannel->GroupSep() && iteratorChannel != channel && iteratorChannel->GetChannelID() == newchannel.GetChannelID()) { // See if it has unique Plugin param. If yes then increment // the corresponding Rid until it is unique if (strcmp(iteratorChannel->PluginParam(), newchannel.PluginParam())) { // If the channel RID is already at maximum, then fail the // channel modification if (iteratorChannel->Rid() >= 0x1FFF) { debug("Cannot increment RID over maximum value\n"); uniquityFailed = true; break; } debug("Incrementing conflicting channel RID\n"); iteratorChannel->SetId(iteratorChannel->Nid(), iteratorChannel->Tid(), iteratorChannel->Sid(), firstIncrement ? 0 : iteratorChannel->Rid() + 1); // Try zero Rid:s at first increment. Prevents them from // creeping slowly towards their maximum value firstIncrement = false; // Re-set the search and start again iteratorChannel = Channels.First(); continue; // Cannot work around by incrementing rid because channels // are actually copies of each other } else { uniquityFailed = true; break; } } } if (!uniquityFailed) { if (channel) { SetChannelData(channel); isyslog("edited channel %d %s", channel->Number(), *channel->ToText()); state = osBack; } else { channel = new cChannel; SetChannelData(channel); Channels.Add(channel); Channels.ReNumber(); isyslog("added channel %d %s", channel->Number(), *channel->ToText()); state = osUser1; } Channels.SetModified(true); } else { Skins.Message(mtError, tr("Cannot find unique channel settings!")); state = osContinue; } } } if ((Key != kNone) && (data.protocol != oldProtocol)) { switch (data.protocol) { case eProtocolEXT: strn0cpy(data.location, "iptvstream.sh", sizeof(data.location)); data.parameter = 0; break; case eProtocolFILE: strn0cpy(data.location, "/video/stream.ts", sizeof(data.location)); data.parameter = 0; break; case eProtocolHTTP: strn0cpy(data.location, "127.0.0.1/TS/1", sizeof(data.location)); data.parameter = 3000; break; default: case eProtocolUDP: strn0cpy(data.location, "127.0.0.1", sizeof(data.location)); data.parameter = 1234; break; } Setup(); } return state; } // --- cIptvMenuChannelItem -------------------------------------------------- class cIptvMenuChannelItem : public cOsdItem { private: cChannel *channel; public: cIptvMenuChannelItem(cChannel *Channel); virtual void Set(void); cChannel *Channel(void) { return channel; } }; cIptvMenuChannelItem::cIptvMenuChannelItem(cChannel *Channel) { channel = Channel; Set(); } void cIptvMenuChannelItem::Set(void) { char *buffer = NULL; asprintf(&buffer, "%d\t%s", channel->Number(), channel->Name()); SetText(buffer, false); } // --- cIptvMenuChannels ----------------------------------------------------- class cIptvMenuChannels : public cOsdMenu { private: void Setup(void); cChannel *GetChannel(int Index); void Propagate(void); protected: eOSState Edit(void); eOSState New(void); eOSState Delete(void); eOSState Switch(void); public: cIptvMenuChannels(); ~cIptvMenuChannels(); virtual eOSState ProcessKey(eKeys Key); }; cIptvMenuChannels::cIptvMenuChannels(void) :cOsdMenu(tr("IPTV Channels"), numdigits(Channels.MaxNumber()) + 1) { Setup(); Channels.IncBeingEdited(); } cIptvMenuChannels::~cIptvMenuChannels() { Channels.DecBeingEdited(); } void cIptvMenuChannels::Setup(void) { Clear(); for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { if (!channel->GroupSep() && channel->IsPlug() && !strncasecmp(channel->PluginParam(), "IPTV", 4)) { cIptvMenuChannelItem *item = new cIptvMenuChannelItem(channel); Add(item); } } SetHelp(trVDR("Button$Edit"), trVDR("Button$New"), trVDR("Button$Delete"), NULL); Display(); } cChannel *cIptvMenuChannels::GetChannel(int Index) { cIptvMenuChannelItem *p = (cIptvMenuChannelItem *)Get(Index); return p ? (cChannel *)p->Channel() : NULL; } void cIptvMenuChannels::Propagate(void) { Channels.ReNumber(); for (cIptvMenuChannelItem *ci = (cIptvMenuChannelItem *)First(); ci; ci = (cIptvMenuChannelItem *)ci->Next()) ci->Set(); Display(); Channels.SetModified(true); } eOSState cIptvMenuChannels::Switch(void) { if (HasSubMenu() || Count() == 0) return osContinue; cChannel *ch = GetChannel(Current()); if (ch) return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue; return osEnd; } eOSState cIptvMenuChannels::Edit(void) { if (HasSubMenu() || Count() == 0) return osContinue; cChannel *ch = GetChannel(Current()); if (ch) return AddSubMenu(new cIptvMenuEditChannel(ch)); return osContinue; } eOSState cIptvMenuChannels::New(void) { if (HasSubMenu()) return osContinue; return AddSubMenu(new cIptvMenuEditChannel(GetChannel(Current()), true)); } eOSState cIptvMenuChannels::Delete(void) { if (!HasSubMenu() && Count() > 0) { int CurrentChannelNr = cDevice::CurrentChannel(); cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); int Index = Current(); cChannel *channel = GetChannel(Current()); int DeletedChannel = channel->Number(); // Check if there is a timer using this channel: if (channel->HasTimer()) { Skins.Message(mtError, trVDR("Channel is being used by a timer!")); return osContinue; } if (Interface->Confirm(trVDR("Delete channel?"))) { if (CurrentChannel && channel == CurrentChannel) { int n = Channels.GetNextNormal(CurrentChannel->Index()); if (n < 0) n = Channels.GetPrevNormal(CurrentChannel->Index()); CurrentChannel = Channels.Get(n); CurrentChannelNr = 0; // triggers channel switch below } Channels.Del(channel); cOsdMenu::Del(Index); Propagate(); isyslog("channel %d deleted", DeletedChannel); if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) Channels.SwitchTo(CurrentChannel->Number()); else cDevice::SetCurrentChannel(CurrentChannel); } } } return osContinue; } eOSState cIptvMenuChannels::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); switch (state) { case osUser1: { cChannel *channel = Channels.Last(); if (channel) { Add(new cIptvMenuChannelItem(channel), true); return CloseSubMenu(); } } break; default: if (state == osUnknown) { switch (Key) { case kOk: return Switch(); case kRed: return Edit(); case kGreen: return New(); case kYellow: return Delete(); default: break; } } } return state; } // --- cIptvMenuInfo --------------------------------------------------------- class cIptvMenuInfo : public cOsdMenu { private: enum { INFO_TIMEOUT_MS = 2000 }; cString text; cTimeMs timeout; unsigned int page; void UpdateInfo(); public: cIptvMenuInfo(); virtual ~cIptvMenuInfo(); virtual void Display(void); virtual eOSState ProcessKey(eKeys Key); }; cIptvMenuInfo::cIptvMenuInfo() :cOsdMenu(tr("IPTV Information")), text(""), timeout(), page(IPTV_DEVICE_INFO_GENERAL) { timeout.Set(INFO_TIMEOUT_MS); UpdateInfo(); SetHelp(tr("General"), tr("Pids"), tr("Filters"), tr("Bits/bytes")); } cIptvMenuInfo::~cIptvMenuInfo() { } void cIptvMenuInfo::UpdateInfo() { cIptvDevice *device = cIptvDevice::GetIptvDevice(cDevice::ActualDevice()->CardIndex()); if (device) text = device->GetInformation(page); else text = cString(tr("IPTV information not available!")); Display(); timeout.Set(INFO_TIMEOUT_MS); } void cIptvMenuInfo::Display(void) { cOsdMenu::Display(); DisplayMenu()->SetText(text, true); if (text) cStatus::MsgOsdTextItem(text); } eOSState cIptvMenuInfo::ProcessKey(eKeys Key) { switch (Key) { case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: case kLeft|k_Repeat: case kLeft: case kRight|k_Repeat: case kRight: DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight); cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft); return osContinue; default: break; } eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: return osBack; case kRed: page = IPTV_DEVICE_INFO_GENERAL; UpdateInfo(); break; case kGreen: page = IPTV_DEVICE_INFO_PIDS; UpdateInfo(); break; case kYellow: page = IPTV_DEVICE_INFO_FILTERS; UpdateInfo(); break; case kBlue: IptvConfig.SetUseBytes(!IptvConfig.GetUseBytes()); UpdateInfo(); break; default: if (timeout.TimedOut()) UpdateInfo(); state = osContinue; break; } } return state; } // --- cIptvPluginSetup ------------------------------------------------------ cIptvPluginSetup::cIptvPluginSetup() { debug("cIptvPluginSetup::cIptvPluginSetup()\n"); tsBufferSize = IptvConfig.GetTsBufferSize(); tsBufferPrefill = IptvConfig.GetTsBufferPrefillRatio(); extProtocolBasePort = IptvConfig.GetExtProtocolBasePort(); sectionFiltering = IptvConfig.GetSectionFiltering(); sidScanning = IptvConfig.GetSidScanning(); numDisabledFilters = IptvConfig.GetDisabledFiltersCount(); if (numDisabledFilters > SECTION_FILTER_TABLE_SIZE) numDisabledFilters = SECTION_FILTER_TABLE_SIZE; for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) { disabledFilterIndexes[i] = IptvConfig.GetDisabledFilters(i); disabledFilterNames[i] = tr(section_filter_table[i].description); } Setup(); SetHelp(trVDR("Channels"), NULL, NULL, trVDR("Button$Info")); } void cIptvPluginSetup::Setup(void) { int current = Current(); Clear(); Add(new cMenuEditIntItem( tr("TS buffer size [MB]"), &tsBufferSize, 1, 4)); Add(new cMenuEditIntItem( tr("TS buffer prefill ratio [%]"), &tsBufferPrefill, 0, 40)); Add(new cMenuEditIntItem( tr("EXT protocol base port"), &extProtocolBasePort, 0, 0xFFF7)); Add(new cMenuEditBoolItem(tr("Use section filtering"), §ionFiltering)); if (sectionFiltering) { Add(new cMenuEditBoolItem(tr("Scan Sid automatically"), &sidScanning)); Add(new cMenuEditIntItem( tr("Disable filters"), &numDisabledFilters, 0, SECTION_FILTER_TABLE_SIZE)); for (int i = 0; i < numDisabledFilters; ++i) { // TRANSLATORS: note the singular! Add(new cMenuEditStraItem(tr("Disable filter"), &disabledFilterIndexes[i], SECTION_FILTER_TABLE_SIZE, disabledFilterNames)); } } SetCurrent(Get(current)); Display(); } eOSState cIptvPluginSetup::EditChannel(void) { debug("cIptvPluginSetup::EditChannel()\n"); if (HasSubMenu()) return osContinue; return AddSubMenu(new cIptvMenuChannels()); } eOSState cIptvPluginSetup::ShowInfo(void) { debug("cIptvPluginSetup::ShowInfo()\n"); if (HasSubMenu()) return osContinue; return AddSubMenu(new cIptvMenuInfo()); } eOSState cIptvPluginSetup::ProcessKey(eKeys Key) { int oldsectionFiltering = sectionFiltering; int oldNumDisabledFilters = numDisabledFilters; eOSState state = cMenuSetupPage::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kRed: return EditChannel(); case kBlue: return ShowInfo(); default: state = osContinue; } } if ((Key != kNone) && ((numDisabledFilters != oldNumDisabledFilters) || (sectionFiltering != oldsectionFiltering))) { while ((numDisabledFilters < oldNumDisabledFilters) && (oldNumDisabledFilters > 0)) disabledFilterIndexes[--oldNumDisabledFilters] = -1; Setup(); } return state; } void cIptvPluginSetup::StoreFilters(const char *Name, int *Values) { char buffer[SECTION_FILTER_TABLE_SIZE * 4]; char *q = buffer; for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) { char s[3]; if (Values[i] < 0) break; if (q > buffer) *q++ = ' '; snprintf(s, sizeof(s), "%d", Values[i]); strncpy(q, s, strlen(s)); q += strlen(s); } *q = 0; debug("cIptvPluginSetup::StoreFilters(): %s=%s\n", Name, buffer); SetupStore(Name, buffer); } void cIptvPluginSetup::Store(void) { // Store values into setup.conf SetupStore("TsBufferSize", tsBufferSize); SetupStore("TsBufferPrefill", tsBufferPrefill); SetupStore("ExtProtocolBasePort", extProtocolBasePort); SetupStore("SectionFiltering", sectionFiltering); SetupStore("SidScanning", sidScanning); StoreFilters("DisabledFilters", disabledFilterIndexes); // Update global config IptvConfig.SetTsBufferSize(tsBufferSize); IptvConfig.SetTsBufferPrefillRatio(tsBufferPrefill); IptvConfig.SetExtProtocolBasePort(extProtocolBasePort); IptvConfig.SetSectionFiltering(sectionFiltering); IptvConfig.SetSidScanning(sidScanning); for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) IptvConfig.SetDisabledFilters(i, disabledFilterIndexes[i]); }