summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/PVRPlaybackState.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/pvr/PVRPlaybackState.cpp566
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;
+}