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(CURL::Encode(item_id));
187 URIUtils::AddSlashAtEnd(id);
191 CLog::Log(LOGDEBUG, "UPNP: notfified container update %s", (const char*)path);
192 CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
193 message.SetStringParam(path.GetChars());
194 g_windowManager.SendThreadMessage(message);
197 bool MarkWatched(const CFileItem& item, const bool watched)
200 CFileItem temp(item);
201 temp.SetProperty("original_listitem_url", item.GetPath());
202 return SaveFileState(temp, CBookmark(), watched);
205 CLog::Log(LOGDEBUG, "UPNP: Marking video item %s as watched", item.GetPath().c_str());
206 return InvokeUpdateObject(item.GetPath().c_str(), "<upnp:playCount>1</upnp:playCount>", "<upnp:playCount>0</upnp:playCount>");
210 bool SaveFileState(const CFileItem& item, const CBookmark& bookmark, const bool updatePlayCount)
212 string path = item.GetProperty("original_listitem_url").asString();
213 if (!item.HasVideoInfoTag() || path.empty()) {
217 NPT_String curr_value;
218 NPT_String new_value;
220 if (item.GetVideoInfoTag()->m_resumePoint.timeInSeconds != bookmark.timeInSeconds) {
221 CLog::Log(LOGDEBUG, "UPNP: Updating resume point for item %s", path.c_str());
222 long time = (long)bookmark.timeInSeconds;
223 if (time < 0) time = 0;
224 curr_value.Append(NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>",
225 (long)item.GetVideoInfoTag()->m_resumePoint.timeInSeconds));
226 new_value.Append(NPT_String::Format("<upnp:lastPlaybackPosition>%ld</upnp:lastPlaybackPosition>", time));
228 if (updatePlayCount) {
229 CLog::Log(LOGDEBUG, "UPNP: Marking video item %s as watched", path.c_str());
230 if (!curr_value.IsEmpty()) curr_value.Append(",");
231 if (!new_value.IsEmpty()) new_value.Append(",");
232 curr_value.Append("<upnp:playCount>0</upnp:playCount>");
233 new_value.Append("<upnp:playCount>1</upnp:playCount>");
236 return InvokeUpdateObject(path.c_str(), (const char*)curr_value, (const char*)new_value);
239 bool InvokeUpdateObject(const char* id, const char* curr_value, const char* new_value)
242 PLT_DeviceDataReference device;
244 PLT_ActionReference action;
246 CLog::Log(LOGDEBUG, "UPNP: attempting to invoke UpdateObject for %s", id);
248 // check this server supports UpdateObject action
249 NPT_CHECK_LABEL(FindServer(url.GetHostName().c_str(), device),failed);
250 NPT_CHECK_LABEL(device->FindServiceById("urn:upnp-org:serviceId:ContentDirectory", cds),failed);
252 NPT_CHECK_SEVERE(m_CtrlPoint->CreateAction(
254 "urn:schemas-upnp-org:service:ContentDirectory:1",
258 NPT_CHECK_LABEL(action->SetArgumentValue("ObjectID", url.GetFileName().c_str()), failed);
259 NPT_CHECK_LABEL(action->SetArgumentValue("CurrentTagValue", curr_value), failed);
260 NPT_CHECK_LABEL(action->SetArgumentValue("NewTagValue", new_value), failed);
262 NPT_CHECK_LABEL(m_CtrlPoint->InvokeAction(action, NULL),failed);
264 CLog::Log(LOGDEBUG, "UPNP: invoked UpdateObject successfully");
268 CLog::Log(LOGINFO, "UPNP: invoking UpdateObject failed");
274 /*----------------------------------------------------------------------
275 | CMediaController class
276 +---------------------------------------------------------------------*/
277 class CMediaController
278 : public PLT_MediaControllerDelegate
279 , public PLT_MediaController
282 CMediaController(PLT_CtrlPointReference& ctrl_point)
283 : PLT_MediaController(ctrl_point)
285 PLT_MediaController::SetDelegate(this);
292 #define CHECK_USERDATA_RETURN(userdata) do { \
293 if (!g_UserData.Contains(userdata)) \
297 virtual void OnStopResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
298 { CHECK_USERDATA_RETURN(userdata);
299 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnStopResult(res, device, userdata);
302 virtual void OnSetPlayModeResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
303 { CHECK_USERDATA_RETURN(userdata);
304 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSetPlayModeResult(res, device, userdata);
307 virtual void OnSetAVTransportURIResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
308 { CHECK_USERDATA_RETURN(userdata);
309 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSetAVTransportURIResult(res, device, userdata);
312 virtual void OnSeekResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
313 { CHECK_USERDATA_RETURN(userdata);
314 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnSeekResult(res, device, userdata);
317 virtual void OnPreviousResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
318 { CHECK_USERDATA_RETURN(userdata);
319 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPreviousResult(res, device, userdata);
322 virtual void OnPlayResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
323 { CHECK_USERDATA_RETURN(userdata);
324 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPlayResult(res, device, userdata);
327 virtual void OnPauseResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
328 { CHECK_USERDATA_RETURN(userdata);
329 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnPauseResult(res, device, userdata);
332 virtual void OnNextResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
333 { CHECK_USERDATA_RETURN(userdata);
334 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnNextResult(res, device, userdata);
337 virtual void OnGetMediaInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_MediaInfo* info, void* userdata)
338 { CHECK_USERDATA_RETURN(userdata);
339 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetMediaInfoResult(res, device, info, userdata);
342 virtual void OnGetPositionInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_PositionInfo* info, void* userdata)
343 { CHECK_USERDATA_RETURN(userdata);
344 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetPositionInfoResult(res, device, info, userdata);
347 virtual void OnGetTransportInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_TransportInfo* info, void* userdata)
348 { CHECK_USERDATA_RETURN(userdata);
349 static_cast<PLT_MediaControllerDelegate*>(userdata)->OnGetTransportInfoResult(res, device, info, userdata);
352 virtual bool OnMRAdded(PLT_DeviceDataReference& device )
354 CPlayerCoreFactory::Get().OnPlayerDiscovered((const char*)device->GetUUID()
355 ,(const char*)device->GetFriendlyName()
360 virtual void OnMRRemoved(PLT_DeviceDataReference& device )
362 CPlayerCoreFactory::Get().OnPlayerRemoved((const char*)device->GetUUID());
366 /*----------------------------------------------------------------------
368 +---------------------------------------------------------------------*/
370 m_MediaBrowser(NULL),
371 m_MediaController(NULL),
372 m_ServerHolder(new CDeviceHostReferenceHolder()),
373 m_RendererHolder(new CRendererReferenceHolder()),
374 m_CtrlPointHolder(new CCtrlPointReferenceHolder())
376 // initialize upnp context
377 m_UPnP = new PLT_UPnP();
379 // keep main IP around
380 if (g_application.getNetwork().GetFirstConnectedInterface()) {
381 m_IP = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
383 NPT_List<NPT_IpAddress> list;
384 if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIPAddresses(list)) && list.GetItemCount()) {
385 m_IP = (*(list.GetFirstItem())).ToString();
387 else if(m_IP.empty())
390 // start upnp monitoring
394 /*----------------------------------------------------------------------
396 +---------------------------------------------------------------------*/
404 delete m_ServerHolder;
405 delete m_RendererHolder;
406 delete m_CtrlPointHolder;
409 /*----------------------------------------------------------------------
411 +---------------------------------------------------------------------*/
422 /*----------------------------------------------------------------------
423 | CUPnP::ReleaseInstance
424 +---------------------------------------------------------------------*/
426 CUPnP::ReleaseInstance(bool bWait)
435 // since it takes a while to clean up
436 // starts a detached thread to do this
437 CUPnPCleaner* cleaner = new CUPnPCleaner(_upnp);
443 /*----------------------------------------------------------------------
445 +---------------------------------------------------------------------*/
446 CUPnPServer* CUPnP::GetServer()
449 return (CUPnPServer*)upnp->m_ServerHolder->m_Device.AsPointer();
453 /*----------------------------------------------------------------------
455 +---------------------------------------------------------------------*/
457 CUPnP::MarkWatched(const CFileItem& item, const bool watched)
459 if (upnp && upnp->m_MediaBrowser) {
460 // dynamic_cast is safe here, avoids polluting CUPnP.h header file
461 CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
462 return browser->MarkWatched(item, watched);
467 /*----------------------------------------------------------------------
468 | CUPnP::SaveFileState
469 +---------------------------------------------------------------------*/
471 CUPnP::SaveFileState(const CFileItem& item, const CBookmark& bookmark, const bool updatePlayCount)
473 if (upnp && upnp->m_MediaBrowser) {
474 // dynamic_cast is safe here, avoids polluting CUPnP.h header file
475 CMediaBrowser* browser = dynamic_cast<CMediaBrowser*>(upnp->m_MediaBrowser);
476 return browser->SaveFileState(item, bookmark, updatePlayCount);
481 /*----------------------------------------------------------------------
483 +---------------------------------------------------------------------*/
487 if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
489 // create controlpoint
490 m_CtrlPointHolder->m_CtrlPoint = new PLT_CtrlPoint();
493 m_UPnP->AddCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
496 m_MediaBrowser = new CMediaBrowser(m_CtrlPointHolder->m_CtrlPoint);
499 if (CSettings::Get().GetBool("services.upnpcontroller")) {
500 m_MediaController = new CMediaController(m_CtrlPointHolder->m_CtrlPoint);
504 /*----------------------------------------------------------------------
506 +---------------------------------------------------------------------*/
510 if (m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
512 m_UPnP->RemoveCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
513 m_CtrlPointHolder->m_CtrlPoint = NULL;
515 delete m_MediaBrowser;
516 m_MediaBrowser = NULL;
517 delete m_MediaController;
518 m_MediaController = NULL;
521 /*----------------------------------------------------------------------
522 | CUPnP::CreateServer
523 +---------------------------------------------------------------------*/
525 CUPnP::CreateServer(int port /* = 0 */)
527 CUPnPServer* device =
528 new CUPnPServer(g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME),
529 CUPnPSettings::Get().GetServerUUID().length() ? CUPnPSettings::Get().GetServerUUID().c_str() : NULL,
532 // trying to set optional upnp values for XP UPnP UI Icons to detect us
533 // but it doesn't work anyways as it requires multicast for XP to detect us
534 device->m_PresentationURL =
536 CSettings::Get().GetInt("services.webserverport"),
539 device->m_ModelName = "XBMC Media Center";
540 device->m_ModelNumber = g_infoManager.GetVersion().c_str();
541 device->m_ModelDescription = "XBMC Media Center - Media Server";
542 device->m_ModelURL = "http://xbmc.org/";
543 device->m_Manufacturer = "Team XBMC";
544 device->m_ManufacturerURL = "http://xbmc.org/";
546 device->SetDelegate(device);
550 /*----------------------------------------------------------------------
552 +---------------------------------------------------------------------*/
556 if (!m_ServerHolder->m_Device.IsNull()) return false;
558 // load upnpserver.xml
559 CStdString filename = URIUtils::AddFileToFolder(CProfilesManager::Get().GetUserDataFolder(), "upnpserver.xml");
560 CUPnPSettings::Get().Load(filename);
562 // create the server with a XBox compatible friendlyname and UUID from upnpserver.xml if found
563 m_ServerHolder->m_Device = CreateServer(CUPnPSettings::Get().GetServerPort());
566 NPT_Result res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
567 if (NPT_FAILED(res)) {
568 // if the upnp device port was not 0, it could have failed because
569 // of port being in used, so restart with a random port
570 if (CUPnPSettings::Get().GetServerPort() > 0) m_ServerHolder->m_Device = CreateServer(0);
572 res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
575 // save port but don't overwrite saved settings if port was random
576 if (NPT_SUCCEEDED(res)) {
577 if (CUPnPSettings::Get().GetServerPort() == 0) {
578 CUPnPSettings::Get().SetServerPort(m_ServerHolder->m_Device->GetPort());
580 CUPnPServer::m_MaxReturnedItems = UPNP_DEFAULT_MAX_RETURNED_ITEMS;
581 if (CUPnPSettings::Get().GetMaximumReturnedItems() > 0) {
582 // must be > UPNP_DEFAULT_MIN_RETURNED_ITEMS
583 CUPnPServer::m_MaxReturnedItems = max(UPNP_DEFAULT_MIN_RETURNED_ITEMS, CUPnPSettings::Get().GetMaximumReturnedItems());
585 CUPnPSettings::Get().SetMaximumReturnedItems(CUPnPServer::m_MaxReturnedItems);
589 CUPnPSettings::Get().SetServerUUID(m_ServerHolder->m_Device->GetUUID().GetChars());
590 return CUPnPSettings::Get().Save(filename);
593 /*----------------------------------------------------------------------
595 +---------------------------------------------------------------------*/
599 if (m_ServerHolder->m_Device.IsNull()) return;
601 m_UPnP->RemoveDevice(m_ServerHolder->m_Device);
602 m_ServerHolder->m_Device = NULL;
605 /*----------------------------------------------------------------------
606 | CUPnP::CreateRenderer
607 +---------------------------------------------------------------------*/
609 CUPnP::CreateRenderer(int port /* = 0 */)
611 CUPnPRenderer* device =
612 new CUPnPRenderer(g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME),
614 (CUPnPSettings::Get().GetRendererUUID().length() ? CUPnPSettings::Get().GetRendererUUID().c_str() : NULL),
617 device->m_PresentationURL =
619 CSettings::Get().GetInt("services.webserverport"),
621 device->m_ModelName = "XBMC Media Center";
622 device->m_ModelNumber = g_infoManager.GetVersion().c_str();
623 device->m_ModelDescription = "XBMC Media Center - Media Renderer";
624 device->m_ModelURL = "http://xbmc.org/";
625 device->m_Manufacturer = "Team XBMC";
626 device->m_ManufacturerURL = "http://xbmc.org/";
631 /*----------------------------------------------------------------------
632 | CUPnP::StartRenderer
633 +---------------------------------------------------------------------*/
634 bool CUPnP::StartRenderer()
636 if (!m_RendererHolder->m_Device.IsNull()) return false;
638 CStdString filename = URIUtils::AddFileToFolder(CProfilesManager::Get().GetUserDataFolder(), "upnpserver.xml");
639 CUPnPSettings::Get().Load(filename);
641 m_RendererHolder->m_Device = CreateRenderer(CUPnPSettings::Get().GetRendererPort());
643 NPT_Result res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
645 // failed most likely because port is in use, try again with random port now
646 if (NPT_FAILED(res) && CUPnPSettings::Get().GetRendererPort() != 0) {
647 m_RendererHolder->m_Device = CreateRenderer(0);
649 res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
652 // save port but don't overwrite saved settings if random
653 if (NPT_SUCCEEDED(res) && CUPnPSettings::Get().GetRendererPort() == 0) {
654 CUPnPSettings::Get().SetRendererPort(m_RendererHolder->m_Device->GetPort());
658 CUPnPSettings::Get().SetRendererUUID(m_RendererHolder->m_Device->GetUUID().GetChars());
659 return CUPnPSettings::Get().Save(filename);
662 /*----------------------------------------------------------------------
663 | CUPnP::StopRenderer
664 +---------------------------------------------------------------------*/
665 void CUPnP::StopRenderer()
667 if (m_RendererHolder->m_Device.IsNull()) return;
669 m_UPnP->RemoveDevice(m_RendererHolder->m_Device);
670 m_RendererHolder->m_Device = NULL;
673 /*----------------------------------------------------------------------
675 +---------------------------------------------------------------------*/
676 void CUPnP::UpdateState()
678 if (!m_RendererHolder->m_Device.IsNull())
679 ((CUPnPRenderer*)m_RendererHolder->m_Device.AsPointer())->UpdateState();
682 void CUPnP::RegisterUserdata(void* ptr)
684 NPT_AutoLock lock(g_UserDataLock);
688 void CUPnP::UnregisterUserdata(void* ptr)
690 NPT_AutoLock lock(g_UserDataLock);
691 g_UserData.Remove(ptr);
694 } /* namespace UPNP */