diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/network/upnp/UPnPPlayer.cpp | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/xbmc/network/upnp/UPnPPlayer.cpp b/xbmc/network/upnp/UPnPPlayer.cpp new file mode 100644 index 0000000..0f0d52a --- /dev/null +++ b/xbmc/network/upnp/UPnPPlayer.cpp @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2006 elupus (Joakim Plate) + * Copyright (C) 2006-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 "UPnPPlayer.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "ThumbLoader.h" +#include "UPnP.h" +#include "UPnPInternal.h" +#include "application/Application.h" +#include "cores/DataCacheCore.h" +#include "dialogs/GUIDialogBusy.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogHelper.h" +#include "music/MusicThumbLoader.h" +#include "threads/Event.h" +#include "utils/StringUtils.h" +#include "utils/TimeUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoThumbLoader.h" +#include "windowing/WinSystem.h" + +#include <mutex> + +#include <Platinum/Source/Devices/MediaRenderer/PltMediaController.h> +#include <Platinum/Source/Devices/MediaServer/PltDidl.h> +#include <Platinum/Source/Platinum/Platinum.h> + +using namespace KODI::MESSAGING; + +using KODI::MESSAGING::HELPERS::DialogResponse; +using namespace std::chrono_literals; + +NPT_SET_LOCAL_LOGGER("xbmc.upnp.player") + +namespace UPNP +{ + +class CUPnPPlayerController : public PLT_MediaControllerDelegate +{ +public: + CUPnPPlayerController(PLT_MediaController* control, + PLT_DeviceDataReference& device, + IPlayerCallback& callback) + : m_control(control), + m_transport(NULL), + m_device(device), + m_instance(0), + m_callback(callback), + m_postime(0), + m_logger(CServiceBroker::GetLogging().GetLogger("CUPnPPlayerController")) + { + m_posinfo = {}; + m_device->FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", m_transport); + } + + void OnSetAVTransportURIResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override + { + if(NPT_FAILED(res)) + m_logger->error("OnSetAVTransportURIResult failed"); + m_resstatus = res; + m_resevent.Set(); + } + + void OnPlayResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override + { + if(NPT_FAILED(res)) + m_logger->error("OnPlayResult failed"); + m_resstatus = res; + m_resevent.Set(); + } + + void OnStopResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata) override + { + if(NPT_FAILED(res)) + m_logger->error("OnStopResult failed"); + m_resstatus = res; + m_resevent.Set(); + } + + void OnGetMediaInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_MediaInfo* info, void* userdata) override + { + if(NPT_FAILED(res) || info == NULL) + m_logger->error("OnGetMediaInfoResult failed"); + } + + void OnGetTransportInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_TransportInfo* info, void* userdata) override + { + std::unique_lock<CCriticalSection> lock(m_section); + + if(NPT_FAILED(res)) + { + m_logger->error("OnGetTransportInfoResult failed"); + m_trainfo.cur_speed = "0"; + m_trainfo.cur_transport_state = "STOPPED"; + m_trainfo.cur_transport_status = "ERROR_OCCURED"; + } + else + m_trainfo = *info; + m_traevnt.Set(); + } + + void UpdatePositionInfo() + { + if(m_postime == 0 + || m_postime > CTimeUtils::GetFrameTime()) + return; + + m_control->GetTransportInfo(m_device, m_instance, this); + m_control->GetPositionInfo(m_device, m_instance, this); + m_postime = 0; + } + + void OnGetPositionInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_PositionInfo* info, void* userdata) override + { + std::unique_lock<CCriticalSection> lock(m_section); + + if(NPT_FAILED(res) || info == NULL) + { + m_logger->error("OnGetMediaInfoResult failed"); + m_posinfo = PLT_PositionInfo(); + } + else + m_posinfo = *info; + m_postime = CTimeUtils::GetFrameTime() + 500; + m_posevnt.Set(); + } + + + ~CUPnPPlayerController() override = default; + + PLT_MediaController* m_control; + PLT_Service * m_transport; + PLT_DeviceDataReference m_device; + NPT_UInt32 m_instance; + IPlayerCallback& m_callback; + + NPT_Result m_resstatus; + CEvent m_resevent; + + CCriticalSection m_section; + unsigned int m_postime; + + CEvent m_posevnt; + PLT_PositionInfo m_posinfo; + + CEvent m_traevnt; + PLT_TransportInfo m_trainfo; + + Logger m_logger; +}; + +CUPnPPlayer::CUPnPPlayer(IPlayerCallback& callback, const char* uuid) + : IPlayer(callback), + m_control(NULL), + m_delegate(NULL), + m_started(false), + m_stopremote(false), + m_logger(CServiceBroker::GetLogging().GetLogger(StringUtils::Format("CUPnPPlayer[{}]", uuid))) +{ + m_control = CUPnP::GetInstance()->m_MediaController; + + PLT_DeviceDataReference device; + if(NPT_SUCCEEDED(m_control->FindRenderer(uuid, device))) + { + m_delegate = new CUPnPPlayerController(m_control, device, callback); + CUPnP::RegisterUserdata(m_delegate); + } + else + m_logger->error("couldn't find device as {}", uuid); + + CServiceBroker::GetWinSystem()->RegisterRenderLoop(this); +} + +CUPnPPlayer::~CUPnPPlayer() +{ + CServiceBroker::GetWinSystem()->UnregisterRenderLoop(this); + CloseFile(); + CUPnP::UnregisterUserdata(m_delegate); + delete m_delegate; +} + +static NPT_Result WaitOnEvent(CEvent& event, XbmcThreads::EndTime<>& timeout) +{ + if (event.Wait(0ms)) + return NPT_SUCCESS; + + if (!CGUIDialogBusy::WaitOnEvent(event)) + return NPT_FAILURE; + + return NPT_SUCCESS; +} + +int CUPnPPlayer::PlayFile(const CFileItem& file, + const CPlayerOptions& options, + XbmcThreads::EndTime<>& timeout) +{ + CFileItem item(file); + NPT_Reference<CThumbLoader> thumb_loader; + NPT_Reference<PLT_MediaObject> obj; + NPT_String path(file.GetPath().c_str()); + NPT_String tmp, resource; + EMediaControllerQuirks quirks = EMEDIACONTROLLERQUIRKS_NONE; + + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + + if (file.IsVideoDb()) + thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader()); + else if (item.IsMusicDb()) + thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader()); + + obj = BuildObject(item, path, false, thumb_loader, NULL, CUPnP::GetServer(), UPnPPlayer); + if(obj.IsNull()) goto failed; + + NPT_CHECK_LABEL_SEVERE(PLT_Didl::ToDidl(*obj, "", tmp), failed_todidl); + tmp.Insert(didl_header, 0); + tmp.Append(didl_footer); + + quirks = GetMediaControllerQuirks(m_delegate->m_device.AsPointer()); + if (quirks & EMEDIACONTROLLERQUIRKS_X_MKV) + { + for (NPT_Cardinal i=0; i< obj->m_Resources.GetItemCount(); i++) { + if (obj->m_Resources[i].m_ProtocolInfo.GetContentType().Compare("video/x-matroska") == 0) { + m_logger->debug("PlayFile({}): applying video/x-mkv quirk", file.GetPath()); + NPT_String protocolInfo = obj->m_Resources[i].m_ProtocolInfo.ToString(); + protocolInfo.Replace(":video/x-matroska:", ":video/x-mkv:"); + obj->m_Resources[i].m_ProtocolInfo = PLT_ProtocolInfo(protocolInfo); + } + } + } + + /* The resource uri's are stored in the Didl. We must choose the best resource + * for the playback device */ + NPT_Cardinal res_index; + NPT_CHECK_LABEL_SEVERE(m_control->FindBestResource(m_delegate->m_device, *obj, res_index), failed_findbestresource); + + // get the transport info to evaluate the TransportState to be able to + // determine whether we first need to call Stop() + timeout.Set(timeout.GetInitialTimeoutValue()); + NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed_gettransportinfo); + NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout), failed_gettransportinfo); + + if (m_delegate->m_trainfo.cur_transport_state != "NO_MEDIA_PRESENT" && + m_delegate->m_trainfo.cur_transport_state != "STOPPED") + { + timeout.Set(timeout.GetInitialTimeoutValue()); + NPT_CHECK_LABEL_SEVERE(m_control->Stop(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed_stop); + NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout), failed_stop); + NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed_stop); + } + + + timeout.Set(timeout.GetInitialTimeoutValue()); + NPT_CHECK_LABEL_SEVERE(m_control->SetAVTransportURI(m_delegate->m_device + , m_delegate->m_instance + , obj->m_Resources[res_index].m_Uri + , (const char*)tmp + , m_delegate), failed_setavtransporturi); + NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout), failed_setavtransporturi); + NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed_setavtransporturi); + + timeout.Set(timeout.GetInitialTimeoutValue()); + NPT_CHECK_LABEL_SEVERE(m_control->Play(m_delegate->m_device + , m_delegate->m_instance + , "1" + , m_delegate), failed_play); + NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout), failed_play); + NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed_play); + + + /* wait for PLAYING state */ + timeout.Set(timeout.GetInitialTimeoutValue()); + do { + NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed_waitplaying); + + + { + std::unique_lock<CCriticalSection> lock(m_delegate->m_section); + if(m_delegate->m_trainfo.cur_transport_state == "PLAYING" + || m_delegate->m_trainfo.cur_transport_state == "PAUSED_PLAYBACK") + break; + + if(m_delegate->m_trainfo.cur_transport_state == "STOPPED" + && m_delegate->m_trainfo.cur_transport_status != "OK") + { + m_logger->error("OpenFile({}): remote player signalled error", file.GetPath()); + return NPT_FAILURE; + } + } + + NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout), failed_waitplaying); + + } while(!timeout.IsTimePast()); + + if(options.starttime > 0) + { + /* many upnp units won't load file properly until after play (including xbmc) */ + NPT_CHECK_LABEL(m_control->Seek(m_delegate->m_device + , m_delegate->m_instance + , "REL_TIME" + , PLT_Didl::FormatTimeStamp((NPT_UInt32)options.starttime) + , m_delegate), failed_seek); + } + + return NPT_SUCCESS; +failed_todidl: + m_logger->error("PlayFile({}) failed to serialize item into DIDL-Lite", file.GetPath()); + return NPT_FAILURE; +failed_findbestresource: + m_logger->error("PlayFile({}) failed to find a matching resource", file.GetPath()); + return NPT_FAILURE; +failed_gettransportinfo: + m_logger->error("PlayFile({}): call to GetTransportInfo failed", file.GetPath()); + return NPT_FAILURE; +failed_stop: + m_logger->error("PlayFile({}) failed to stop current playback", file.GetPath()); + return NPT_FAILURE; +failed_setavtransporturi: + m_logger->error("PlayFile({}) failed to set the playback URI", file.GetPath()); + return NPT_FAILURE; +failed_play: + m_logger->error("PlayFile({}) failed to start playback", file.GetPath()); + return NPT_FAILURE; +failed_waitplaying: + m_logger->error("PlayFile({}) failed to wait for PLAYING state", file.GetPath()); + return NPT_FAILURE; +failed_seek: + m_logger->error("PlayFile({}) failed to seek to start offset", file.GetPath()); + return NPT_FAILURE; +failed: + m_logger->error("PlayFile({}) failed", file.GetPath()); + return NPT_FAILURE; +} + +bool CUPnPPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options) +{ + XbmcThreads::EndTime<> timeout(10s); + + /* if no path we want to attach to a already playing player */ + if(file.GetPath() == "") + { + NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed); + + NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout), failed); + + /* make sure the attached player is actually playing */ + { + std::unique_lock<CCriticalSection> lock(m_delegate->m_section); + if(m_delegate->m_trainfo.cur_transport_state != "PLAYING" + && m_delegate->m_trainfo.cur_transport_state != "PAUSED_PLAYBACK") + goto failed; + } + } + else + NPT_CHECK_LABEL_SEVERE(PlayFile(file, options, timeout), failed); + + m_stopremote = true; + m_started = true; + m_callback.OnPlayBackStarted(file); + m_callback.OnAVStarted(file); + NPT_CHECK_LABEL_SEVERE(m_control->GetPositionInfo(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed); + NPT_CHECK_LABEL_SEVERE(m_control->GetMediaInfo(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed); + + m_updateTimer.Set(0ms); + + return true; +failed: + m_logger->error("OpenFile({}) failed to open file", file.GetPath()); + return false; +} + +bool CUPnPPlayer::QueueNextFile(const CFileItem& file) +{ + CFileItem item(file); + NPT_Reference<CThumbLoader> thumb_loader; + NPT_Reference<PLT_MediaObject> obj; + NPT_String path(file.GetPath().c_str()); + NPT_String tmp; + + if (file.IsVideoDb()) + thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader()); + else if (item.IsMusicDb()) + thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader()); + + + obj = BuildObject(item, path, false, thumb_loader, NULL, CUPnP::GetServer(), UPnPPlayer); + if(!obj.IsNull()) + { + NPT_CHECK_LABEL_SEVERE(PLT_Didl::ToDidl(*obj, "", tmp), failed); + tmp.Insert(didl_header, 0); + tmp.Append(didl_footer); + } + + NPT_CHECK_LABEL_WARNING(m_control->SetNextAVTransportURI(m_delegate->m_device + , m_delegate->m_instance + , file.GetPath().c_str() + , (const char*)tmp + , m_delegate), failed); + if (!m_delegate->m_resevent.Wait(10000ms)) + goto failed; + NPT_CHECK_LABEL_WARNING(m_delegate->m_resstatus, failed); + return true; + +failed: + m_logger->error("QueueNextFile({}) failed to queue file", file.GetPath()); + return false; +} + +bool CUPnPPlayer::CloseFile(bool reopen) +{ + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + if(m_stopremote) + { + NPT_CHECK_LABEL(m_control->Stop(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed); + if (!m_delegate->m_resevent.Wait(10000ms)) + goto failed; + NPT_CHECK_LABEL(m_delegate->m_resstatus, failed); + } + + if(m_started) + { + m_started = false; + m_callback.OnPlayBackStopped(); + } + + return true; +failed: + m_logger->error("CloseFile - unable to stop playback"); + return false; +} + +void CUPnPPlayer::Pause() +{ + if(IsPaused()) + { + NPT_CHECK_LABEL(m_control->Play(m_delegate->m_device + , m_delegate->m_instance + , "1" + , m_delegate), failed); + CDataCacheCore::GetInstance().SetSpeed(1.0, 1.0); + } + else + { + NPT_CHECK_LABEL(m_control->Pause(m_delegate->m_device + , m_delegate->m_instance + , m_delegate), failed); + CDataCacheCore::GetInstance().SetSpeed(1.0, 0.0); + } + + return; +failed: + m_logger->error("CloseFile - unable to pause/unpause playback"); +} + +void CUPnPPlayer::SeekTime(int64_t ms) +{ + NPT_CHECK_LABEL(m_control->Seek(m_delegate->m_device + , m_delegate->m_instance + , "REL_TIME", PLT_Didl::FormatTimeStamp((NPT_UInt32)(ms / 1000)) + , m_delegate), failed); + + CDataCacheCore::GetInstance().SeekFinished(0); + return; +failed: + m_logger->error("SeekTime - unable to seek playback"); +} + +float CUPnPPlayer::GetPercentage() +{ + int64_t tot = GetTotalTime(); + if(tot) + return 100.0f * GetTime() / tot; + else + return 0.0f; +} + +void CUPnPPlayer::SeekPercentage(float percent) +{ + int64_t tot = GetTotalTime(); + if (tot) + SeekTime((int64_t)(tot * percent / 100)); +} + +void CUPnPPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) +{ +} + +void CUPnPPlayer::DoAudioWork() +{ + NPT_String data; + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + m_delegate->UpdatePositionInfo(); + + if(m_started) { + NPT_String uri, meta; + NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("CurrentTrackURI", uri), failed); + NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("CurrentTrackMetadata", meta), failed); + + if(m_current_uri != (const char*)uri + || m_current_meta != (const char*)meta) { + m_current_uri = (const char*)uri; + m_current_meta = (const char*)meta; + CFileItemPtr item = GetFileItem(uri, meta); + g_application.CurrentFileItem() = *item; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_UPDATE_CURRENT_ITEM, 0, -1, + static_cast<void*>(new CFileItem(*item))); + } + + NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("TransportState", data), failed); + if(data == "STOPPED") + { + m_started = false; + m_callback.OnPlayBackEnded(); + } + } + return; +failed: + return; +} + +bool CUPnPPlayer::IsPlaying() const +{ + NPT_String data; + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("TransportState", data), failed); + return data != "STOPPED"; +failed: + return false; +} + +bool CUPnPPlayer::IsPaused() const +{ + NPT_String data; + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("TransportState", data), failed); + return data == "PAUSED_PLAYBACK"; +failed: + return false; +} + +void CUPnPPlayer::SetVolume(float volume) +{ + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + NPT_CHECK_LABEL(m_control->SetVolume(m_delegate->m_device + , m_delegate->m_instance + , "Master", (int)(volume * 100) + , m_delegate), failed); + return; +failed: + m_logger->error("- unable to set volume"); +} + +int64_t CUPnPPlayer::GetTime() +{ + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + return m_delegate->m_posinfo.rel_time.ToMillis(); +failed: + return 0; +} + +int64_t CUPnPPlayer::GetTotalTime() +{ + NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); + return m_delegate->m_posinfo.track_duration.ToMillis(); +failed: + return 0; +}; + +bool CUPnPPlayer::OnAction(const CAction &action) +{ + switch (action.GetID()) + { + case ACTION_STOP: + if(IsPlaying()) + { + //stop on remote system + m_stopremote = HELPERS::ShowYesNoDialogText(CVariant{37022}, CVariant{37023}) == + DialogResponse::CHOICE_YES; + + return false; /* let normal code handle the action */ + } + [[fallthrough]]; + default: + return false; + } +} + +void CUPnPPlayer::SetSpeed(float speed) +{ + +} + +void CUPnPPlayer::FrameMove() +{ + if (m_updateTimer.IsTimePast()) + { + CDataCacheCore::GetInstance().SetPlayTimes(0, GetTime(), 0, GetTotalTime()); + m_updateTimer.Set(500ms); + } +} + +} /* namespace UPNP */ |