/* * 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 #include using namespace PVR; class CPVRPlaybackState::CLastWatchedUpdateTimer : public CTimer, private ITimerCallback { public: CLastWatchedUpdateTimer(CPVRPlaybackState& state, const std::shared_ptr& 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 m_channel; const CDateTime m_time; }; CPVRPlaybackState::CPVRPlaybackState() = default; CPVRPlaybackState::~CPVRPlaybackState() = default; void CPVRPlaybackState::ReInit() { std::unique_lock lock(m_critSection); Clear(); if (m_playingClientId != -1) { if (m_playingChannelUniqueId != -1) { const std::shared_ptr 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 epg = CServiceBroker::GetPVRManager().EpgContainer().GetByChannelUid( m_playingClientId, m_playingEpgTagChannelUniqueId); if (epg) m_playingEpgTag = epg->GetTagByBroadcastId(m_playingEpgTagUniqueId); } } const std::shared_ptr 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 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 lock(m_critSection); m_playingChannel.reset(); m_playingRecording.reset(); m_playingEpgTag.reset(); ClearData(); if (item.HasPVRChannelGroupMemberInfoTag()) { const std::shared_ptr 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 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 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 lock(m_critSection); return m_playingChannel != nullptr || m_playingRecording != nullptr || m_playingEpgTag != nullptr; } bool CPVRPlaybackState::IsPlayingTV() const { std::unique_lock lock(m_critSection); return m_playingChannel && !m_playingChannel->Channel()->IsRadio(); } bool CPVRPlaybackState::IsPlayingRadio() const { std::unique_lock lock(m_critSection); return m_playingChannel && m_playingChannel->Channel()->IsRadio(); } bool CPVRPlaybackState::IsPlayingEncryptedChannel() const { std::unique_lock lock(m_critSection); return m_playingChannel && m_playingChannel->Channel()->IsEncrypted(); } bool CPVRPlaybackState::IsPlayingRecording() const { std::unique_lock lock(m_critSection); return m_playingRecording != nullptr; } bool CPVRPlaybackState::IsPlayingEpgTag() const { std::unique_lock lock(m_critSection); return m_playingEpgTag != nullptr; } bool CPVRPlaybackState::IsPlayingChannel(int iClientID, int iUniqueChannelID) const { std::unique_lock lock(m_critSection); return m_playingClientId == iClientID && m_playingChannelUniqueId == iUniqueChannelID; } bool CPVRPlaybackState::IsPlayingChannel(const std::shared_ptr& channel) const { if (channel) { const std::shared_ptr current = GetPlayingChannel(); if (current && current->ClientID() == channel->ClientID() && current->UniqueID() == channel->UniqueID()) return true; } return false; } bool CPVRPlaybackState::IsPlayingRecording(const std::shared_ptr& recording) const { if (recording) { const std::shared_ptr current = GetPlayingRecording(); if (current && current->ClientID() == recording->ClientID() && current->ClientRecordingID() == recording->ClientRecordingID()) return true; } return false; } bool CPVRPlaybackState::IsPlayingEpgTag(const std::shared_ptr& epgTag) const { if (epgTag) { const std::shared_ptr current = GetPlayingEpgTag(); if (current && current->ClientID() == epgTag->ClientID() && current->UniqueChannelID() == epgTag->UniqueChannelID() && current->UniqueBroadcastID() == epgTag->UniqueBroadcastID()) return true; } return false; } std::shared_ptr CPVRPlaybackState::GetPlayingChannel() const { std::unique_lock lock(m_critSection); return m_playingChannel ? m_playingChannel->Channel() : std::shared_ptr(); } std::shared_ptr CPVRPlaybackState::GetPlayingChannelGroupMember() const { std::unique_lock lock(m_critSection); return m_playingChannel; } std::shared_ptr CPVRPlaybackState::GetPlayingRecording() const { std::unique_lock lock(m_critSection); return m_playingRecording; } std::shared_ptr CPVRPlaybackState::GetPlayingEpgTag() const { std::unique_lock lock(m_critSection); return m_playingEpgTag; } int CPVRPlaybackState::GetPlayingChannelUniqueID() const { std::unique_lock lock(m_critSection); return m_playingChannelUniqueId; } std::string CPVRPlaybackState::GetPlayingClientName() const { std::unique_lock lock(m_critSection); return m_strPlayingClientName; } int CPVRPlaybackState::GetPlayingClientID() const { std::unique_lock lock(m_critSection); return m_playingClientId; } bool CPVRPlaybackState::IsRecording() const { return CServiceBroker::GetPVRManager().Timers()->IsRecording(); } bool CPVRPlaybackState::IsRecordingOnPlayingChannel() const { const std::shared_ptr currentChannel = GetPlayingChannel(); return currentChannel && CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*currentChannel); } bool CPVRPlaybackState::IsPlayingActiveRecording() const { const std::shared_ptr currentRecording = GetPlayingRecording(); return currentRecording && currentRecording->IsInProgress(); } bool CPVRPlaybackState::CanRecordOnPlayingChannel() const { const std::shared_ptr currentChannel = GetPlayingChannel(); return currentChannel && currentChannel->CanRecord(); } void CPVRPlaybackState::SetActiveChannelGroup(const std::shared_ptr& 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(duration).count(); group->SetLastOpened(tsMillis); } } void CPVRPlaybackState::SetActiveChannelGroup( const std::shared_ptr& channel) { const bool bRadio = channel->Channel()->IsRadio(); const std::shared_ptr group = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)->GetById(channel->GroupID()); SetActiveChannelGroup(group); } namespace { std::shared_ptr GetFirstNonDeletedAndNonHiddenChannelGroup(bool bRadio) { CPVRChannelGroups* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio); if (groups) { const std::vector> 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 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(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(this)->SetActiveChannelGroup(group); } return m_activeGroupTV; } } CDateTime CPVRPlaybackState::GetPlaybackTime(int iClientID, int iUniqueChannelID) const { const std::shared_ptr 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& 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 group = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)->GetById(channel->GroupID()); if (group) group->SetLastWatched(iTime); } std::shared_ptr CPVRPlaybackState::GetLastPlayedChannelGroupMember( bool bRadio) const { std::unique_lock lock(m_critSection); return bRadio ? m_lastPlayedChannelRadio : m_lastPlayedChannelTV; } std::shared_ptr CPVRPlaybackState:: GetPreviousToLastPlayedChannelGroupMember(bool bRadio) const { std::unique_lock lock(m_critSection); return bRadio ? m_previousToLastPlayedChannelRadio : m_previousToLastPlayedChannelTV; }