CPVREpgInfoTagPtr CPVREpg::GetTagNow(bool bUpdateIfNeeded /* = true */) const { CSingleLock lock(m_critSection); if (m_nowActiveStart.IsValid()) { std::map<CDateTime, CPVREpgInfoTagPtr>::const_iterator it = m_tags.find(m_nowActiveStart); if (it != m_tags.end() && it->second->IsActive()) return it->second; } if (bUpdateIfNeeded) { CPVREpgInfoTagPtr lastActiveTag; /* one of the first items will always match if the list is sorted */ for (std::map<CDateTime, CPVREpgInfoTagPtr>::const_iterator it = m_tags.begin(); it != m_tags.end(); ++it) { if (it->second->IsActive()) { m_nowActiveStart = it->first; return it->second; } else if (it->second->WasActive()) lastActiveTag = it->second; } /* there might be a gap between the last and next event. return the last if found and it ended not more than 5 minutes ago */ if (lastActiveTag && lastActiveTag->EndAsUTC() + CDateTimeSpan(0, 0, 5, 0) >= CDateTime::GetUTCDateTime()) return lastActiveTag; } return CPVREpgInfoTagPtr(); }
CPVRTimerInfoTagPtr CPVRTimers::GetTimerForEpgTag(const CPVREpgInfoTagPtr &epgTag) const { if (epgTag) { CSingleLock lock(m_critSection); for (const auto &tagsEntry : m_tags) { for (const auto &timersEntry : tagsEntry.second) { if (timersEntry->IsTimerRule()) continue; if (timersEntry->GetEpgInfoTag(false) == epgTag) return timersEntry; if (timersEntry->m_iClientChannelUid != PVR_CHANNEL_INVALID_UID && timersEntry->m_iClientChannelUid == epgTag->UniqueChannelID()) { if (timersEntry->UniqueBroadcastID() != EPG_TAG_INVALID_UID && timersEntry->UniqueBroadcastID() == epgTag->UniqueBroadcastID()) return timersEntry; if (timersEntry->m_bIsRadio == epgTag->IsRadio() && timersEntry->StartAsUTC() <= epgTag->StartAsUTC() && timersEntry->EndAsUTC() >= epgTag->EndAsUTC()) return timersEntry; } } } } return CPVRTimerInfoTagPtr(); }
CPVRRecordingPtr CPVRRecordings::GetRecordingForEpgTag(const CPVREpgInfoTagPtr &epgTag) const { CSingleLock lock(m_critSection); for (const auto recording : m_recordings) { if (recording.second->IsDeleted()) continue; unsigned int iEpgEvent = recording.second->BroadcastUid(); if (iEpgEvent != EPG_TAG_INVALID_UID) { if (iEpgEvent == epgTag->UniqueBroadcastID()) { // uid matches. perfect. return recording.second; } } else { // uid is optional, so check other relevant data. // note: don't use recording.second->Channel() for comparing channels here as this can lead // to deadlocks. compare client ids and channel ids instead, this has the same effect. if (epgTag->Channel() && recording.second->ClientID() == epgTag->Channel()->ClientID() && recording.second->ChannelUid() == epgTag->Channel()->UniqueID() && recording.second->RecordingTimeAsUTC() <= epgTag->StartAsUTC() && recording.second->EndTimeAsUTC() >= epgTag->EndAsUTC()) return recording.second; } } return CPVRRecordingPtr(); }
CPVRRecordingPtr CPVRRecordings::GetRecordingForEpgTag(const CPVREpgInfoTagPtr &epgTag) const { if (!epgTag) return {}; CSingleLock lock(m_critSection); for (const auto recording : m_recordings) { if (recording.second->IsDeleted()) continue; if (recording.second->ClientID() != epgTag->ClientID()) continue; if (recording.second->ChannelUid() != epgTag->UniqueChannelID()) continue; unsigned int iEpgEvent = recording.second->BroadcastUid(); if (iEpgEvent != EPG_TAG_INVALID_UID) { if (iEpgEvent == epgTag->UniqueBroadcastID()) return recording.second; } else { if (recording.second->RecordingTimeAsUTC() <= epgTag->StartAsUTC() && recording.second->EndTimeAsUTC() >= epgTag->EndAsUTC()) return recording.second; } } return CPVRRecordingPtr(); }
void CGUIEPGGridContainerModel::FindChannelAndBlockIndex(int channelUid, unsigned int broadcastUid, int eventOffset, int &newChannelIndex, int &newBlockIndex) const { const CDateTimeSpan blockDuration(0, 0, MINSPERBLOCK, 0); newChannelIndex = INVALID_INDEX; newBlockIndex = INVALID_INDEX; // find the channel int iCurrentChannel = 0; for (const auto& channel : m_channelItems) { if (channel->GetPVRChannelInfoTag()->UniqueID() == channelUid) { newChannelIndex = iCurrentChannel; break; } iCurrentChannel++; } if (newChannelIndex != INVALID_INDEX) { // find the block CDateTime gridCursor(m_gridStart); //reset cursor for new channel unsigned long progIdx = m_epgItemsPtr[newChannelIndex].start; unsigned long lastIdx = m_epgItemsPtr[newChannelIndex].stop; int iEpgId = m_programmeItems[progIdx]->GetEPGInfoTag()->EpgID(); CPVREpgInfoTagPtr tag; for (int block = 0; block < m_blocks; ++block) { while (progIdx <= lastIdx) { tag = m_programmeItems[progIdx]->GetEPGInfoTag(); if (tag->EpgID() != iEpgId || gridCursor < tag->StartAsUTC() || m_gridEnd <= tag->StartAsUTC()) break; // next block if (gridCursor < tag->EndAsUTC()) { if (broadcastUid > 0 && tag->UniqueBroadcastID() == broadcastUid) { newBlockIndex = block + eventOffset; return; // done. } break; // next block } progIdx++; } gridCursor += blockDuration; } } }
int CGUIEPGGridContainerModel::GetLastEventBlock(const CPVREpgInfoTagPtr &event) const { // Last block of a tag is always the block calculated using event's end time, not rounded up. // Refer to CGUIEPGGridContainerModel::Refresh, where the model is created, for details! return GetBlock(event->EndAsUTC()); }
void CGUIEPGGridContainerModel::Initialize(const std::unique_ptr<CFileItemList> &items, const CDateTime &gridStart, const CDateTime &gridEnd, int iRulerUnit, int iBlocksPerPage, float fBlockSize) { if (!m_channelItems.empty()) { CLog::LogF(LOGERROR, "Already initialized!"); return; } //////////////////////////////////////////////////////////////////////// // Create programme & channel items m_programmeItems.reserve(items->Size()); CFileItemPtr fileItem; int iLastChannelID = -1; ItemsPtr itemsPointer; itemsPointer.start = 0; CPVRChannelPtr channel; int j = 0; for (int i = 0; i < items->Size(); ++i) { fileItem = items->Get(i); if (!fileItem->HasEPGInfoTag() || !fileItem->GetEPGInfoTag()->HasChannel()) continue; m_programmeItems.emplace_back(fileItem); channel = fileItem->GetEPGInfoTag()->Channel(); if (!channel) continue; int iCurrentChannelID = channel->ChannelID(); if (iCurrentChannelID != iLastChannelID) { if (j > 0) { itemsPointer.stop = j - 1; m_epgItemsPtr.emplace_back(itemsPointer); itemsPointer.start = j; } iLastChannelID = iCurrentChannelID; m_channelItems.emplace_back(CFileItemPtr(new CFileItem(channel))); } ++j; } if (!m_programmeItems.empty()) { itemsPointer.stop = m_programmeItems.size() - 1; m_epgItemsPtr.emplace_back(itemsPointer); } /* check for invalid start and end time */ if (gridStart >= gridEnd) { // default to start "now minus GRID_START_PADDING minutes" and end "start plus one page". m_gridStart = CDateTime::GetUTCDateTime() - CDateTimeSpan(0, 0, GetGridStartPadding(), 0); m_gridEnd = m_gridStart + CDateTimeSpan(0, 0, iBlocksPerPage * MINSPERBLOCK, 0); } else if (gridStart > (CDateTime::GetUTCDateTime() - CDateTimeSpan(0, 0, GetGridStartPadding(), 0))) { // adjust to start "now minus GRID_START_PADDING minutes". m_gridStart = CDateTime::GetUTCDateTime() - CDateTimeSpan(0, 0, GetGridStartPadding(), 0); m_gridEnd = gridEnd; } else { m_gridStart = gridStart; m_gridEnd = gridEnd; } // roundup m_gridStart = CDateTime(m_gridStart.GetYear(), m_gridStart.GetMonth(), m_gridStart.GetDay(), m_gridStart.GetHour(), m_gridStart.GetMinute() >= 30 ? 30 : 0, 0); m_gridEnd = CDateTime(m_gridEnd.GetYear(), m_gridEnd.GetMonth(), m_gridEnd.GetDay(), m_gridEnd.GetHour(), m_gridEnd.GetMinute() >= 30 ? 30 : 0, 0); //////////////////////////////////////////////////////////////////////// // Create ruler items CDateTime ruler; ruler.SetFromUTCDateTime(m_gridStart); CDateTime rulerEnd; rulerEnd.SetFromUTCDateTime(m_gridEnd); CFileItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedDate(true))); rulerItem->SetProperty("DateLabel", true); m_rulerItems.emplace_back(rulerItem); const CDateTimeSpan unit(0, 0, iRulerUnit * MINSPERBLOCK, 0); for (; ruler < rulerEnd; ruler += unit) { rulerItem.reset(new CFileItem(ruler.GetAsLocalizedTime("", false))); rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true)); m_rulerItems.emplace_back(rulerItem); } FreeItemsMemory(); //////////////////////////////////////////////////////////////////////// // Create epg grid const CDateTimeSpan blockDuration(0, 0, MINSPERBLOCK, 0); const CDateTimeSpan gridDuration(m_gridEnd - m_gridStart); m_blocks = (gridDuration.GetDays() * 24 * 60 + gridDuration.GetHours() * 60 + gridDuration.GetMinutes()) / MINSPERBLOCK; if (m_blocks >= MAXBLOCKS) m_blocks = MAXBLOCKS; else if (m_blocks < iBlocksPerPage) m_blocks = iBlocksPerPage; m_gridIndex.reserve(m_channelItems.size()); const std::vector<GridItem> blocks(m_blocks); for (size_t channel = 0; channel < m_channelItems.size(); ++channel) { m_gridIndex.emplace_back(blocks); CDateTime gridCursor(m_gridStart); //reset cursor for new channel unsigned long progIdx = m_epgItemsPtr[channel].start; unsigned long lastIdx = m_epgItemsPtr[channel].stop; int iEpgId = m_programmeItems[progIdx]->GetEPGInfoTag()->EpgID(); int itemSize = 1; // size of the programme in blocks int savedBlock = 0; CFileItemPtr item; CPVREpgInfoTagPtr tag; for (int block = 0; block < m_blocks; ++block) { while (progIdx <= lastIdx) { item = m_programmeItems[progIdx]; tag = item->GetEPGInfoTag(); // Note: Start block of an event is start-time-based calculated block + 1, // unless start times matches exactly the begin of a block. if (tag->EpgID() != iEpgId || gridCursor < tag->StartAsUTC() || m_gridEnd <= tag->StartAsUTC()) break; if (gridCursor < tag->EndAsUTC()) { m_gridIndex[channel][block].item = item; m_gridIndex[channel][block].progIndex = progIdx; break; } progIdx++; } gridCursor += blockDuration; if (block == 0) continue; const CFileItemPtr prevItem(m_gridIndex[channel][block - 1].item); const CFileItemPtr currItem(m_gridIndex[channel][block].item); if (block == m_blocks - 1 || prevItem != currItem) { // special handling for last block. int blockDelta = -1; int sizeDelta = 0; if (block == m_blocks - 1 && prevItem == currItem) { itemSize++; blockDelta = 0; sizeDelta = 1; } if (prevItem) { m_gridIndex[channel][savedBlock].item->SetProperty("GenreType", prevItem->GetEPGInfoTag()->GenreType()); } else { CPVREpgInfoTagPtr gapTag(CPVREpgInfoTag::CreateDefaultTag()); gapTag->SetChannel(m_channelItems[channel]->GetPVRChannelInfoTag()); CFileItemPtr gapItem(new CFileItem(gapTag)); for (int i = block + blockDelta; i >= block - itemSize + sizeDelta; --i) { m_gridIndex[channel][i].item = gapItem; } } float fItemWidth = itemSize * fBlockSize; m_gridIndex[channel][savedBlock].originWidth = fItemWidth; m_gridIndex[channel][savedBlock].width = fItemWidth; itemSize = 1; savedBlock = block; // special handling for last block. if (block == m_blocks - 1 && prevItem != currItem) { if (currItem) { m_gridIndex[channel][savedBlock].item->SetProperty("GenreType", currItem->GetEPGInfoTag()->GenreType()); } else { CPVREpgInfoTagPtr gapTag(CPVREpgInfoTag::CreateDefaultTag()); gapTag->SetChannel(m_channelItems[channel]->GetPVRChannelInfoTag()); CFileItemPtr gapItem(new CFileItem(gapTag)); m_gridIndex[channel][block].item = gapItem; } m_gridIndex[channel][savedBlock].originWidth = fBlockSize; // size always 1 block here m_gridIndex[channel][savedBlock].width = fBlockSize; } } else { itemSize++; } } } }
bool CGUIWindowPVRGuideBase::OnMessage(CGUIMessage& message) { bool bReturn = false; switch (message.GetMessage()) { case GUI_MSG_CLICKED: { if (message.GetSenderId() == m_viewControl.GetCurrentControl()) { int iItem = m_viewControl.GetSelectedItem(); if (iItem >= 0 && iItem < m_vecItems->Size()) { CFileItemPtr pItem = m_vecItems->Get(iItem); /* process actions */ switch (message.GetParam1()) { case ACTION_SELECT_ITEM: case ACTION_MOUSE_LEFT_CLICK: switch(CServiceBroker::GetSettings().GetInt(CSettings::SETTING_EPG_SELECTACTION)) { case EPG_SELECT_ACTION_CONTEXT_MENU: OnPopupMenu(iItem); bReturn = true; break; case EPG_SELECT_ACTION_SWITCH: CServiceBroker::GetPVRManager().GUIActions()->SwitchToChannel(pItem, true); bReturn = true; break; case EPG_SELECT_ACTION_PLAY_RECORDING: CServiceBroker::GetPVRManager().GUIActions()->PlayRecording(pItem, true); bReturn = true; break; case EPG_SELECT_ACTION_INFO: CServiceBroker::GetPVRManager().GUIActions()->ShowEPGInfo(pItem); bReturn = true; break; case EPG_SELECT_ACTION_RECORD: CServiceBroker::GetPVRManager().GUIActions()->ToggleTimer(pItem); bReturn = true; break; case EPG_SELECT_ACTION_SMART_SELECT: { const CPVREpgInfoTagPtr tag(pItem->GetEPGInfoTag()); if (tag) { const CDateTime start(tag->StartAsUTC()); const CDateTime end(tag->EndAsUTC()); const CDateTime now(CDateTime::GetUTCDateTime()); if (start <= now && now <= end) { // current event CServiceBroker::GetPVRManager().GUIActions()->SwitchToChannel(pItem, true); } else if (now < start) { // future event if (tag->HasTimer()) CServiceBroker::GetPVRManager().GUIActions()->EditTimer(pItem); else CServiceBroker::GetPVRManager().GUIActions()->AddTimer(pItem, false); } else { // past event if (tag->HasRecording()) CServiceBroker::GetPVRManager().GUIActions()->PlayRecording(pItem, true); else if (tag->IsPlayable()) CServiceBroker::GetPVRManager().GUIActions()->PlayEpgTag(pItem); else CServiceBroker::GetPVRManager().GUIActions()->ShowEPGInfo(pItem); } bReturn = true; } break; } } break; case ACTION_SHOW_INFO: CServiceBroker::GetPVRManager().GUIActions()->ShowEPGInfo(pItem); bReturn = true; break; case ACTION_PLAY: CServiceBroker::GetPVRManager().GUIActions()->PlayRecording(pItem, true); bReturn = true; break; case ACTION_RECORD: CServiceBroker::GetPVRManager().GUIActions()->ToggleTimer(pItem); bReturn = true; break; case ACTION_PVR_SHOW_TIMER_RULE: CServiceBroker::GetPVRManager().GUIActions()->AddTimerRule(pItem, true); bReturn = true; break; case ACTION_CONTEXT_MENU: case ACTION_MOUSE_RIGHT_CLICK: OnPopupMenu(iItem); bReturn = true; break; } } else if (iItem == -1) { /* process actions */ switch (message.GetParam1()) { case ACTION_SELECT_ITEM: case ACTION_MOUSE_LEFT_CLICK: case ACTION_PLAY: { // EPG "gap" selected => switch to associated channel. CGUIEPGGridContainer *epgGridContainer = GetGridControl(); if (epgGridContainer) { const CFileItemPtr item(epgGridContainer->GetSelectedChannelItem()); if (item) { CServiceBroker::GetPVRManager().GUIActions()->SwitchToChannel(item, true); bReturn = true; } } break; } } } } else if (message.GetSenderId() == CONTROL_BTNVIEWASICONS) { // let's set the view mode first before update CGUIWindowPVRBase::OnMessage(message); Refresh(true); bReturn = true; } break; } case GUI_MSG_CHANGE_VIEW_MODE: { // let's set the view mode first before update CGUIWindowPVRBase::OnMessage(message); // force data update for the new view control { CSingleLock lock(m_critSection); m_bRefreshTimelineItems = true; } Init(); Refresh(true); bReturn = true; break; } case GUI_MSG_REFRESH_LIST: switch(message.GetParam1()) { case ObservableMessageChannelGroupsLoaded: { // late init InitChannelGroup(); Init(); break; } case ObservableMessageChannelGroupReset: case ObservableMessageChannelGroup: case ObservableMessageEpg: case ObservableMessageEpgContainer: { Refresh(true); break; } case ObservableMessageTimersReset: case ObservableMessageTimers: { SetInvalid(); break; } } break; } return bReturn || CGUIWindowPVRBase::OnMessage(message); }