diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/pvr/PVRPlaybackState.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp new file mode 100644 index 0000000..56bbd5b --- /dev/null +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PVRPlaybackState.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "XBDateTime.h" +#include "cores/DataCacheCore.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroup.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimers.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "threads/Timer.h" +#include "utils/log.h" + +#include <algorithm> +#include <mutex> + +using namespace PVR; + +class CPVRPlaybackState::CLastWatchedUpdateTimer : public CTimer, private ITimerCallback +{ +public: + CLastWatchedUpdateTimer(CPVRPlaybackState& state, + const std::shared_ptr<CPVRChannelGroupMember>& channel, + const CDateTime& time) + : CTimer(this), m_state(state), m_channel(channel), m_time(time) + { + } + + // ITimerCallback implementation + void OnTimeout() override { m_state.UpdateLastWatched(m_channel, m_time); } + +private: + CLastWatchedUpdateTimer() = delete; + + CPVRPlaybackState& m_state; + const std::shared_ptr<CPVRChannelGroupMember> m_channel; + const CDateTime m_time; +}; + +CPVRPlaybackState::CPVRPlaybackState() = default; + +CPVRPlaybackState::~CPVRPlaybackState() = default; + +void CPVRPlaybackState::ReInit() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + Clear(); + + if (m_playingClientId != -1) + { + if (m_playingChannelUniqueId != -1) + { + const std::shared_ptr<CPVRChannelGroup> group = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByIdFromAll(m_playingGroupId); + if (group) + m_playingChannel = group->GetByUniqueID({m_playingClientId, m_playingChannelUniqueId}); + else + CLog::LogFC(LOGERROR, LOGPVR, "Failed to obtain group with id '{}'", m_playingGroupId); + } + else if (!m_strPlayingRecordingUniqueId.empty()) + { + m_playingRecording = CServiceBroker::GetPVRManager().Recordings()->GetById( + m_playingClientId, m_strPlayingRecordingUniqueId); + } + else if (m_playingEpgTagChannelUniqueId != -1 && m_playingEpgTagUniqueId != 0) + { + const std::shared_ptr<CPVREpg> epg = + CServiceBroker::GetPVRManager().EpgContainer().GetByChannelUid( + m_playingClientId, m_playingEpgTagChannelUniqueId); + if (epg) + m_playingEpgTag = epg->GetTagByBroadcastId(m_playingEpgTagUniqueId); + } + } + + const std::shared_ptr<CPVRChannelGroupsContainer> groups = + CServiceBroker::GetPVRManager().ChannelGroups(); + const CPVRChannelGroups* groupsTV = groups->GetTV(); + const CPVRChannelGroups* groupsRadio = groups->GetRadio(); + + m_activeGroupTV = groupsTV->GetLastOpenedGroup(); + m_activeGroupRadio = groupsRadio->GetLastOpenedGroup(); + if (!m_activeGroupTV) + m_activeGroupTV = groupsTV->GetGroupAll(); + if (!m_activeGroupRadio) + m_activeGroupRadio = groupsRadio->GetGroupAll(); + + GroupMemberPair lastPlayed = groupsTV->GetLastAndPreviousToLastPlayedChannelGroupMember(); + m_lastPlayedChannelTV = lastPlayed.first; + m_previousToLastPlayedChannelTV = lastPlayed.second; + + lastPlayed = groupsRadio->GetLastAndPreviousToLastPlayedChannelGroupMember(); + m_lastPlayedChannelRadio = lastPlayed.first; + m_previousToLastPlayedChannelRadio = lastPlayed.second; +} + +void CPVRPlaybackState::ClearData() +{ + m_playingGroupId = -1; + m_playingChannelUniqueId = -1; + m_strPlayingRecordingUniqueId.clear(); + m_playingEpgTagChannelUniqueId = -1; + m_playingEpgTagUniqueId = 0; + m_playingClientId = -1; + m_strPlayingClientName.clear(); +} + +void CPVRPlaybackState::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_playingChannel.reset(); + m_playingRecording.reset(); + m_playingEpgTag.reset(); + m_lastPlayedChannelTV.reset(); + m_lastPlayedChannelRadio.reset(); + m_previousToLastPlayedChannelTV.reset(); + m_previousToLastPlayedChannelRadio.reset(); + m_lastWatchedUpdateTimer.reset(); + m_activeGroupTV.reset(); + m_activeGroupRadio.reset(); +} + +void CPVRPlaybackState::OnPlaybackStarted(const CFileItem& item) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_playingChannel.reset(); + m_playingRecording.reset(); + m_playingEpgTag.reset(); + ClearData(); + + if (item.HasPVRChannelGroupMemberInfoTag()) + { + const std::shared_ptr<CPVRChannelGroupMember> channel = item.GetPVRChannelGroupMemberInfoTag(); + + m_playingChannel = channel; + m_playingGroupId = m_playingChannel->GroupID(); + m_playingClientId = m_playingChannel->Channel()->ClientID(); + m_playingChannelUniqueId = m_playingChannel->Channel()->UniqueID(); + + SetActiveChannelGroup(channel); + + if (channel->Channel()->IsRadio()) + { + if (m_lastPlayedChannelRadio != channel) + { + m_previousToLastPlayedChannelRadio = m_lastPlayedChannelRadio; + m_lastPlayedChannelRadio = channel; + } + } + else + { + if (m_lastPlayedChannelTV != channel) + { + m_previousToLastPlayedChannelTV = m_lastPlayedChannelTV; + m_lastPlayedChannelTV = channel; + } + } + + int iLastWatchedDelay = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_PVRPLAYBACK_DELAYMARKLASTWATCHED) * + 1000; + if (iLastWatchedDelay > 0) + { + // Insert new / replace existing last watched update timer + if (m_lastWatchedUpdateTimer) + m_lastWatchedUpdateTimer->Stop(true); + + m_lastWatchedUpdateTimer.reset( + new CLastWatchedUpdateTimer(*this, channel, CDateTime::GetUTCDateTime())); + m_lastWatchedUpdateTimer->Start(std::chrono::milliseconds(iLastWatchedDelay)); + } + else + { + // Store last watched timestamp immediately + UpdateLastWatched(channel, CDateTime::GetUTCDateTime()); + } + } + else if (item.HasPVRRecordingInfoTag()) + { + m_playingRecording = item.GetPVRRecordingInfoTag(); + m_playingClientId = m_playingRecording->ClientID(); + m_strPlayingRecordingUniqueId = m_playingRecording->ClientRecordingID(); + } + else if (item.HasEPGInfoTag()) + { + m_playingEpgTag = item.GetEPGInfoTag(); + m_playingClientId = m_playingEpgTag->ClientID(); + m_playingEpgTagChannelUniqueId = m_playingEpgTag->UniqueChannelID(); + m_playingEpgTagUniqueId = m_playingEpgTag->UniqueBroadcastID(); + } + else if (item.HasPVRChannelInfoTag()) + { + CLog::LogFC(LOGERROR, LOGPVR, "Channel item without channel group member!"); + } + + if (m_playingClientId != -1) + { + const std::shared_ptr<CPVRClient> client = + CServiceBroker::GetPVRManager().GetClient(m_playingClientId); + if (client) + m_strPlayingClientName = client->GetFriendlyName(); + } +} + +bool CPVRPlaybackState::OnPlaybackStopped(const CFileItem& item) +{ + // Playback ended due to user interaction + + std::unique_lock<CCriticalSection> lock(m_critSection); + + bool bChanged = false; + + if (item.HasPVRChannelGroupMemberInfoTag() && + item.GetPVRChannelGroupMemberInfoTag()->Channel()->ClientID() == m_playingClientId && + item.GetPVRChannelGroupMemberInfoTag()->Channel()->UniqueID() == m_playingChannelUniqueId) + { + bool bUpdateLastWatched = true; + + if (m_lastWatchedUpdateTimer) + { + if (m_lastWatchedUpdateTimer->IsRunning()) + { + // If last watched timer is still running, cancel it. Channel was not watched long enough to store the value. + m_lastWatchedUpdateTimer->Stop(true); + bUpdateLastWatched = false; + } + m_lastWatchedUpdateTimer.reset(); + } + + if (bUpdateLastWatched) + { + // If last watched timer is not running (any more), channel was watched long enough to store the value. + UpdateLastWatched(m_playingChannel, CDateTime::GetUTCDateTime()); + } + + bChanged = true; + m_playingChannel.reset(); + ClearData(); + } + else if (item.HasPVRRecordingInfoTag() && + item.GetPVRRecordingInfoTag()->ClientID() == m_playingClientId && + item.GetPVRRecordingInfoTag()->ClientRecordingID() == m_strPlayingRecordingUniqueId) + { + bChanged = true; + m_playingRecording.reset(); + ClearData(); + } + else if (item.HasEPGInfoTag() && item.GetEPGInfoTag()->ClientID() == m_playingClientId && + item.GetEPGInfoTag()->UniqueChannelID() == m_playingEpgTagChannelUniqueId && + item.GetEPGInfoTag()->UniqueBroadcastID() == m_playingEpgTagUniqueId) + { + bChanged = true; + m_playingEpgTag.reset(); + ClearData(); + } + else if (item.HasPVRChannelInfoTag()) + { + CLog::LogFC(LOGERROR, LOGPVR, "Channel item without channel group member!"); + } + + return bChanged; +} + +void CPVRPlaybackState::OnPlaybackEnded(const CFileItem& item) +{ + // Playback ended, but not due to user interaction + OnPlaybackStopped(item); +} + +bool CPVRPlaybackState::IsPlaying() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannel != nullptr || m_playingRecording != nullptr || m_playingEpgTag != nullptr; +} + +bool CPVRPlaybackState::IsPlayingTV() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannel && !m_playingChannel->Channel()->IsRadio(); +} + +bool CPVRPlaybackState::IsPlayingRadio() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannel && m_playingChannel->Channel()->IsRadio(); +} + +bool CPVRPlaybackState::IsPlayingEncryptedChannel() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannel && m_playingChannel->Channel()->IsEncrypted(); +} + +bool CPVRPlaybackState::IsPlayingRecording() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingRecording != nullptr; +} + +bool CPVRPlaybackState::IsPlayingEpgTag() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingEpgTag != nullptr; +} + +bool CPVRPlaybackState::IsPlayingChannel(int iClientID, int iUniqueChannelID) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingClientId == iClientID && m_playingChannelUniqueId == iUniqueChannelID; +} + +bool CPVRPlaybackState::IsPlayingChannel(const std::shared_ptr<CPVRChannel>& channel) const +{ + if (channel) + { + const std::shared_ptr<CPVRChannel> current = GetPlayingChannel(); + if (current && current->ClientID() == channel->ClientID() && + current->UniqueID() == channel->UniqueID()) + return true; + } + + return false; +} + +bool CPVRPlaybackState::IsPlayingRecording(const std::shared_ptr<CPVRRecording>& recording) const +{ + if (recording) + { + const std::shared_ptr<CPVRRecording> current = GetPlayingRecording(); + if (current && current->ClientID() == recording->ClientID() && + current->ClientRecordingID() == recording->ClientRecordingID()) + return true; + } + + return false; +} + +bool CPVRPlaybackState::IsPlayingEpgTag(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const +{ + if (epgTag) + { + const std::shared_ptr<CPVREpgInfoTag> current = GetPlayingEpgTag(); + if (current && current->ClientID() == epgTag->ClientID() && + current->UniqueChannelID() == epgTag->UniqueChannelID() && + current->UniqueBroadcastID() == epgTag->UniqueBroadcastID()) + return true; + } + + return false; +} + +std::shared_ptr<CPVRChannel> CPVRPlaybackState::GetPlayingChannel() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannel ? m_playingChannel->Channel() : std::shared_ptr<CPVRChannel>(); +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRPlaybackState::GetPlayingChannelGroupMember() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannel; +} + +std::shared_ptr<CPVRRecording> CPVRPlaybackState::GetPlayingRecording() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingRecording; +} + +std::shared_ptr<CPVREpgInfoTag> CPVRPlaybackState::GetPlayingEpgTag() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingEpgTag; +} + +int CPVRPlaybackState::GetPlayingChannelUniqueID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingChannelUniqueId; +} + +std::string CPVRPlaybackState::GetPlayingClientName() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_strPlayingClientName; +} + +int CPVRPlaybackState::GetPlayingClientID() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_playingClientId; +} + +bool CPVRPlaybackState::IsRecording() const +{ + return CServiceBroker::GetPVRManager().Timers()->IsRecording(); +} + +bool CPVRPlaybackState::IsRecordingOnPlayingChannel() const +{ + const std::shared_ptr<CPVRChannel> currentChannel = GetPlayingChannel(); + return currentChannel && + CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*currentChannel); +} + +bool CPVRPlaybackState::IsPlayingActiveRecording() const +{ + const std::shared_ptr<CPVRRecording> currentRecording = GetPlayingRecording(); + return currentRecording && currentRecording->IsInProgress(); +} + +bool CPVRPlaybackState::CanRecordOnPlayingChannel() const +{ + const std::shared_ptr<CPVRChannel> currentChannel = GetPlayingChannel(); + return currentChannel && currentChannel->CanRecord(); +} + +void CPVRPlaybackState::SetActiveChannelGroup(const std::shared_ptr<CPVRChannelGroup>& group) +{ + if (group) + { + if (group->IsRadio()) + m_activeGroupRadio = group; + else + m_activeGroupTV = group; + + auto duration = std::chrono::system_clock::now().time_since_epoch(); + uint64_t tsMillis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + group->SetLastOpened(tsMillis); + } +} + +void CPVRPlaybackState::SetActiveChannelGroup( + const std::shared_ptr<CPVRChannelGroupMember>& channel) +{ + const bool bRadio = channel->Channel()->IsRadio(); + const std::shared_ptr<CPVRChannelGroup> group = + CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)->GetById(channel->GroupID()); + + SetActiveChannelGroup(group); +} + +namespace +{ +std::shared_ptr<CPVRChannelGroup> GetFirstNonDeletedAndNonHiddenChannelGroup(bool bRadio) +{ + CPVRChannelGroups* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio); + if (groups) + { + const std::vector<std::shared_ptr<CPVRChannelGroup>> members = + groups->GetMembers(true); // exclude hidden + + const auto it = std::find_if(members.cbegin(), members.cend(), + [](const auto& group) { return !group->IsDeleted(); }); + if (it != members.cend()) + return (*it); + } + + CLog::LogFC(LOGERROR, LOGPVR, "Failed to obtain any non-deleted and non-hidden group"); + return {}; +} +} // unnamed namespace + +std::shared_ptr<CPVRChannelGroup> CPVRPlaybackState::GetActiveChannelGroup(bool bRadio) const +{ + if (bRadio) + { + if (m_activeGroupRadio && (m_activeGroupRadio->IsDeleted() || m_activeGroupRadio->IsHidden())) + { + // switch to first non-deleted and non-hidden group + const auto group = GetFirstNonDeletedAndNonHiddenChannelGroup(bRadio); + if (group) + const_cast<CPVRPlaybackState*>(this)->SetActiveChannelGroup(group); + } + return m_activeGroupRadio; + } + else + { + if (m_activeGroupTV && (m_activeGroupTV->IsDeleted() || m_activeGroupTV->IsHidden())) + { + // switch to first non-deleted and non-hidden group + const auto group = GetFirstNonDeletedAndNonHiddenChannelGroup(bRadio); + if (group) + const_cast<CPVRPlaybackState*>(this)->SetActiveChannelGroup(group); + } + return m_activeGroupTV; + } +} + +CDateTime CPVRPlaybackState::GetPlaybackTime(int iClientID, int iUniqueChannelID) const +{ + const std::shared_ptr<CPVREpgInfoTag> epgTag = GetPlayingEpgTag(); + if (epgTag && iClientID == epgTag->ClientID() && iUniqueChannelID == epgTag->UniqueChannelID()) + { + // playing an epg tag on requested channel + return epgTag->StartAsUTC() + + CDateTimeSpan(0, 0, 0, CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000); + } + + // not playing / playing live / playing timeshifted + return GetChannelPlaybackTime(iClientID, iUniqueChannelID); +} + +CDateTime CPVRPlaybackState::GetChannelPlaybackTime(int iClientID, int iUniqueChannelID) const +{ + if (IsPlayingChannel(iClientID, iUniqueChannelID)) + { + // start time valid? + time_t startTime = CServiceBroker::GetDataCacheCore().GetStartTime(); + if (startTime > 0) + return CDateTime(startTime + CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000); + } + + // not playing / playing live + return CDateTime::GetUTCDateTime(); +} + +void CPVRPlaybackState::UpdateLastWatched(const std::shared_ptr<CPVRChannelGroupMember>& channel, + const CDateTime& time) +{ + time_t iTime; + time.GetAsTime(iTime); + + channel->Channel()->SetLastWatched(iTime); + + // update last watched timestamp for group + const bool bRadio = channel->Channel()->IsRadio(); + const std::shared_ptr<CPVRChannelGroup> group = + CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)->GetById(channel->GroupID()); + if (group) + group->SetLastWatched(iTime); +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRPlaybackState::GetLastPlayedChannelGroupMember( + bool bRadio) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return bRadio ? m_lastPlayedChannelRadio : m_lastPlayedChannelTV; +} + +std::shared_ptr<CPVRChannelGroupMember> CPVRPlaybackState:: + GetPreviousToLastPlayedChannelGroupMember(bool bRadio) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return bRadio ? m_previousToLastPlayedChannelRadio : m_previousToLastPlayedChannelTV; +} |