Merge pull request #4875 from koying/fixdroidremotekeyboard
[vuplus_xbmc] / xbmc / cores / ExternalPlayer / ExternalPlayer.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "system.h"
23 #include "signal.h"
24 #include "limits.h"
25 #include "threads/SingleLock.h"
26 #include "ExternalPlayer.h"
27 #include "windowing/WindowingFactory.h"
28 #include "dialogs/GUIDialogOK.h"
29 #include "guilib/GUIWindowManager.h"
30 #include "Application.h"
31 #include "filesystem/MusicDatabaseFile.h"
32 #include "FileItem.h"
33 #include "utils/RegExp.h"
34 #include "utils/StringUtils.h"
35 #include "utils/URIUtils.h"
36 #include "URL.h"
37 #include "utils/XMLUtils.h"
38 #include "utils/TimeUtils.h"
39 #include "utils/log.h"
40 #include "cores/AudioEngine/AEFactory.h"
41 #if defined(TARGET_WINDOWS)
42   #include "utils/CharsetConverter.h"
43   #include "Windows.h"
44   #ifdef HAS_IRSERVERSUITE
45     #include "input/windows/IRServerSuite.h"
46   #endif
47 #endif
48 #if defined(HAS_LIRC)
49   #include "input/linux/LIRC.h"
50 #endif
51 #if defined(TARGET_ANDROID)
52   #include "android/activity/XBMCApp.h"
53 #endif
54
55 // If the process ends in less than this time (ms), we assume it's a launcher
56 // and wait for manual intervention before continuing
57 #define LAUNCHER_PROCESS_TIME 2000
58 // Time (ms) we give a process we sent a WM_QUIT to close before terminating
59 #define PROCESS_GRACE_TIME 3000
60 // Default time after which the item's playcount is incremented
61 #define DEFAULT_PLAYCOUNT_MIN_TIME 10
62
63 using namespace XFILE;
64
65 #if defined(TARGET_WINDOWS)
66 extern HWND g_hWnd;
67 #endif
68
69 CExternalPlayer::CExternalPlayer(IPlayerCallback& callback)
70     : IPlayer(callback),
71       CThread("ExternalPlayer")
72 {
73   m_bAbortRequest = false;
74   m_bIsPlaying = false;
75   m_paused = false;
76   m_playbackStartTime = 0;
77   m_speed = 1;
78   m_totalTime = 1;
79   m_time = 0;
80
81   m_hideconsole = false;
82   m_warpcursor = WARP_NONE;
83   m_hidexbmc = false;
84   m_islauncher = false;
85   m_playCountMinTime = DEFAULT_PLAYCOUNT_MIN_TIME;
86   m_playOneStackItem = false;
87
88   m_dialog = NULL;
89   m_hwndXbmc = NULL;
90   m_xPos = 0;
91   m_yPos = 0;
92
93
94 #if defined(TARGET_WINDOWS)
95   memset(&m_processInfo, 0, sizeof(m_processInfo));
96 #endif
97 }
98
99 CExternalPlayer::~CExternalPlayer()
100 {
101   CloseFile();
102 }
103
104 bool CExternalPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
105 {
106   try
107   {
108     m_bIsPlaying = true;
109     m_launchFilename = file.GetPath();
110     CLog::Log(LOGNOTICE, "%s: %s", __FUNCTION__, m_launchFilename.c_str());
111     Create();
112
113     return true;
114   }
115   catch(...)
116   {
117     m_bIsPlaying = false;
118     CLog::Log(LOGERROR,"%s - Exception thrown", __FUNCTION__);
119     return false;
120   }
121 }
122
123 bool CExternalPlayer::CloseFile(bool reopen)
124 {
125   m_bAbortRequest = true;
126
127   if (m_dialog && m_dialog->IsActive()) m_dialog->Close();
128
129 #if defined(TARGET_WINDOWS)
130   if (m_bIsPlaying && m_processInfo.hProcess)
131   {
132     TerminateProcess(m_processInfo.hProcess, 1);
133   }
134 #endif
135
136   return true;
137 }
138
139 bool CExternalPlayer::IsPlaying() const
140 {
141   return m_bIsPlaying;
142 }
143
144 void CExternalPlayer::Process()
145 {
146   CStdString mainFile = m_launchFilename;
147   CStdString archiveContent = "";
148
149   if (m_args.find("{0}") == std::string::npos)
150   {
151     // Unwind archive names
152     CURL url(m_launchFilename);
153     CStdString protocol = url.GetProtocol();
154     if (protocol == "zip" || protocol == "rar"/* || protocol == "iso9660" ??*/ || protocol == "udf")
155     {
156       mainFile = url.GetHostName();
157       archiveContent = url.GetFileName();
158     }
159     if (protocol == "musicdb")
160       mainFile = CMusicDatabaseFile::TranslateUrl(url);
161     if (protocol == "bluray")
162     {
163       CURL base(url.GetHostName());
164       if(base.GetProtocol() == "udf")
165       {
166         mainFile = base.GetHostName(); /* image file */
167         archiveContent = base.GetFileName();
168       }
169       else
170         mainFile = URIUtils::AddFileToFolder(base.Get(), url.GetFileName());
171     }
172   }
173
174   if (m_filenameReplacers.size() > 0)
175   {
176     for (unsigned int i = 0; i < m_filenameReplacers.size(); i++)
177     {
178       std::vector<CStdString> vecSplit;
179       StringUtils::SplitString(m_filenameReplacers[i], " , ", vecSplit);
180
181       // something is wrong, go to next substitution
182       if (vecSplit.size() != 4)
183         continue;
184
185       CStdString strMatch = vecSplit[0];
186       StringUtils::Replace(strMatch, ",,",",");
187       bool bCaseless = vecSplit[3].find('i') != std::string::npos;
188       CRegExp regExp(bCaseless, CRegExp::autoUtf8);
189
190       if (!regExp.RegComp(strMatch.c_str()))
191       { // invalid regexp - complain in logs
192         CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strMatch.c_str());
193         continue;
194       }
195
196       if (regExp.RegFind(mainFile) > -1)
197       {
198         CStdString strPat = vecSplit[1];
199         StringUtils::Replace(strPat, ",,",",");
200
201         if (!regExp.RegComp(strPat.c_str()))
202         { // invalid regexp - complain in logs
203           CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strPat.c_str());
204           continue;
205         }
206
207         CStdString strRep = vecSplit[2];
208         StringUtils::Replace(strRep, ",,",",");
209         bool bGlobal = vecSplit[3].find('g') != std::string::npos;
210         bool bStop = vecSplit[3].find('s') != std::string::npos;
211         int iStart = 0;
212         while ((iStart = regExp.RegFind(mainFile, iStart)) > -1)
213         {
214           int iLength = regExp.GetFindLen();
215           mainFile = mainFile.substr(0, iStart) + regExp.GetReplaceString(strRep) + mainFile.substr(iStart + iLength);
216           if (!bGlobal)
217             break;
218         }
219         CLog::Log(LOGINFO, "%s: File matched:'%s' (RE='%s',Rep='%s') new filename:'%s'.", __FUNCTION__, strMatch.c_str(), strPat.c_str(), strRep.c_str(), mainFile.c_str());
220         if (bStop) break;
221       }
222     }
223   }
224
225   CLog::Log(LOGNOTICE, "%s: Player : %s", __FUNCTION__, m_filename.c_str());
226   CLog::Log(LOGNOTICE, "%s: File   : %s", __FUNCTION__, mainFile.c_str());
227   CLog::Log(LOGNOTICE, "%s: Content: %s", __FUNCTION__, archiveContent.c_str());
228   CLog::Log(LOGNOTICE, "%s: Args   : %s", __FUNCTION__, m_args.c_str());
229   CLog::Log(LOGNOTICE, "%s: Start", __FUNCTION__);
230
231   // make sure we surround the arguments with quotes where necessary
232   CStdString strFName;
233   CStdString strFArgs;
234 #if defined(TARGET_WINDOWS)
235   // W32 batch-file handline
236   if (StringUtils::EndsWith(m_filename, ".bat") || StringUtils::EndsWith(m_filename, ".cmd"))
237   {
238     // MSDN says you just need to do this, but cmd's handing of spaces and
239     // quotes is soo broken it seems to work much better if you just omit
240     // lpApplicationName and enclose the module in lpCommandLine in quotes
241     //strFName = "cmd.exe";
242     //strFArgs = "/c ";
243   }
244   else
245 #endif
246     strFName = m_filename;
247
248   strFArgs.append("\"");
249   strFArgs.append(m_filename);
250   strFArgs.append("\" ");
251   strFArgs.append(m_args);
252
253   int nReplaced = StringUtils::Replace(strFArgs, "{0}", mainFile);
254
255   if (!nReplaced)
256     nReplaced = StringUtils::Replace(strFArgs, "{1}", mainFile) + StringUtils::Replace(strFArgs, "{2}", archiveContent);
257
258   if (!nReplaced)
259   {
260     strFArgs.append(" \"");
261     strFArgs.append(mainFile);
262     strFArgs.append("\"");
263   }
264
265 #if defined(TARGET_WINDOWS)
266   if (m_warpcursor)
267   {
268     GetCursorPos(&m_ptCursorpos);
269     int x = 0;
270     int y = 0;
271     switch (m_warpcursor)
272     {
273       case WARP_BOTTOM_RIGHT:
274         x = GetSystemMetrics(SM_CXSCREEN);
275       case WARP_BOTTOM_LEFT:
276         y = GetSystemMetrics(SM_CYSCREEN);
277         break;
278       case WARP_TOP_RIGHT:
279         x = GetSystemMetrics(SM_CXSCREEN);
280         break;
281       case WARP_CENTER:
282         x = GetSystemMetrics(SM_CXSCREEN) / 2;
283         y = GetSystemMetrics(SM_CYSCREEN) / 2;
284         break;
285     }
286     CLog::Log(LOGNOTICE, "%s: Warping cursor to (%d,%d)", __FUNCTION__, x, y);
287     SetCursorPos(x,y);
288   }
289
290   LONG currentStyle = GetWindowLong(g_hWnd, GWL_EXSTYLE);
291 #endif
292
293   if (m_hidexbmc && !m_islauncher)
294   {
295     CLog::Log(LOGNOTICE, "%s: Hiding XBMC window", __FUNCTION__);
296     g_Windowing.Hide();
297   }
298 #if defined(TARGET_WINDOWS)
299   else if (currentStyle & WS_EX_TOPMOST)
300   {
301     CLog::Log(LOGNOTICE, "%s: Lowering XBMC window", __FUNCTION__);
302     SetWindowPos(g_hWnd,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOREDRAW);
303   }
304
305   CLog::Log(LOGDEBUG, "%s: Unlocking foreground window", __FUNCTION__);
306   LockSetForegroundWindow(LSFW_UNLOCK);
307 #endif
308
309   m_playbackStartTime = XbmcThreads::SystemClockMillis();
310
311   /* Suspend AE temporarily so exclusive or hog-mode sinks */
312   /* don't block external player's access to audio device  */
313   CAEFactory::Suspend();
314   // wait for AE has completed suspended
315   XbmcThreads::EndTime timer(2000);
316   while (!timer.IsTimePast() && !CAEFactory::IsSuspended())
317   {
318     Sleep(50);
319   }
320   if (timer.IsTimePast())
321   {
322     CLog::Log(LOGERROR,"%s: AudioEngine did not suspend before launching external player", __FUNCTION__);
323   }
324
325   m_callback.OnPlayBackStarted();
326
327   BOOL ret = TRUE;
328 #if defined(TARGET_WINDOWS)
329   ret = ExecuteAppW32(strFName.c_str(),strFArgs.c_str());
330 #elif defined(TARGET_ANDROID)
331   ret = ExecuteAppAndroid(m_filename.c_str(), mainFile.c_str());
332 #elif defined(TARGET_POSIX) || defined(TARGET_DARWIN_OSX)
333   ret = ExecuteAppLinux(strFArgs.c_str());
334 #endif
335   int64_t elapsedMillis = XbmcThreads::SystemClockMillis() - m_playbackStartTime;
336
337   if (ret && (m_islauncher || elapsedMillis < LAUNCHER_PROCESS_TIME))
338   {
339     if (m_hidexbmc)
340     {
341       CLog::Log(LOGNOTICE, "%s: XBMC cannot stay hidden for a launcher process", __FUNCTION__);
342       g_Windowing.Show(false);
343     }
344
345     {
346       CSingleLock lock(g_graphicsContext);
347       m_dialog = (CGUIDialogOK *)g_windowManager.GetWindow(WINDOW_DIALOG_OK);
348       m_dialog->SetHeading(23100);
349       m_dialog->SetLine(1, 23104);
350       m_dialog->SetLine(2, 23105);
351       m_dialog->SetLine(3, 23106);
352     }
353
354     if (!m_bAbortRequest) m_dialog->DoModal();
355   }
356
357   m_bIsPlaying = false;
358   CLog::Log(LOGNOTICE, "%s: Stop", __FUNCTION__);
359
360 #if defined(TARGET_WINDOWS)
361   g_Windowing.Restore();
362
363   if (currentStyle & WS_EX_TOPMOST)
364   {
365     CLog::Log(LOGNOTICE, "%s: Showing XBMC window TOPMOST", __FUNCTION__);
366     SetWindowPos(g_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);
367     SetForegroundWindow(g_hWnd);
368   }
369   else
370 #endif
371   {
372     CLog::Log(LOGNOTICE, "%s: Showing XBMC window", __FUNCTION__);
373     g_Windowing.Show();
374   }
375
376 #if defined(TARGET_WINDOWS)
377   if (m_warpcursor)
378   {
379     m_xPos = 0;
380     m_yPos = 0;
381     if (&m_ptCursorpos != 0)
382     {
383       m_xPos = (m_ptCursorpos.x);
384       m_yPos = (m_ptCursorpos.y);
385     }
386     CLog::Log(LOGNOTICE, "%s: Restoring cursor to (%d,%d)", __FUNCTION__, m_xPos, m_yPos);
387     SetCursorPos(m_xPos,m_yPos);
388   }
389 #endif
390
391   /* Resume AE processing of XBMC native audio */
392   if (!CAEFactory::Resume())
393   {
394     CLog::Log(LOGFATAL, "%s: Failed to restart AudioEngine after return from external player",__FUNCTION__);
395   }
396
397   // We don't want to come back to an active screensaver
398   g_application.ResetScreenSaver();
399   g_application.WakeUpScreenSaverAndDPMS();
400
401   if (!ret || (m_playOneStackItem && g_application.CurrentFileItem().IsStack()))
402     m_callback.OnPlayBackStopped();
403   else
404     m_callback.OnPlayBackEnded();
405 }
406
407 #if defined(TARGET_WINDOWS)
408 BOOL CExternalPlayer::ExecuteAppW32(const char* strPath, const char* strSwitches)
409 {
410   CLog::Log(LOGNOTICE, "%s: %s %s", __FUNCTION__, strPath, strSwitches);
411
412   STARTUPINFOW si;
413   memset(&si, 0, sizeof(si));
414   si.cb = sizeof(si);
415   si.dwFlags = STARTF_USESHOWWINDOW;
416   si.wShowWindow = m_hideconsole ? SW_HIDE : SW_SHOW;
417
418   CStdStringW WstrPath, WstrSwitches;
419   g_charsetConverter.utf8ToW(strPath, WstrPath);
420   g_charsetConverter.utf8ToW(strSwitches, WstrSwitches);
421
422   if (m_bAbortRequest) return false;
423
424   BOOL ret = CreateProcessW(WstrPath.empty() ? NULL : WstrPath.c_str(),
425                             (LPWSTR) WstrSwitches.c_str(), NULL, NULL, FALSE, NULL,
426                             NULL, NULL, &si, &m_processInfo);
427
428   if (ret == FALSE)
429   {
430     DWORD lastError = GetLastError();
431     CLog::Log(LOGNOTICE, "%s - Failure: %d", __FUNCTION__, lastError);
432   }
433   else
434   {
435     int res = WaitForSingleObject(m_processInfo.hProcess, INFINITE);
436
437     switch (res)
438     {
439       case WAIT_OBJECT_0:
440         CLog::Log(LOGNOTICE, "%s: WAIT_OBJECT_0", __FUNCTION__);
441         break;
442       case WAIT_ABANDONED:
443         CLog::Log(LOGNOTICE, "%s: WAIT_ABANDONED", __FUNCTION__);
444         break;
445       case WAIT_TIMEOUT:
446         CLog::Log(LOGNOTICE, "%s: WAIT_TIMEOUT", __FUNCTION__);
447         break;
448       case WAIT_FAILED:
449         CLog::Log(LOGNOTICE, "%s: WAIT_FAILED (%d)", __FUNCTION__, GetLastError());
450         ret = FALSE;
451         break;
452     }
453
454     CloseHandle(m_processInfo.hThread);
455     m_processInfo.hThread = 0;
456     CloseHandle(m_processInfo.hProcess);
457     m_processInfo.hProcess = 0;
458   }
459
460   return ret;
461 }
462 #endif
463
464 #if !defined(TARGET_ANDROID) && (defined(TARGET_POSIX) || defined(TARGET_DARWIN_OSX))
465 BOOL CExternalPlayer::ExecuteAppLinux(const char* strSwitches)
466 {
467   CLog::Log(LOGNOTICE, "%s: %s", __FUNCTION__, strSwitches);
468 #ifdef HAS_LIRC
469   bool remoteused = g_RemoteControl.IsInUse();
470   g_RemoteControl.Disconnect();
471   g_RemoteControl.setUsed(false);
472 #endif
473
474   int ret = system(strSwitches);
475
476 #ifdef HAS_LIRC
477   g_RemoteControl.setUsed(remoteused);
478   g_RemoteControl.Initialize();
479 #endif
480
481   if (ret != 0)
482   {
483     CLog::Log(LOGNOTICE, "%s: Failure: %d", __FUNCTION__, ret);
484   }
485
486   return ret == 0;
487 }
488 #endif
489
490 #if defined(TARGET_ANDROID)
491 BOOL CExternalPlayer::ExecuteAppAndroid(const char* strSwitches,const char* strPath)
492 {
493   CLog::Log(LOGNOTICE, "%s: %s", __FUNCTION__, strSwitches);
494
495   int ret = CXBMCApp::StartActivity(strSwitches, "android.intent.action.VIEW", "video/*", strPath);
496
497   if (ret != 0)
498   {
499     CLog::Log(LOGNOTICE, "%s: Failure: %d", __FUNCTION__, ret);
500   }
501
502   return ret == 0;
503 }
504 #endif
505
506 void CExternalPlayer::Pause()
507 {
508 }
509
510 bool CExternalPlayer::IsPaused() const
511 {
512   return false;
513 }
514
515 bool CExternalPlayer::HasVideo() const
516 {
517   return true;
518 }
519
520 bool CExternalPlayer::HasAudio() const
521 {
522   return false;
523 }
524
525 void CExternalPlayer::SwitchToNextLanguage()
526 {
527 }
528
529 void CExternalPlayer::ToggleSubtitles()
530 {
531 }
532
533 bool CExternalPlayer::CanSeek()
534 {
535   return false;
536 }
537
538 void CExternalPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
539 {
540 }
541
542 void CExternalPlayer::GetAudioInfo(CStdString& strAudioInfo)
543 {
544   strAudioInfo = "CExternalPlayer:GetAudioInfo";
545 }
546
547 void CExternalPlayer::GetVideoInfo(CStdString& strVideoInfo)
548 {
549   strVideoInfo = "CExternalPlayer:GetVideoInfo";
550 }
551
552 void CExternalPlayer::GetGeneralInfo(CStdString& strGeneralInfo)
553 {
554   strGeneralInfo = "CExternalPlayer:GetGeneralInfo";
555 }
556
557 void CExternalPlayer::SwitchToNextAudioLanguage()
558 {
559 }
560
561 void CExternalPlayer::SeekPercentage(float iPercent)
562 {
563 }
564
565 float CExternalPlayer::GetPercentage()
566 {
567   int64_t iTime = GetTime();
568   int64_t iTotalTime = GetTotalTime();
569
570   if (iTotalTime != 0)
571   {
572     CLog::Log(LOGDEBUG, "Percentage is %f", (iTime * 100 / (float)iTotalTime));
573     return iTime * 100 / (float)iTotalTime;
574   }
575
576   return 0.0f;
577 }
578
579 void CExternalPlayer::SetAVDelay(float fValue)
580 {
581 }
582
583 float CExternalPlayer::GetAVDelay()
584 {
585   return 0.0f;
586 }
587
588 void CExternalPlayer::SetSubTitleDelay(float fValue)
589 {
590 }
591
592 float CExternalPlayer::GetSubTitleDelay()
593 {
594   return 0.0;
595 }
596
597 void CExternalPlayer::SeekTime(int64_t iTime)
598 {
599 }
600
601 int64_t CExternalPlayer::GetTime() // in millis
602 {
603   if ((XbmcThreads::SystemClockMillis() - m_playbackStartTime) / 1000 > m_playCountMinTime)
604   {
605     m_time = m_totalTime * 1000;
606   }
607
608   return m_time;
609 }
610
611 int64_t CExternalPlayer::GetTotalTime() // in milliseconds
612 {
613   return (int64_t)m_totalTime * 1000;
614 }
615
616 void CExternalPlayer::ToFFRW(int iSpeed)
617 {
618   m_speed = iSpeed;
619 }
620
621 void CExternalPlayer::ShowOSD(bool bOnoff)
622 {
623 }
624
625 CStdString CExternalPlayer::GetPlayerState()
626 {
627   return "";
628 }
629
630 bool CExternalPlayer::SetPlayerState(CStdString state)
631 {
632   return true;
633 }
634
635 bool CExternalPlayer::Initialize(TiXmlElement* pConfig)
636 {
637   XMLUtils::GetString(pConfig, "filename", m_filename);
638   if (m_filename.length() > 0)
639   {
640     CLog::Log(LOGNOTICE, "ExternalPlayer Filename: %s", m_filename.c_str());
641   }
642   else
643   {
644     CStdString xml;
645     xml<<*pConfig;
646     CLog::Log(LOGERROR, "ExternalPlayer Error: filename element missing from: %s", xml.c_str());
647     return false;
648   }
649
650   XMLUtils::GetString(pConfig, "args", m_args);
651   XMLUtils::GetBoolean(pConfig, "playonestackitem", m_playOneStackItem);
652   XMLUtils::GetBoolean(pConfig, "islauncher", m_islauncher);
653   XMLUtils::GetBoolean(pConfig, "hidexbmc", m_hidexbmc);
654   if (!XMLUtils::GetBoolean(pConfig, "hideconsole", m_hideconsole))
655   {
656 #ifdef TARGET_WINDOWS
657     // Default depends on whether player is a batch file
658     m_hideconsole = StringUtils::EndsWith(m_filename, ".bat");
659 #endif
660   }
661
662   bool bHideCursor;
663   if (XMLUtils::GetBoolean(pConfig, "hidecursor", bHideCursor) && bHideCursor)
664     m_warpcursor = WARP_BOTTOM_RIGHT;
665
666   CStdString warpCursor;
667   if (XMLUtils::GetString(pConfig, "warpcursor", warpCursor) && !warpCursor.empty())
668   {
669     if (warpCursor == "bottomright") m_warpcursor = WARP_BOTTOM_RIGHT;
670     else if (warpCursor == "bottomleft") m_warpcursor = WARP_BOTTOM_LEFT;
671     else if (warpCursor == "topleft") m_warpcursor = WARP_TOP_LEFT;
672     else if (warpCursor == "topright") m_warpcursor = WARP_TOP_RIGHT;
673     else if (warpCursor == "center") m_warpcursor = WARP_CENTER;
674     else
675     {
676       warpCursor = "none";
677       CLog::Log(LOGWARNING, "ExternalPlayer: invalid value for warpcursor: %s", warpCursor.c_str());
678     }
679   }
680
681   XMLUtils::GetInt(pConfig, "playcountminimumtime", m_playCountMinTime, 1, INT_MAX);
682
683   CLog::Log(LOGNOTICE, "ExternalPlayer Tweaks: hideconsole (%s), hidexbmc (%s), islauncher (%s), warpcursor (%s)",
684           m_hideconsole ? "true" : "false",
685           m_hidexbmc ? "true" : "false",
686           m_islauncher ? "true" : "false",
687           warpCursor.c_str());
688
689 #ifdef TARGET_WINDOWS
690   m_filenameReplacers.push_back("^smb:// , / , \\\\ , g");
691   m_filenameReplacers.push_back("^smb:\\\\\\\\ , smb:(\\\\\\\\[^\\\\]*\\\\) , \\1 , ");
692 #endif
693
694   TiXmlElement* pReplacers = pConfig->FirstChildElement("replacers");
695   while (pReplacers)
696   {
697     GetCustomRegexpReplacers(pReplacers, m_filenameReplacers);
698     pReplacers = pReplacers->NextSiblingElement("replacers");
699   }
700
701   return true;
702 }
703
704 void CExternalPlayer::GetCustomRegexpReplacers(TiXmlElement *pRootElement,
705                                                CStdStringArray& settings)
706 {
707   int iAction = 0; // overwrite
708   // for backward compatibility
709   const char* szAppend = pRootElement->Attribute("append");
710   if ((szAppend && stricmp(szAppend, "yes") == 0))
711     iAction = 1;
712   // action takes precedence if both attributes exist
713   const char* szAction = pRootElement->Attribute("action");
714   if (szAction)
715   {
716     iAction = 0; // overwrite
717     if (stricmp(szAction, "append") == 0)
718       iAction = 1; // append
719     else if (stricmp(szAction, "prepend") == 0)
720       iAction = 2; // prepend
721   }
722   if (iAction == 0)
723     settings.clear();
724
725   TiXmlElement* pReplacer = pRootElement->FirstChildElement("replacer");
726   int i = 0;
727   while (pReplacer)
728   {
729     if (pReplacer->FirstChild())
730     {
731       const char* szGlobal = pReplacer->Attribute("global");
732       const char* szStop = pReplacer->Attribute("stop");
733       bool bGlobal = szGlobal && stricmp(szGlobal, "true") == 0;
734       bool bStop = szStop && stricmp(szStop, "true") == 0;
735
736       CStdString strMatch;
737       CStdString strPat;
738       CStdString strRep;
739       XMLUtils::GetString(pReplacer,"match",strMatch);
740       XMLUtils::GetString(pReplacer,"pat",strPat);
741       XMLUtils::GetString(pReplacer,"rep",strRep);
742
743       if (!strPat.empty() && !strRep.empty())
744       {
745         CLog::Log(LOGDEBUG,"  Registering replacer:");
746         CLog::Log(LOGDEBUG,"    Match:[%s] Pattern:[%s] Replacement:[%s]", strMatch.c_str(), strPat.c_str(), strRep.c_str());
747         CLog::Log(LOGDEBUG,"    Global:[%s] Stop:[%s]", bGlobal?"true":"false", bStop?"true":"false");
748         // keep literal commas since we use comma as a seperator
749         StringUtils::Replace(strMatch, ",",",,");
750         StringUtils::Replace(strPat, ",",",,");
751         StringUtils::Replace(strRep, ",",",,");
752
753         CStdString strReplacer = strMatch + " , " + strPat + " , " + strRep + " , " + (bGlobal ? "g" : "") + (bStop ? "s" : "");
754         if (iAction == 2)
755           settings.insert(settings.begin() + i++, 1, strReplacer);
756         else
757           settings.push_back(strReplacer);
758       }
759       else
760       {
761         // error message about missing tag
762         if (strPat.empty())
763           CLog::Log(LOGERROR,"  Missing <Pat> tag");
764         else
765           CLog::Log(LOGERROR,"  Missing <Rep> tag");
766       }
767     }
768
769     pReplacer = pReplacer->NextSiblingElement("replacer");
770   }
771 }