2 * UPnP Support for XBMC
3 * Copyright (c) 2006 c0diq (Sylvain Rebaud)
4 * Portions Copyright (c) by the authors of libPlatinum
5 * http://www.plutinosoft.com/blog/category/platinum/
6 * Copyright (C) 2006-2013 Team XBMC
9 * This Program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2, or (at your option)
14 * This Program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with XBMC; see the file COPYING. If not, see
21 * <http://www.gnu.org/licenses/>.
25 #include "threads/SystemClock.h"
27 #include "UPnPInternal.h"
28 #include "UPnPRenderer.h"
29 #include "UPnPServer.h"
30 #include "UPnPSettings.h"
31 #include "utils/URIUtils.h"
32 #include "Application.h"
33 #include "ApplicationMessenger.h"
34 #include "network/Network.h"
35 #include "utils/log.h"
38 #include "profiles/ProfilesManager.h"
39 #include "settings/Settings.h"
40 #include "GUIUserMessages.h"
42 #include "guilib/GUIWindowManager.h"
43 #include "GUIInfoManager.h"
44 #include "utils/TimeUtils.h"
45 #include "video/VideoInfoTag.h"
46 #include "guilib/Key.h"
52 NPT_SET_LOCAL_LOGGER("xbmc.upnp")
54 #define UPNP_DEFAULT_MAX_RETURNED_ITEMS 200
55 #define UPNP_DEFAULT_MIN_RETURNED_ITEMS 30
61 DLNA_ORG_PS = 'DLNA.ORG_PS'
64 # Convertion Indicator
67 DLNA_ORG_CI = 'DLNA.ORG_CI'
71 # 00 not time seek range, not range
73 # 10 time seek range supported
75 DLNA_ORG_OP = 'DLNA.ORG_OP'
76 DLNA_ORG_OP_VAL = '01'
79 # senderPaced 80000000 31
80 # lsopTimeBasedSeekSupported 40000000 30
81 # lsopByteBasedSeekSupported 20000000 29
82 # playcontainerSupported 10000000 28
83 # s0IncreasingSupported 08000000 27
84 # sNIncreasingSupported 04000000 26
85 # rtspPauseSupported 02000000 25
86 # streamingTransferModeSupported 01000000 24
87 # interactiveTransferModeSupported 00800000 23
88 # backgroundTransferModeSupported 00400000 22
89 # connectionStallingSupported 00200000 21
90 # dlnaVersion15Supported 00100000 20
91 DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
92 DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
95 /*----------------------------------------------------------------------
97 +---------------------------------------------------------------------*/
99 NPT_Console::Output(const char* message)
101 CLog::Log(LOGDEBUG, "%s", message);
107 /*----------------------------------------------------------------------
109 +---------------------------------------------------------------------*/
110 CUPnP* CUPnP::upnp = NULL;
111 static NPT_List<void*> g_UserData;
112 static NPT_Mutex g_UserDataLock;
114 /*----------------------------------------------------------------------
115 | CDeviceHostReferenceHolder class
116 +---------------------------------------------------------------------*/
117 class CDeviceHostReferenceHolder
120 PLT_DeviceHostReference m_Device;
123 /*----------------------------------------------------------------------
124 | CCtrlPointReferenceHolder class
125 +---------------------------------------------------------------------*/
126 class CCtrlPointReferenceHolder
129 PLT_CtrlPointReference m_CtrlPoint;
132 /*----------------------------------------------------------------------
134 +---------------------------------------------------------------------*/
135 class CUPnPCleaner : public NPT_Thread
138 CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
146 /*----------------------------------------------------------------------
147 | CMediaBrowser class
148 +---------------------------------------------------------------------*/
149 class CMediaBrowser : public PLT_SyncMediaBrowser,
150 public PLT_MediaContainerChangesListener
153 CMediaBrowser(PLT_CtrlPointReference& ctrlPoint)
154 : PLT_SyncMediaBrowser(ctrlPoint, true)
156 SetContainerListener(this);
159 // PLT_MediaBrowser methods
160 virtual bool OnMSAdded(PLT_DeviceDataReference& device)
162 CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
163 message.SetStringParam("upnp://");
164 g_windowManager.SendThreadMessage(message);
166 return PLT_SyncMediaBrowser::OnMSAdded(device);
168 virtual void OnMSRemoved(PLT_DeviceDataReference& device)
170 PLT_SyncMediaBrowser::OnMSRemoved(device);
172 CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
173 message.SetStringParam("upnp://");
174 g_windowManager.SendThreadMessage(message);
176 PLT_SyncMediaBrowser::OnMSRemoved(device);
179 // PLT_MediaContainerChangesListener methods
180 virtual void OnContainerChanged(PLT_DeviceDataReference& device,
182 const char* update_id)
184 NPT_String path = "upnp://"+device->GetUUID()+"/";
185 if (!NPT_StringsEqual(item_id, "0")) {
186 CStdString id = item_id;
188 URIUtils::AddSlashAtEnd(id);
192 CLog::Log(LOGDEBUG, "UPNP: notfified container update %s", (const char*)path);
193 CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
194 message.SetStringParam(path.GetChars());
195 g_windowManager.SendThreadMessage(message);
198 bool MarkWatched(const CFileItem& item, const bool watched)
201 CFileItem temp(item);
202 temp.SetProperty("original_listitem_url", item.GetPath());
203 return SaveFileState(temp, CBookmark(), watched);
206 CLog::Log(LOGDEBUG, "UPNP: Marking video item %s as watched", item.GetPath().c_str());
207 return InvokeUpdateObject(item.GetPath().c_str(), "<upnp:playCount>1</upnp:playCount>", "<upnp:playCount>0</upnp:playCount>");
211 bool SaveFileState(const CFileItem& item, const CBookmark& bookmark, const bool updatePlayCount)
213 string path = item.GetProperty("original_listitem_url").asString();
214 if (!item.HasVideoInfoTag() || path.empty()) {
218 NPT_String curr_value;
219 NPT_String new_value;
221 if (item.GetVideoInfoTag()->m_resumePoint.timeInSeconds != bookmark.timeInSeconds) {
222 CLog::Log(LOGDEBUG, "UPNP: Updating resume point for item %s", path.c_str());
223 long time = (long)bookmark.timeInSeconds;
224 if (time < 0) time = 0;
225 curr_value.Append(NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>",
226 (long)item.GetVideoInfoTag()->m_resumePoint.timeInSeconds));
227 new_value.Append(NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>", time));
229 if (updatePlayCount) {
230 CLog::Log(LOGDEBUG, "UPNP: Marking video item %s as watched", path.c_str());
231 if (!curr_value.IsEmpty()) curr_value.Append(",");
232 if (!new_value.IsEmpty()) new_value.Append(",");
233 curr_value.Append("<upnp:playCount>0</upnp:playCount>");
234 new_value.Append("<upnp:playCount>1</upnp:playCount>");
237 return InvokeUpdateObject(path.c_str(), (const char*)curr_value, (const char*)new_value);
240 bool InvokeUpdateObject(const char* id, const char* curr_value, const char* new_value)
243 PLT_DeviceDataReference device;
245 PLT_ActionReference action;
247 CLog::Log(LOGDEBUG, "UPNP: attempting to invoke UpdateObject for %s", id);
249 // check this server supports UpdateObject action
250 NPT_CHECK_LABEL(FindServer(url.GetHostName().c_str(), device),failed);
251 NPT_CHECK_LABEL(device->FindServiceById("urn:upnp-org:serviceId:ContentDirectory", cds),failed);
253 NPT_CHECK_SEVERE(m_CtrlPoint->CreateAction(
255 "urn:schemas-upnp-org:service:ContentDirectory:1",
259 NPT_CHECK_LABEL(action->SetArgumentValue("ObjectID", url.GetFileName().c_str()), failed);
260 NPT_CHECK_LABEL(action->SetArgumentValue("CurrentTagValue", curr_value), failed);
261 NPT_CHECK_LABEL(action->SetArgumentValue("NewTagValue", new_value), failed);
263 NPT_CHECK_LABEL(m_CtrlPoint->InvokeAction(action, NULL),failed);
265 CLog::Log(LOGDEBUG, "UPNP: invoked UpdateObject successfully");
269 CLog::Log(LOGINFO, "UPNP: invoking UpdateObject failed");
275 /*----------------------------------------------------------------------
276 | CMediaController class
277 +---------------------------------------------------------------------*/
278 class CMediaController
279 : public PLT_MediaControllerDelegate
280 , public PLT_MediaController
283 CMediaController(PLT_CtrlPointReference& ctrl_point)
284 : PLT_MediaController(ctrl_point)
286 PLT_MediaController::SetDelegate(this);
293 #define CHECK_USERDATA_RETURN(userdata) do { \
294 if (!g_UserData.Contains(userdata)) \
298 virtual void OnStopResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
299 { CHECK_USERDATA_RETURN(userdata);
300 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnStopResult(res, device, userdata);
303 virtual void OnSetPlayModeResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
304 { CHECK_USERDATA_RETURN(userdata);
305 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSetPlayModeResult(res, device, userdata);
308 virtual void OnSetAVTransportURIResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
309 { CHECK_USERDATA_RETURN(userdata);
310 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSetAVTransportURIResult(res, device, userdata);
313 virtual void OnSeekResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
314 { CHECK_USERDATA_RETURN(userdata);
315 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSeekResult(res, device, userdata);
318 virtual void OnPreviousResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
319 { CHECK_USERDATA_RETURN(userdata);
320 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPreviousResult(res, device, userdata);
323 virtual void OnPlayResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
324 { CHECK_USERDATA_RETURN(userdata);
325 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPlayResult(res, device, userdata);
328 virtual void OnPauseResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
329 { CHECK_USERDATA_RETURN(userdata);
330 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPauseResult(res, device, userdata);
333 virtual void OnNextResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
334 { CHECK_USERDATA_RETURN(userdata);
335 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnNextResult(res, device, userdata);
338 virtual void OnGetMediaInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_MediaInfo* info, void* userdata)
339 { CHECK_USERDATA_RETURN(userdata);
340 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetMediaInfoResult(res, device, info, userdata);
343 virtual void OnGetPositionInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_PositionInfo* info, void* userdata)
344 { CHECK_USERDATA_RETURN(userdata);
345 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetPositionInfoResult(res, device, info, userdata);
348 virtual void OnGetTransportInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_TransportInfo* info, void* userdata)
349 { CHECK_USERDATA_RETURN(userdata);
350 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetTransportInfoResult(res, device, info, userdata);
353 virtual bool OnMRAdded(PLT_DeviceDataReference& device )
355 CPlayerCoreFactory::Get().OnPlayerDiscovered((const char*)device->GetUUID()
356 ,(const char*)device->GetFriendlyName()
361 virtual void OnMRRemoved(PLT_DeviceDataReference& device )
363 CPlayerCoreFactory::Get().OnPlayerRemoved((const char*)device->GetUUID());
367 /*----------------------------------------------------------------------
369 +---------------------------------------------------------------------*/
371 m_MediaBrowser(NULL),
372 m_MediaController(NULL),
373 m_ServerHolder(new CDeviceHostReferenceHolder()),
374 m_RendererHolder(new CRendererReferenceHolder()),
375 m_CtrlPointHolder(new CCtrlPointReferenceHolder())
377 // initialize upnp context
378 m_UPnP = new PLT_UPnP();
380 // keep main IP around
381 if (g_application.getNetwork().GetFirstConnectedInterface()) {
382 m_IP = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
384 NPT_List<NPT_IpAddress> list;
385 if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIPAddresses(list)) && list.GetItemCount()) {
386 m_IP = (*(list.GetFirstItem())).ToString();
388 else if(m_IP.empty())
391 // start upnp monitoring
395 /*----------------------------------------------------------------------
397 +---------------------------------------------------------------------*/
405 delete m_ServerHolder;
406 delete m_RendererHolder;
407 delete m_CtrlPointHolder;
410 /*----------------------------------------------------------------------
412 +---------------------------------------------------------------------*/
423 /*----------------------------------------------------------------------
424 | CUPnP::ReleaseInstance
425 +---------------------------------------------------------------------*/
427 CUPnP::ReleaseInstance(bool bWait)
436 // since it takes a while to clean up
437 // starts a detached thread to do this
438 CUPnPCleaner* cleaner = new CUPnPCleaner(_upnp);
444 /*----------------------------------------------------------------------
446 +---------------------------------------------------------------------*/
447 CUPnPServer* CUPnP::GetServer()
450 return (CUPnPServer*)upnp->m_ServerHolder->m_Device.AsPointer();
454 /*----------------------------------------------------------------------
456 +---------------------------------------------------------------------*/
458 CUPnP::MarkWatched(const CFileItem& item, const bool watched)
460 if (upnp && upnp->m_MediaBrowser) {
461 // dynamic_cast is safe here, avoids polluting CUPnP.h header file
462 CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
463 return browser->MarkWatched(item, watched);
468 /*----------------------------------------------------------------------
469 | CUPnP::SaveFileState
470 +---------------------------------------------------------------------*/
472 CUPnP::SaveFileState(const CFileItem& item, const CBookmark& bookmark, const bool updatePlayCount)
474 if (upnp && upnp->m_MediaBrowser) {
475 // dynamic_cast is safe here, avoids polluting CUPnP.h header file
476 CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
477 return browser->SaveFileState(item, bookmark, updatePlayCount);
482 /*----------------------------------------------------------------------
484 +---------------------------------------------------------------------*/
488 if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
490 // create controlpoint
491 m_CtrlPointHolder->m_CtrlPoint = new PLT_CtrlPoint();
494 m_UPnP->AddCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
497 m_MediaBrowser = new CMediaBrowser(m_CtrlPointHolder->m_CtrlPoint);
500 if (CSettings::Get().GetBool("services.upnpcontroller")) {
501 m_MediaController = new CMediaController(m_CtrlPointHolder->m_CtrlPoint);
505 /*----------------------------------------------------------------------
507 +---------------------------------------------------------------------*/
511 if (m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
513 m_UPnP->RemoveCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
514 m_CtrlPointHolder->m_CtrlPoint = NULL;
516 delete m_MediaBrowser;
517 m_MediaBrowser = NULL;
518 delete m_MediaController;
519 m_MediaController = NULL;
522 /*----------------------------------------------------------------------
523 | CUPnP::CreateServer
524 +---------------------------------------------------------------------*/
526 CUPnP::CreateServer(int port /* = 0 */)
528 CUPnPServer* device =
529 new CUPnPServer(g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME),
530 CUPnPSettings::Get().GetServerUUID().length() ? CUPnPSettings::Get().GetServerUUID().c_str() : NULL,
533 // trying to set optional upnp values for XP UPnP UI Icons to detect us
534 // but it doesn't work anyways as it requires multicast for XP to detect us
535 device->m_PresentationURL =
537 CSettings::Get().GetInt("services.webserverport"),
540 device->m_ModelName = "XBMC Media Center";
541 device->m_ModelNumber = g_infoManager.GetVersion().c_str();
542 device->m_ModelDescription = "XBMC Media Center - Media Server";
543 device->m_ModelURL = "http://xbmc.org/";
544 device->m_Manufacturer = "Team XBMC";
545 device->m_ManufacturerURL = "http://xbmc.org/";
547 device->SetDelegate(device);
551 /*----------------------------------------------------------------------
553 +---------------------------------------------------------------------*/
557 if (!m_ServerHolder->m_Device.IsNull()) return false;
559 // load upnpserver.xml
560 CStdString filename = URIUtils::AddFileToFolder(CProfilesManager::Get().GetUserDataFolder(), "upnpserver.xml");
561 CUPnPSettings::Get().Load(filename);
563 // create the server with a XBox compatible friendlyname and UUID from upnpserver.xml if found
564 m_ServerHolder->m_Device = CreateServer(CUPnPSettings::Get().GetServerPort());
567 NPT_Result res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
568 if (NPT_FAILED(res)) {
569 // if the upnp device port was not 0, it could have failed because
570 // of port being in used, so restart with a random port
571 if (CUPnPSettings::Get().GetServerPort() > 0) m_ServerHolder->m_Device = CreateServer(0);
573 res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
576 // save port but don't overwrite saved settings if port was random
577 if (NPT_SUCCEEDED(res)) {
578 if (CUPnPSettings::Get().GetServerPort() == 0) {
579 CUPnPSettings::Get().SetServerPort(m_ServerHolder->m_Device->GetPort());
581 CUPnPServer::m_MaxReturnedItems = UPNP_DEFAULT_MAX_RETURNED_ITEMS;
582 if (CUPnPSettings::Get().GetMaximumReturnedItems() > 0) {
583 // must be > UPNP_DEFAULT_MIN_RETURNED_ITEMS
584 CUPnPServer::m_MaxReturnedItems = max(UPNP_DEFAULT_MIN_RETURNED_ITEMS, CUPnPSettings::Get().GetMaximumReturnedItems());
586 CUPnPSettings::Get().SetMaximumReturnedItems(CUPnPServer::m_MaxReturnedItems);
590 CUPnPSettings::Get().SetServerUUID(m_ServerHolder->m_Device->GetUUID().GetChars());
591 return CUPnPSettings::Get().Save(filename);
594 /*----------------------------------------------------------------------
596 +---------------------------------------------------------------------*/
600 if (m_ServerHolder->m_Device.IsNull()) return;
602 m_UPnP->RemoveDevice(m_ServerHolder->m_Device);
603 m_ServerHolder->m_Device = NULL;
606 /*----------------------------------------------------------------------
607 | CUPnP::CreateRenderer
608 +---------------------------------------------------------------------*/
610 CUPnP::CreateRenderer(int port /* = 0 */)
612 CUPnPRenderer* device =
613 new CUPnPRenderer(g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME),
615 (CUPnPSettings::Get().GetRendererUUID().length() ? CUPnPSettings::Get().GetRendererUUID().c_str() : NULL),
618 device->m_PresentationURL =
620 CSettings::Get().GetInt("services.webserverport"),
622 device->m_ModelName = "XBMC Media Center";
623 device->m_ModelNumber = g_infoManager.GetVersion().c_str();
624 device->m_ModelDescription = "XBMC Media Center - Media Renderer";
625 device->m_ModelURL = "http://xbmc.org/";
626 device->m_Manufacturer = "Team XBMC";
627 device->m_ManufacturerURL = "http://xbmc.org/";
632 /*----------------------------------------------------------------------
633 | CUPnP::StartRenderer
634 +---------------------------------------------------------------------*/
635 bool CUPnP::StartRenderer()
637 if (!m_RendererHolder->m_Device.IsNull()) return false;
639 CStdString filename = URIUtils::AddFileToFolder(CProfilesManager::Get().GetUserDataFolder(), "upnpserver.xml");
640 CUPnPSettings::Get().Load(filename);
642 m_RendererHolder->m_Device = CreateRenderer(CUPnPSettings::Get().GetRendererPort());
644 NPT_Result res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
646 // failed most likely because port is in use, try again with random port now
647 if (NPT_FAILED(res) && CUPnPSettings::Get().GetRendererPort() != 0) {
648 m_RendererHolder->m_Device = CreateRenderer(0);
650 res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
653 // save port but don't overwrite saved settings if random
654 if (NPT_SUCCEEDED(res) && CUPnPSettings::Get().GetRendererPort() == 0) {
655 CUPnPSettings::Get().SetRendererPort(m_RendererHolder->m_Device->GetPort());
659 CUPnPSettings::Get().SetRendererUUID(m_RendererHolder->m_Device->GetUUID().GetChars());
660 return CUPnPSettings::Get().Save(filename);
663 /*----------------------------------------------------------------------
664 | CUPnP::StopRenderer
665 +---------------------------------------------------------------------*/
666 void CUPnP::StopRenderer()
668 if (m_RendererHolder->m_Device.IsNull()) return;
670 m_UPnP->RemoveDevice(m_RendererHolder->m_Device);
671 m_RendererHolder->m_Device = NULL;
674 /*----------------------------------------------------------------------
676 +---------------------------------------------------------------------*/
677 void CUPnP::UpdateState()
679 if (!m_RendererHolder->m_Device.IsNull())
680 ((CUPnPRenderer*)m_RendererHolder->m_Device.AsPointer())->UpdateState();
683 void CUPnP::RegisterUserdata(void* ptr)
685 NPT_AutoLock lock(g_UserDataLock);
689 void CUPnP::UnregisterUserdata(void* ptr)
691 NPT_AutoLock lock(g_UserDataLock);
692 g_UserData.Remove(ptr);
695 } /* namespace UPNP */