changed: Improve (fallback) logic for subtitle downloading
[vuplus_xbmc] / xbmc / network / upnp / UPnPPlayer.cpp
1 /*
2  *      Copyright (c) 2006 elupus (Joakim Plate)
3  *      Copyright (C) 2006-2013 Team XBMC
4  *      http://xbmc.org
5  *
6  *  This Program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  *
11  *  This Program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with XBMC; see the file COPYING.  If not, see
18  *  <http://www.gnu.org/licenses/>.
19  *
20  */
21
22 #include "UPnPPlayer.h"
23 #include "UPnP.h"
24 #include "UPnPInternal.h"
25 #include "Platinum.h"
26 #include "PltMediaController.h"
27 #include "PltDidl.h"
28 #include "FileItem.h"
29 #include "threads/Event.h"
30 #include "utils/log.h"
31 #include "utils/TimeUtils.h"
32 #include "GUIInfoManager.h"
33 #include "ThumbLoader.h"
34 #include "video/VideoThumbLoader.h"
35 #include "music/MusicThumbLoader.h"
36 #include "ApplicationMessenger.h"
37 #include "Application.h"
38 #include "dialogs/GUIDialogBusy.h"
39 #include "guilib/GUIWindowManager.h"
40 #include "guilib/Key.h"
41 #include "dialogs/GUIDialogYesNo.h"
42
43
44 NPT_SET_LOCAL_LOGGER("xbmc.upnp.player")
45
46 namespace UPNP
47 {
48
49 class CUPnPPlayerController
50   : public PLT_MediaControllerDelegate
51 {
52 public:
53   CUPnPPlayerController(PLT_MediaController* control, PLT_DeviceDataReference& device, IPlayerCallback& callback)
54     : m_control(control)
55     , m_transport(NULL)
56     , m_device(device)
57     , m_instance(0)
58     , m_callback(callback)
59     , m_postime(0)
60   {
61     memset(&m_posinfo, 0, sizeof(m_posinfo));
62     m_device->FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", m_transport);
63   }
64
65   virtual void OnSetAVTransportURIResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
66   {
67     if(NPT_FAILED(res))
68       CLog::Log(LOGERROR, "UPNP: CUPnPPlayer : OnSetAVTransportURIResult failed");
69     m_resstatus = res;
70     m_resevent.Set();
71   }
72
73   virtual void OnPlayResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
74   {
75     if(NPT_FAILED(res))
76       CLog::Log(LOGERROR, "UPNP: CUPnPPlayer : OnPlayResult failed");
77     m_resstatus = res;
78     m_resevent.Set();
79   }
80
81   virtual void OnStopResult(NPT_Result res, PLT_DeviceDataReference& device, void* userdata)
82   {
83     if(NPT_FAILED(res))
84       CLog::Log(LOGERROR, "UPNP: CUPnPPlayer : OnStopResult failed");
85     m_resstatus = res;
86     m_resevent.Set();
87   }
88
89   virtual void OnGetMediaInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_MediaInfo* info, void* userdata)
90   {
91     if(NPT_FAILED(res) || info == NULL)
92       CLog::Log(LOGERROR, "UPNP: CUPnPPlayer : OnGetMediaInfoResult failed");
93   }
94
95   virtual void OnGetTransportInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_TransportInfo* info, void* userdata)
96   {
97     CSingleLock lock(m_section);
98
99     if(NPT_FAILED(res))
100     {
101       CLog::Log(LOGERROR, "UPNP: CUPnPPlayer : OnGetTransportInfoResult failed");
102       m_trainfo.cur_speed            = "0";
103       m_trainfo.cur_transport_state  = "STOPPED";
104       m_trainfo.cur_transport_status = "ERROR_OCCURED";
105     }
106     else
107       m_trainfo = *info;
108     m_traevnt.Set();
109   }
110
111   void UpdatePositionInfo()
112   {
113     if(m_postime == 0
114     || m_postime > CTimeUtils::GetFrameTime())
115       return;
116
117     m_control->GetTransportInfo(m_device, m_instance, this);
118     m_control->GetPositionInfo(m_device, m_instance, this);
119     m_postime = 0;
120   }
121
122   virtual void OnGetPositionInfoResult(NPT_Result res, PLT_DeviceDataReference& device, PLT_PositionInfo* info, void* userdata)
123   {
124     CSingleLock lock(m_section);
125
126     if(NPT_FAILED(res) || info == NULL)
127     {
128       CLog::Log(LOGERROR, "UPNP: CUPnPPlayer : OnGetMediaInfoResult failed");
129       m_posinfo = PLT_PositionInfo();
130     }
131     else
132       m_posinfo = *info;
133     m_postime = CTimeUtils::GetFrameTime() + 500;
134     m_posevnt.Set();
135   }
136
137
138   ~CUPnPPlayerController()
139   {
140   }
141
142   PLT_MediaController*     m_control;
143   PLT_Service *            m_transport;
144   PLT_DeviceDataReference  m_device;
145   NPT_UInt32               m_instance;
146   IPlayerCallback&         m_callback;
147
148   NPT_Result               m_resstatus;
149   CEvent                   m_resevent;
150
151   CCriticalSection         m_section;
152   unsigned int             m_postime;
153
154   CEvent                   m_posevnt;
155   PLT_PositionInfo         m_posinfo;
156
157   CEvent                   m_traevnt;
158   PLT_TransportInfo        m_trainfo;
159 };
160
161 CUPnPPlayer::CUPnPPlayer(IPlayerCallback& callback, const char* uuid)
162 : IPlayer(callback)
163 , m_control(NULL)
164 , m_delegate(NULL)
165 , m_started(false)
166 , m_stopremote(false)
167 {
168   m_control  = CUPnP::GetInstance()->m_MediaController;
169
170   PLT_DeviceDataReference device;
171   if(NPT_SUCCEEDED(m_control->FindRenderer(uuid, device)))
172   {
173     m_delegate = new CUPnPPlayerController(m_control, device, callback);
174     CUPnP::RegisterUserdata(m_delegate);
175   }
176   else
177     CLog::Log(LOGERROR, "UPNP: CUPnPPlayer couldn't find device as %s", uuid);
178 }
179
180 CUPnPPlayer::~CUPnPPlayer()
181 {
182   CloseFile();
183   CUPnP::UnregisterUserdata(m_delegate);
184   delete m_delegate;
185 }
186
187 static NPT_Result WaitOnEvent(CEvent& event, XbmcThreads::EndTime& timeout, CGUIDialogBusy*& dialog)
188 {
189   if(event.WaitMSec(0))
190     return NPT_SUCCESS;
191
192   if(dialog == NULL) {
193     dialog = (CGUIDialogBusy*)g_windowManager.GetWindow(WINDOW_DIALOG_BUSY);
194     dialog->Show();
195   }
196
197   g_windowManager.ProcessRenderLoop(false);
198
199   do {
200     if(event.WaitMSec(100))
201       return NPT_SUCCESS;
202
203     g_windowManager.ProcessRenderLoop(false);
204
205     if(dialog->IsCanceled())
206       return NPT_FAILURE;
207
208   } while(!timeout.IsTimePast());
209
210   return NPT_FAILURE;
211 }
212
213 int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, CGUIDialogBusy*& dialog, XbmcThreads::EndTime& timeout)
214 {
215   CFileItem item(file);
216   NPT_Reference<CThumbLoader> thumb_loader;
217   NPT_Reference<PLT_MediaObject> obj;
218   NPT_String path(file.GetPath().c_str());
219   NPT_String tmp, resource;
220   EMediaControllerQuirks quirks = EMEDIACONTROLLERQUIRKS_NONE;
221
222   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
223
224   if (file.IsVideoDb())
225     thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader());
226   else if (item.IsMusicDb())
227     thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader());
228
229   obj = BuildObject(item, path, false, thumb_loader, NULL, CUPnP::GetServer());
230   if(obj.IsNull()) goto failed;
231
232   NPT_CHECK_LABEL_SEVERE(PLT_Didl::ToDidl(*obj, "", tmp), failed);
233   tmp.Insert(didl_header, 0);
234   tmp.Append(didl_footer);
235
236   quirks = GetMediaControllerQuirks(m_delegate->m_device.AsPointer());
237   if (quirks & EMEDIACONTROLLERQUIRKS_X_MKV)
238   {
239     for (NPT_Cardinal i=0; i< obj->m_Resources.GetItemCount(); i++) {
240       if (obj->m_Resources[i].m_ProtocolInfo.GetContentType().Compare("video/x-matroska") == 0) {
241         NPT_String protocolInfo = obj->m_Resources[i].m_ProtocolInfo.ToString();
242         protocolInfo.Replace(":video/x-matroska:", ":video/x-mkv:");
243         obj->m_Resources[i].m_ProtocolInfo = PLT_ProtocolInfo(protocolInfo);
244       }
245     }
246   }
247
248   /* The resource uri's are stored in the Didl. We must choose the best resource
249    * for the playback device */
250   NPT_Cardinal res_index;
251   NPT_CHECK_LABEL_SEVERE(m_control->FindBestResource(m_delegate->m_device, *obj, res_index), failed);
252
253
254   timeout.Set(timeout.GetInitialTimeoutValue());
255   /* dlna specifies that a return code of 705 should be returned
256    * if TRANSPORT_STATE is not STOPPED or NO_MEDIA_PRESENT */
257   NPT_CHECK_LABEL_SEVERE(m_control->Stop(m_delegate->m_device
258                                          , m_delegate->m_instance
259                                          , m_delegate), failed);
260   NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed);
261   NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed);
262
263
264   timeout.Set(timeout.GetInitialTimeoutValue());
265   NPT_CHECK_LABEL_SEVERE(m_control->SetAVTransportURI(m_delegate->m_device
266                                                     , m_delegate->m_instance
267                                                     , obj->m_Resources[res_index].m_Uri
268                                                     , (const char*)tmp
269                                                     , m_delegate), failed);
270   NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed);
271   NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed);
272
273   timeout.Set(timeout.GetInitialTimeoutValue());
274   NPT_CHECK_LABEL_SEVERE(m_control->Play(m_delegate->m_device
275                                        , m_delegate->m_instance
276                                        , "1"
277                                        , m_delegate), failed);
278   NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed);
279   NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed);
280
281
282   /* wait for PLAYING state */
283   timeout.Set(timeout.GetInitialTimeoutValue());
284   do {
285     NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device
286                                                      , m_delegate->m_instance
287                                                      , m_delegate), failed);
288
289
290     { CSingleLock lock(m_delegate->m_section);
291       if(m_delegate->m_trainfo.cur_transport_state == "PLAYING"
292       || m_delegate->m_trainfo.cur_transport_state == "PAUSED_PLAYBACK")
293         break;
294
295       if(m_delegate->m_trainfo.cur_transport_state  == "STOPPED"
296       && m_delegate->m_trainfo.cur_transport_status != "OK")
297       {
298         CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::OpenFile - remote player signalled error %s", file.GetPath().c_str());
299         goto failed;
300       }
301     }
302
303     NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout, dialog), failed);
304
305   } while(!timeout.IsTimePast());
306
307   if(options.starttime > 0)
308   {
309     /* many upnp units won't load file properly until after play (including xbmc) */
310     NPT_CHECK_LABEL(m_control->Seek(m_delegate->m_device
311                                     , m_delegate->m_instance
312                                     , "REL_TIME"
313                                     , PLT_Didl::FormatTimeStamp((NPT_UInt32)options.starttime)
314                                     , m_delegate), failed);
315   }
316
317   return NPT_SUCCESS;
318 failed:
319   return NPT_FAILURE;
320 }
321
322 bool CUPnPPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options)
323 {
324   CGUIDialogBusy* dialog = NULL;
325   XbmcThreads::EndTime timeout(10000);
326
327   /* if no path we want to attach to a already playing player */
328   if(file.GetPath() == "")
329   {
330     NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device
331                                                      , m_delegate->m_instance
332                                                      , m_delegate), failed);
333
334     NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout, dialog), failed);
335
336     /* make sure the attached player is actually playing */
337     { CSingleLock lock(m_delegate->m_section);
338       if(m_delegate->m_trainfo.cur_transport_state != "PLAYING"
339       && m_delegate->m_trainfo.cur_transport_state != "PAUSED_PLAYBACK")
340         goto failed;
341     }
342   }
343   else
344     NPT_CHECK_LABEL_SEVERE(PlayFile(file, options, dialog, timeout), failed);
345
346   m_stopremote = true;
347   m_started = true;
348   m_callback.OnPlayBackStarted();
349   NPT_CHECK_LABEL_SEVERE(m_control->GetPositionInfo(m_delegate->m_device
350                                                   , m_delegate->m_instance
351                                                   , m_delegate), failed);
352   NPT_CHECK_LABEL_SEVERE(m_control->GetMediaInfo(m_delegate->m_device
353                                                , m_delegate->m_instance
354                                                , m_delegate), failed);
355
356   if(dialog)
357     dialog->Close();
358
359   return true;
360 failed:
361   CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::OpenFile - unable to open file %s", file.GetPath().c_str());
362   if(dialog)
363     dialog->Close();
364   return false;
365 }
366
367 bool CUPnPPlayer::QueueNextFile(const CFileItem& file)
368 {
369   CFileItem item(file);
370   NPT_Reference<CThumbLoader> thumb_loader;
371   NPT_Reference<PLT_MediaObject> obj;
372   NPT_String path(file.GetPath().c_str());
373   NPT_String tmp;
374
375   if (file.IsVideoDb())
376     thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader());
377   else if (item.IsMusicDb())
378     thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader());
379
380
381   obj = BuildObject(item, path, 0, thumb_loader, NULL, CUPnP::GetServer());
382   if(!obj.IsNull())
383   {
384     NPT_CHECK_LABEL_SEVERE(PLT_Didl::ToDidl(*obj, "", tmp), failed);
385     tmp.Insert(didl_header, 0);
386     tmp.Append(didl_footer);
387   }
388
389   NPT_CHECK_LABEL_WARNING(m_control->SetNextAVTransportURI(m_delegate->m_device
390                                                          , m_delegate->m_instance
391                                                          , file.GetPath().c_str()
392                                                          , (const char*)tmp
393                                                          , m_delegate), failed);
394   if(!m_delegate->m_resevent.WaitMSec(10000)) goto failed;
395   NPT_CHECK_LABEL_WARNING(m_delegate->m_resstatus, failed);
396   return true;
397
398 failed:
399   CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::QueueNextFile - unable to queue file %s", file.GetPath().c_str());
400   return false;
401 }
402
403 bool CUPnPPlayer::CloseFile(bool reopen)
404 {
405   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
406   if(m_stopremote)
407   {
408     NPT_CHECK_LABEL(m_control->Stop(m_delegate->m_device
409                                   , m_delegate->m_instance
410                                   , m_delegate), failed);
411     if(!m_delegate->m_resevent.WaitMSec(10000)) goto failed;
412     NPT_CHECK_LABEL(m_delegate->m_resstatus, failed);
413   }
414
415   if(m_started)
416   {
417     m_started = false;
418     m_callback.OnPlayBackStopped();
419   }
420
421   return true;
422 failed:
423   CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::CloseFile - unable to stop playback");
424   return false;
425 }
426
427 void CUPnPPlayer::Pause()
428 {
429   if(IsPaused())
430     NPT_CHECK_LABEL(m_control->Play(m_delegate->m_device
431                                   , m_delegate->m_instance
432                                   , "1"
433                                   , m_delegate), failed);
434   else
435     NPT_CHECK_LABEL(m_control->Pause(m_delegate->m_device
436                                    , m_delegate->m_instance
437                                    , m_delegate), failed);
438
439   return;
440 failed:
441   CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::CloseFile - unable to pause/unpause playback");
442   return;
443 }
444
445 void CUPnPPlayer::SeekTime(__int64 ms)
446 {
447   NPT_CHECK_LABEL(m_control->Seek(m_delegate->m_device
448                                 , m_delegate->m_instance
449                                 , "REL_TIME", PLT_Didl::FormatTimeStamp((NPT_UInt32)(ms / 1000))
450                                 , m_delegate), failed);
451
452   g_infoManager.SetDisplayAfterSeek();
453   return;
454 failed:
455   CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::SeekTime - unable to seek playback");
456 }
457
458 float CUPnPPlayer::GetPercentage()
459 {
460   int64_t tot = GetTotalTime();
461   if(tot)
462     return 100.0f * GetTime() / tot;
463   else
464     return 0.0f;
465 }
466
467 void CUPnPPlayer::SeekPercentage(float percent)
468 {
469   int64_t tot = GetTotalTime();
470   if (tot)
471     SeekTime((int64_t)(tot * percent / 100));
472 }
473
474 void CUPnPPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
475 {
476 }
477
478 void CUPnPPlayer::DoAudioWork()
479 {
480   NPT_String data;
481   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
482   m_delegate->UpdatePositionInfo();
483
484   if(m_started) {
485     NPT_String uri, meta;
486     NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("CurrentTrackURI", uri), failed);
487     NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("CurrentTrackMetadata", meta), failed);
488
489     if(m_current_uri  != (const char*)uri
490     || m_current_meta != (const char*)meta) {
491       m_current_uri  = (const char*)uri;
492       m_current_meta = (const char*)meta;
493       CFileItemPtr item = GetFileItem(uri, meta);
494       g_application.CurrentFileItem() = *item;
495       CApplicationMessenger::Get().SetCurrentItem(*item.get());
496     }
497
498     NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("TransportState", data), failed);
499     if(data == "STOPPED")
500     {
501       m_started = false;
502       m_callback.OnPlayBackEnded();
503     }
504   }
505   return;
506 failed:
507   return;
508 }
509
510 bool CUPnPPlayer::IsPlaying() const
511 {
512   NPT_String data;
513   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
514   NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("TransportState", data), failed);
515   return data != "STOPPED";
516 failed:
517   return false;
518 }
519
520 bool CUPnPPlayer::IsPaused() const
521 {
522   NPT_String data;
523   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
524   NPT_CHECK_LABEL(m_delegate->m_transport->GetStateVariableValue("TransportState", data), failed);
525   return data == "PAUSED_PLAYBACK";
526 failed:
527   return false;
528 }
529
530 void CUPnPPlayer::SetVolume(float volume)
531 {
532   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
533   NPT_CHECK_LABEL(m_control->SetVolume(m_delegate->m_device
534                                      , m_delegate->m_instance
535                                      , "Master", (int)(volume * 100)
536                                      , m_delegate), failed);
537   return;
538 failed:
539   CLog::Log(LOGERROR, "UPNP: CUPnPPlayer - unable to set volume");
540   return;
541 }
542
543 int64_t CUPnPPlayer::GetTime()
544 {
545   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
546   return m_delegate->m_posinfo.rel_time.ToMillis();
547 failed:
548   return 0;
549 }
550
551 int64_t CUPnPPlayer::GetTotalTime()
552 {
553   NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed);
554   return m_delegate->m_posinfo.track_duration.ToMillis();
555 failed:
556   return 0;
557 };
558
559 CStdString CUPnPPlayer::GetPlayingTitle()
560 {
561   return "";
562 };
563
564 bool CUPnPPlayer::OnAction(const CAction &action)
565 {
566   switch (action.GetID())
567   {
568     case ACTION_STOP:
569       if(IsPlaying())
570       {
571         if(CGUIDialogYesNo::ShowAndGetInput(37022, 37023, 0, 0)) /* stop on remote system */
572           m_stopremote = true;
573         else
574           m_stopremote = false;
575         return false; /* let normal code handle the action */
576       }
577     default:
578       return false;
579   }
580 }
581
582 } /* namespace UPNP */