summaryrefslogtreecommitdiffstats
path: root/xbmc/network/upnp/UPnPPlayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/network/upnp/UPnPPlayer.cpp625
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 */