MythTV  master
tv_play.cpp
Go to the documentation of this file.
1 
2 #include "tv_play.h"
3 
4 #include <algorithm>
5 #include <cassert>
6 #include <chrono> // for milliseconds
7 #include <cmath>
8 #include <cstdarg>
9 #include <cstdint>
10 #include <cstdlib>
11 #include <thread> // for sleep_for
12 
13 using namespace std;
14 
15 #include <QApplication>
16 #include <QDomDocument>
17 #include <QDomElement>
18 #include <QDomNode>
19 #include <QEvent>
20 #include <QFile>
21 #include <QKeyEvent>
22 #include <QRegExp>
23 #include <QRunnable>
24 #include <QTimerEvent>
25 #include <utility>
26 
27 #include "mythconfig.h"
28 
29 // libmythbase
30 #include "mthreadpool.h"
31 #include "signalhandling.h"
32 #include "mythdb.h"
33 #include "mythcorecontext.h"
34 #include "mythlogging.h"
35 #include "lcddevice.h"
36 #include "compat.h"
37 #include "mythdirs.h"
38 #include "mythmedia.h"
39 
40 // libmyth
41 #include "programinfo.h"
42 #include "remoteutil.h"
43 
44 // libmythui
45 #include "mythuistatetracker.h"
46 #include "mythuihelper.h"
47 #include "mythdialogbox.h"
48 #include "mythmainwindow.h"
49 #include "mythscreenstack.h"
50 #include "mythscreentype.h"
51 #include "DisplayRes.h"
52 #include "mythuiactions.h" // for ACTION_LEFT, ACTION_RIGHT, etc
53 
54 // libmythtv
55 #include "DVD/dvdringbuffer.h"
56 #include "Bluray/bdringbuffer.h"
57 #include "remoteencoder.h"
58 #include "tvremoteutil.h"
59 #include "mythplayer.h"
60 #include "subtitlescreen.h"
61 #include "DetectLetterbox.h"
62 #include "jobqueue.h"
63 #include "livetvchain.h"
64 #include "playgroup.h"
65 #include "sourceutil.h"
66 #include "cardutil.h"
67 #include "channelutil.h"
68 #include "tv_play_win.h"
69 #include "recordinginfo.h"
70 #include "signalmonitorvalue.h"
71 #include "recordingrule.h"
72 #include "mythsystemevent.h"
73 #include "videometadatautil.h"
74 #include "tvbrowsehelper.h"
75 #include "playercontext.h" // for PlayerContext, osdInfo, etc
76 #include "programtypes.h"
77 #include "ringbuffer.h" // for RingBuffer, etc
78 #include "tv_actions.h" // for ACTION_TOGGLESLEEP, etc
79 #include "mythcodeccontext.h"
80 
81 #if ! HAVE_ROUND
82 #define round(x) ((int) ((x) + 0.5))
83 #endif
84 
85 #define DEBUG_CHANNEL_PREFIX 0
86 #define DEBUG_ACTIONS 0
88 #define LOC QString("TV::%1(): ").arg(__func__)
89 
90 #define GetPlayer(X,Y) GetPlayerHaveLock(X, Y, __FILE__ , __LINE__)
91 #define GetOSDLock(X) GetOSDL(X, __FILE__, __LINE__)
92 
93 #define SetOSDText(CTX, GROUP, FIELD, TEXT, TIMEOUT) { \
94  OSD *osd_m = GetOSDLock(CTX); \
95  if (osd_m) \
96  { \
97  InfoMap map; \
98  map.insert(FIELD,TEXT); \
99  osd_m->SetText(GROUP, map, TIMEOUT); \
100  } \
101  ReturnOSDLock(CTX, osd_m); }
102 
103 #define SetOSDMessage(CTX, MESSAGE) \
104  SetOSDText(CTX, "osd_message", "message_text", MESSAGE, kOSDTimeout_Med)
105 
106 #define HideOSDWindow(CTX, WINDOW) { \
107  OSD *osd = GetOSDLock(CTX); \
108  if (osd) \
109  osd->HideWindow(WINDOW); \
110  ReturnOSDLock(CTX, osd); }
111 
112 //static const QString _Location = TV::tr("TV Player");
113 
114 const int TV::kInitFFRWSpeed = 0;
115 const uint TV::kInputKeysMax = 6;
116 const uint TV::kNextSource = 1;
117 const uint TV::kPreviousSource = 2;
118 const uint TV::kMaxPIPCount = 4;
119 const uint TV::kMaxPBPCount = 2;
120 
121 
122 const uint TV::kInputModeTimeout = 5000;
123 const uint TV::kLCDTimeout = 1000;
124 const uint TV::kBrowseTimeout = 30000;
125 const uint TV::kKeyRepeatTimeout = 300;
126 const uint TV::kPrevChanTimeout = 750;
127 const uint TV::kSleepTimerDialogTimeout = 45000;
128 const uint TV::kIdleTimerDialogTimeout = 45000;
129 const uint TV::kVideoExitDialogTimeout = 120000;
130 
133 const uint TV::kEmbedCheckFrequency = 250;
136 #ifdef USING_VALGRIND
138 #else
140 #endif
141 const uint TV::kSaveLastPlayPosTimeout = 30000;
142 
147 QStringList TV::lastProgramStringList = QStringList();
148 
153 
158 
163 
168 
173 
175 
177 {
178 public:
179  MenuNodeTuple(const MenuBase &menu, const QDomNode &node) :
180  m_menu(menu), m_node(node) {}
182  {
183  assert("Should never be reached.");
184  }
185  const MenuBase &m_menu;
186  const QDomNode m_node;
187 };
188 
190 
191 
194 int TV::ConfiguredTunerCards(void)
195 {
196  int count = 0;
197 
198  MSqlQuery query(MSqlQuery::InitCon());
199  query.prepare("SELECT COUNT(cardid) FROM capturecard;");
200  if (query.exec() && query.isActive() && query.size() && query.next())
201  count = query.value(0).toInt();
202 
203  LOG(VB_RECORD, LOG_INFO,
204  "ConfiguredTunerCards() = " + QString::number(count));
205 
206  return count;
207 }
208 
209 static void multi_lock(QMutex *mutex0, ...)
210 {
211  vector<QMutex*> mutex;
212  mutex.push_back(mutex0);
213 
214  va_list argp;
215  va_start(argp, mutex0);
216  QMutex *cur = va_arg(argp, QMutex*);
217  while (cur)
218  {
219  mutex.push_back(cur);
220  cur = va_arg(argp, QMutex*);
221  }
222  va_end(argp);
223 
224  for (bool success = false; !success;)
225  {
226  success = true;
227  for (uint i = 0; success && (i < mutex.size()); i++)
228  {
229  if (!(success = mutex[i]->tryLock()))
230  {
231  for (uint j = 0; j < i; j++)
232  mutex[j]->unlock();
233  std::this_thread::sleep_for(std::chrono::milliseconds(25));
234  }
235  }
236  }
237 }
238 
239 QMutex* TV::gTVLock = new QMutex();
240 TV* TV::gTV = nullptr;
241 
242 bool TV::IsTVRunning(void)
243 {
244  QMutexLocker locker(gTVLock);
245  return gTV;
246 }
247 
248 TV* TV::GetTV(void)
249 {
250  QMutexLocker locker(gTVLock);
251  if (gTV)
252  {
253  LOG(VB_GENERAL, LOG_WARNING, LOC + "Already have a TV object.");
254  return nullptr;
255  }
256  gTV = new TV();
257  return gTV;
258 }
259 
260 void TV::ReleaseTV(TV* tv)
261 {
262  QMutexLocker locker(gTVLock);
263  if (!tv || !gTV || (gTV != tv))
264  {
265  LOG(VB_GENERAL, LOG_ERR, LOC + "- programmer error.");
266  return;
267  }
268 
269  delete gTV;
270  gTV = nullptr;
271 }
272 
274 {
275  if (TV::IsTVRunning())
276  {
277  QMutexLocker lock(gTVLock);
278 
279  PlayerContext *ctx = gTV->GetPlayerReadLock(0, __FILE__, __LINE__);
280  PrepareToExitPlayer(ctx, __LINE__);
281  SetExitPlayer(true, true);
282  ReturnPlayerLock(ctx);
284  }
285 }
286 
290 bool TV::StartTV(ProgramInfo *tvrec, uint flags,
291  const ChannelInfoList &selection)
292 {
293  TV *tv = GetTV();
294  if (!tv)
295  {
297  return false;
298  }
299 
300  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
301  bool inPlaylist = (flags & kStartTVInPlayList) != 0U;
302  bool initByNetworkCommand = (flags & kStartTVByNetworkCommand) != 0U;
303  bool quitAll = false;
304  bool showDialogs = true;
305  bool playCompleted = false;
306  ProgramInfo *curProgram = nullptr;
307  bool startSysEventSent = false;
308  bool startLivetvEventSent = false;
309 
310  if (tvrec)
311  {
312  curProgram = new ProgramInfo(*tvrec);
313  curProgram->SetIgnoreBookmark((flags & kStartTVIgnoreBookmark) != 0U);
314  curProgram->SetIgnoreProgStart((flags & kStartTVIgnoreProgStart) != 0U);
315  curProgram->SetAllowLastPlayPos((flags & kStartTVAllowLastPlayPos) != 0U);
316  }
317 
319 
320  // Initialize TV
321  if (!tv->Init())
322  {
323  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed initializing TV");
324  ReleaseTV(tv);
326  delete curProgram;
328  return false;
329  }
330 
331  if (!lastProgramStringList.empty())
332  {
333  ProgramInfo pginfo(lastProgramStringList);
334  if (pginfo.HasPathname() || pginfo.GetChanID())
335  tv->SetLastProgram(&pginfo);
336  }
337 
338  // Notify others that we are about to play
340 
341  QString playerError;
342  while (!quitAll)
343  {
344  if (curProgram)
345  {
346  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->Playback() -- begin");
347  if (!tv->Playback(*curProgram))
348  {
349  quitAll = true;
350  }
351  else if (!startSysEventSent)
352  {
353  startSysEventSent = true;
354  SendMythSystemPlayEvent("PLAY_STARTED", curProgram);
355  }
356 
357  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->Playback() -- end");
358  }
359  else if (RemoteGetFreeRecorderCount())
360  {
361  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->LiveTV() -- begin");
362  if (!tv->LiveTV(showDialogs, selection))
363  {
364  tv->SetExitPlayer(true, true);
365  quitAll = true;
366  }
367  else if (!startSysEventSent)
368  {
369  startSysEventSent = true;
370  startLivetvEventSent = true;
371  gCoreContext->SendSystemEvent("LIVETV_STARTED");
372  }
373 
374  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->LiveTV() -- end");
375  }
376  else
377  {
378  if (!ConfiguredTunerCards())
379  LOG(VB_GENERAL, LOG_ERR, LOC + "No tuners configured");
380  else
381  LOG(VB_GENERAL, LOG_ERR, LOC + "No tuners free for live tv");
382  quitAll = true;
383  continue;
384  }
385 
386  tv->setInPlayList(inPlaylist);
387  tv->setUnderNetworkControl(initByNetworkCommand);
388 
390 
391  // Process Events
392  LOG(VB_GENERAL, LOG_INFO, LOC + "Entering main playback loop.");
393  tv->PlaybackLoop();
394  LOG(VB_GENERAL, LOG_INFO, LOC + "Exiting main playback loop.");
395 
396  if (tv->getJumpToProgram())
397  {
398  ProgramInfo *nextProgram = tv->GetLastProgram();
399 
400  tv->SetLastProgram(curProgram);
401  delete curProgram;
402  curProgram = nextProgram;
403 
404  SendMythSystemPlayEvent("PLAY_CHANGED", curProgram);
405  continue;
406  }
407 
408  const PlayerContext *mctx =
409  tv->GetPlayerReadLock(0, __FILE__, __LINE__);
410  quitAll = tv->m_wantsToQuit || (mctx && mctx->m_errored);
411  if (mctx)
412  {
413  mctx->LockDeletePlayer(__FILE__, __LINE__);
414  if (mctx->m_player && mctx->m_player->IsErrored())
415  playerError = mctx->m_player->GetError();
416  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
417  }
418  tv->ReturnPlayerLock(mctx);
419  quitAll |= !playerError.isEmpty();
420  }
421 
422  LOG(VB_PLAYBACK, LOG_INFO, LOC + "-- process events 2 begin");
423  do
424  qApp->processEvents();
425  while (tv->m_isEmbedded);
426  LOG(VB_PLAYBACK, LOG_INFO, LOC + "-- process events 2 end");
427 
428  // check if the show has reached the end.
429  if (tvrec && tv->getEndOfRecording())
430  playCompleted = true;
431 
432  bool allowrerecord = tv->getAllowRerecord();
433  bool deleterecording = tv->m_requestDelete;
434 
435  ReleaseTV(tv);
436 
439 
440  if (curProgram)
441  {
442  if (startSysEventSent)
443  SendMythSystemPlayEvent("PLAY_STOPPED", curProgram);
444 
445  if (deleterecording)
446  {
447  QStringList list;
448  list.push_back(QString::number(curProgram->GetRecordingID()));
449  list.push_back("0"); // do not force delete
450  list.push_back(allowrerecord ? "1" : "0");
451  MythEvent me("LOCAL_PBB_DELETE_RECORDINGS", list);
452  gCoreContext->dispatch(me);
453  }
454  else if (curProgram->IsRecording())
455  {
456  lastProgramStringList.clear();
457  curProgram->ToStringList(lastProgramStringList);
458  }
459 
460  delete curProgram;
461  }
462  else if (startSysEventSent)
463  gCoreContext->SendSystemEvent("PLAY_STOPPED");
464 
465  if (!playerError.isEmpty())
466  {
467  MythScreenStack *ss = GetMythMainWindow()->GetStack("popup stack");
469  ss, playerError, false);
470  if (!dlg->Create())
471  delete dlg;
472  else
473  ss->AddScreen(dlg);
474  }
475 
477 
478  if (startLivetvEventSent)
479  gCoreContext->SendSystemEvent("LIVETV_ENDED");
480 
481  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
482 
483  return playCompleted;
484 }
485 
490 void TV::SetFuncPtr(const char *string, void *lptr)
491 {
492  QString name(string);
493  if (name == "playbackbox")
494  RunPlaybackBoxPtr = (EMBEDRETURNVOID)lptr;
495  else if (name == "viewscheduled")
496  RunViewScheduledPtr = (EMBEDRETURNVOID)lptr;
497  else if (name == "programguide")
498  RunProgramGuidePtr = (EMBEDRETURNVOIDEPG)lptr;
499  else if (name == "programfinder")
500  RunProgramFinderPtr = (EMBEDRETURNVOIDFINDER)lptr;
501  else if (name == "scheduleeditor")
502  RunScheduleEditorPtr = (EMBEDRETURNVOIDSCHEDIT)lptr;
503 }
504 
505 void TV::InitKeys(void)
506 {
507  REG_KEY("TV Frontend", ACTION_PLAYBACK, QT_TRANSLATE_NOOP("MythControls",
508  "Play Program"), "P");
509  REG_KEY("TV Frontend", ACTION_STOP, QT_TRANSLATE_NOOP("MythControls",
510  "Stop Program"), "");
511  REG_KEY("TV Frontend", ACTION_TOGGLERECORD, QT_TRANSLATE_NOOP("MythControls",
512  "Toggle recording status of current program"), "R");
513  REG_KEY("TV Frontend", ACTION_DAYLEFT, QT_TRANSLATE_NOOP("MythControls",
514  "Page the program guide back one day"), "Home");
515  REG_KEY("TV Frontend", ACTION_DAYRIGHT, QT_TRANSLATE_NOOP("MythControls",
516  "Page the program guide forward one day"), "End");
517  REG_KEY("TV Frontend", ACTION_PAGELEFT, QT_TRANSLATE_NOOP("MythControls",
518  "Page the program guide left"), ",,<");
519  REG_KEY("TV Frontend", ACTION_PAGERIGHT, QT_TRANSLATE_NOOP("MythControls",
520  "Page the program guide right"), ">,.");
521  REG_KEY("TV Frontend", ACTION_TOGGLEFAV, QT_TRANSLATE_NOOP("MythControls",
522  "Toggle the current channel as a favorite"), "?");
523  REG_KEY("TV Frontend", ACTION_TOGGLEPGORDER, QT_TRANSLATE_NOOP("MythControls",
524  "Reverse the channel order in the program guide"), "");
525  REG_KEY("TV Frontend", ACTION_GUIDE, QT_TRANSLATE_NOOP("MythControls",
526  "Show the Program Guide"), "S");
527  REG_KEY("TV Frontend", ACTION_FINDER, QT_TRANSLATE_NOOP("MythControls",
528  "Show the Program Finder"), "#");
529  REG_KEY("TV Frontend", ACTION_CHANNELSEARCH, QT_TRANSLATE_NOOP("MythControls",
530  "Show the Channel Search"), "");
531  REG_KEY("TV Frontend", "NEXTFAV", QT_TRANSLATE_NOOP("MythControls",
532  "Cycle through channel groups and all channels in the "
533  "program guide."), "/");
534  REG_KEY("TV Frontend", "CHANUPDATE", QT_TRANSLATE_NOOP("MythControls",
535  "Switch channels without exiting guide in Live TV mode."), "X");
536  REG_KEY("TV Frontend", ACTION_VOLUMEDOWN, QT_TRANSLATE_NOOP("MythControls",
537  "Volume down"), "[,{,F10,Volume Down");
538  REG_KEY("TV Frontend", ACTION_VOLUMEUP, QT_TRANSLATE_NOOP("MythControls",
539  "Volume up"), "],},F11,Volume Up");
540  REG_KEY("TV Frontend", ACTION_MUTEAUDIO, QT_TRANSLATE_NOOP("MythControls",
541  "Mute"), "|,\\,F9,Volume Mute");
542  REG_KEY("TV Frontend", "CYCLEAUDIOCHAN", QT_TRANSLATE_NOOP("MythControls",
543  "Cycle audio channels"), "");
544  REG_KEY("TV Frontend", "RANKINC", QT_TRANSLATE_NOOP("MythControls",
545  "Increase program or channel rank"), "Right");
546  REG_KEY("TV Frontend", "RANKDEC", QT_TRANSLATE_NOOP("MythControls",
547  "Decrease program or channel rank"), "Left");
548  REG_KEY("TV Frontend", "UPCOMING", QT_TRANSLATE_NOOP("MythControls",
549  "List upcoming episodes"), "O");
550  REG_KEY("TV Frontend", ACTION_VIEWSCHEDULED, QT_TRANSLATE_NOOP("MythControls",
551  "List scheduled upcoming episodes"), "");
552  REG_KEY("TV Frontend", ACTION_PREVRECORDED, QT_TRANSLATE_NOOP("MythControls",
553  "List previously recorded episodes"), "");
554  REG_KEY("TV Frontend", "DETAILS", QT_TRANSLATE_NOOP("MythControls",
555  "Show details"), "U");
556  REG_KEY("TV Frontend", "VIEWINPUT", QT_TRANSLATE_NOOP("MythControls",
557  "Switch Recording Input view"), "C");
558  REG_KEY("TV Frontend", "CUSTOMEDIT", QT_TRANSLATE_NOOP("MythControls",
559  "Edit Custom Record Rule"), "");
560  REG_KEY("TV Frontend", "CHANGERECGROUP", QT_TRANSLATE_NOOP("MythControls",
561  "Change Recording Group"), "");
562  REG_KEY("TV Frontend", "CHANGEGROUPVIEW", QT_TRANSLATE_NOOP("MythControls",
563  "Change Group View"), "");
564  REG_KEY("TV Frontend", ACTION_LISTRECORDEDEPISODES, QT_TRANSLATE_NOOP("MythControls",
565  "List recorded episodes"), "");
566  /*
567  * TODO DB update needs to perform the necessary conversion and delete
568  * the following upgrade code and replace bkmKeys and togBkmKeys with "" in the
569  * REG_KEY for ACTION_SETBOOKMARK and ACTION_TOGGLEBOOKMARK.
570  */
571  // Bookmarks - Instead of SELECT to add or toggle,
572  // Use separate bookmark actions. This code is to convert users
573  // who may already be using SELECT. If they are not already using
574  // this frontend then nothing will be assigned to bookmark actions.
575  QString bkmKeys;
576  QString togBkmKeys;
577  // Check if this is a new frontend - if PAUSE returns
578  // "?" then frontend is new, never used before, so we will not assign
579  // any default bookmark keys
580  QString testKey = GetMythMainWindow()->GetKey("TV Playback", ACTION_PAUSE);
581  if (testKey != "?")
582  {
583  int alternate = gCoreContext->GetNumSetting("AltClearSavedPosition",0);
584  QString selectKeys = GetMythMainWindow()->GetKey("Global", ACTION_SELECT);
585  if (selectKeys != "?")
586  {
587  if (alternate)
588  togBkmKeys = selectKeys;
589  else
590  bkmKeys = selectKeys;
591  }
592  }
593  REG_KEY("TV Playback", ACTION_SETBOOKMARK, QT_TRANSLATE_NOOP("MythControls",
594  "Add Bookmark"), bkmKeys);
595  REG_KEY("TV Playback", ACTION_TOGGLEBOOKMARK, QT_TRANSLATE_NOOP("MythControls",
596  "Toggle Bookmark"), togBkmKeys);
597  REG_KEY("TV Playback", "BACK", QT_TRANSLATE_NOOP("MythControls",
598  "Exit or return to DVD menu"), "Esc");
599  REG_KEY("TV Playback", ACTION_MENUCOMPACT, QT_TRANSLATE_NOOP("MythControls",
600  "Playback Compact Menu"), "Alt+M");
601  REG_KEY("TV Playback", ACTION_CLEAROSD, QT_TRANSLATE_NOOP("MythControls",
602  "Clear OSD"), "Backspace");
603  REG_KEY("TV Playback", ACTION_PAUSE, QT_TRANSLATE_NOOP("MythControls",
604  "Pause"), "P,Space");
605  REG_KEY("TV Playback", ACTION_SEEKFFWD, QT_TRANSLATE_NOOP("MythControls",
606  "Fast Forward"), "Right");
607  REG_KEY("TV Playback", ACTION_SEEKRWND, QT_TRANSLATE_NOOP("MythControls",
608  "Rewind"), "Left");
609  REG_KEY("TV Playback", ACTION_SEEKARB, QT_TRANSLATE_NOOP("MythControls",
610  "Arbitrary Seek"), "*");
611  REG_KEY("TV Playback", ACTION_SEEKABSOLUTE, QT_TRANSLATE_NOOP("MythControls",
612  "Seek to a position in seconds"), "");
613  REG_KEY("TV Playback", ACTION_CHANNELUP, QT_TRANSLATE_NOOP("MythControls",
614  "Channel up"), "Up");
615  REG_KEY("TV Playback", ACTION_CHANNELDOWN, QT_TRANSLATE_NOOP("MythControls",
616  "Channel down"), "Down");
617  REG_KEY("TV Playback", "NEXTFAV", QT_TRANSLATE_NOOP("MythControls",
618  "Switch to the next favorite channel"), "/");
619  REG_KEY("TV Playback", "PREVCHAN", QT_TRANSLATE_NOOP("MythControls",
620  "Switch to the previous channel"), "H");
621  REG_KEY("TV Playback", ACTION_JUMPFFWD, QT_TRANSLATE_NOOP("MythControls",
622  "Jump ahead"), "PgDown");
623  REG_KEY("TV Playback", ACTION_JUMPRWND, QT_TRANSLATE_NOOP("MythControls",
624  "Jump back"), "PgUp");
625  REG_KEY("TV Playback", "INFOWITHCUTLIST", QT_TRANSLATE_NOOP("MythControls",
626  "Info utilizing cutlist"), "");
627  REG_KEY("TV Playback", ACTION_JUMPBKMRK, QT_TRANSLATE_NOOP("MythControls",
628  "Jump to bookmark"), "K");
629  REG_KEY("TV Playback", "FFWDSTICKY", QT_TRANSLATE_NOOP("MythControls",
630  "Fast Forward (Sticky) or Forward one second while paused"), ">,.");
631  REG_KEY("TV Playback", "RWNDSTICKY", QT_TRANSLATE_NOOP("MythControls",
632  "Rewind (Sticky) or Rewind one second while paused"), ",,<");
633  REG_KEY("TV Playback", "NEXTSOURCE", QT_TRANSLATE_NOOP("MythControls",
634  "Next Video Source"), "Y");
635  REG_KEY("TV Playback", "PREVSOURCE", QT_TRANSLATE_NOOP("MythControls",
636  "Previous Video Source"), "");
637  REG_KEY("TV Playback", "NEXTINPUT", QT_TRANSLATE_NOOP("MythControls",
638  "Next Input"), "C");
639  REG_KEY("TV Playback", "NEXTCARD", QT_TRANSLATE_NOOP("MythControls",
640  "Next Card"), "");
641  REG_KEY("TV Playback", "SKIPCOMMERCIAL", QT_TRANSLATE_NOOP("MythControls",
642  "Skip Commercial"), "Z,End");
643  REG_KEY("TV Playback", "SKIPCOMMBACK", QT_TRANSLATE_NOOP("MythControls",
644  "Skip Commercial (Reverse)"), "Q,Home");
645  REG_KEY("TV Playback", ACTION_JUMPSTART, QT_TRANSLATE_NOOP("MythControls",
646  "Jump to the start of the recording."), "Ctrl+B");
647  REG_KEY("TV Playback", "TOGGLEBROWSE", QT_TRANSLATE_NOOP("MythControls",
648  "Toggle channel browse mode"), "O");
649  REG_KEY("TV Playback", ACTION_TOGGLERECORD, QT_TRANSLATE_NOOP("MythControls",
650  "Toggle recording status of current program"), "R");
651  REG_KEY("TV Playback", ACTION_TOGGLEFAV, QT_TRANSLATE_NOOP("MythControls",
652  "Toggle the current channel as a favorite"), "?");
653  REG_KEY("TV Playback", ACTION_VOLUMEDOWN, QT_TRANSLATE_NOOP("MythControls",
654  "Volume down"), "[,{,F10,Volume Down");
655  REG_KEY("TV Playback", ACTION_VOLUMEUP, QT_TRANSLATE_NOOP("MythControls",
656  "Volume up"), "],},F11,Volume Up");
657  REG_KEY("TV Playback", ACTION_MUTEAUDIO, QT_TRANSLATE_NOOP("MythControls",
658  "Mute"), "|,\\,F9,Volume Mute");
659  REG_KEY("TV Playback", ACTION_SETVOLUME, QT_TRANSLATE_NOOP("MythControls",
660  "Set the volume"), "");
661  REG_KEY("TV Playback", "CYCLEAUDIOCHAN", QT_TRANSLATE_NOOP("MythControls",
662  "Cycle audio channels"), "");
663  REG_KEY("TV Playback", ACTION_TOGGLEUPMIX, QT_TRANSLATE_NOOP("MythControls",
664  "Toggle audio upmixer"), "Ctrl+U");
665  REG_KEY("TV Playback", "TOGGLEPIPMODE", QT_TRANSLATE_NOOP("MythControls",
666  "Toggle Picture-in-Picture view"), "V");
667  REG_KEY("TV Playback", "TOGGLEPBPMODE", QT_TRANSLATE_NOOP("MythControls",
668  "Toggle Picture-by-Picture view"), "Ctrl+V");
669  REG_KEY("TV Playback", "CREATEPIPVIEW", QT_TRANSLATE_NOOP("MythControls",
670  "Create Picture-in-Picture view"), "");
671  REG_KEY("TV Playback", "CREATEPBPVIEW", QT_TRANSLATE_NOOP("MythControls",
672  "Create Picture-by-Picture view"), "");
673  REG_KEY("TV Playback", "NEXTPIPWINDOW", QT_TRANSLATE_NOOP("MythControls",
674  "Toggle active PIP/PBP window"), "B");
675  REG_KEY("TV Playback", "SWAPPIP", QT_TRANSLATE_NOOP("MythControls",
676  "Swap PBP/PIP Windows"), "N");
677  REG_KEY("TV Playback", "TOGGLEPIPSTATE", QT_TRANSLATE_NOOP("MythControls",
678  "Change PxP view"), "");
679  REG_KEY("TV Playback", ACTION_BOTTOMLINEMOVE,
680  QT_TRANSLATE_NOOP("MythControls", "Move BottomLine off screen"),
681  "L");
682  REG_KEY("TV Playback", ACTION_BOTTOMLINESAVE,
683  QT_TRANSLATE_NOOP("MythControls", "Save manual zoom for BottomLine"),
684  ""),
685  REG_KEY("TV Playback", "TOGGLEASPECT", QT_TRANSLATE_NOOP("MythControls",
686  "Toggle the video aspect ratio"), "Ctrl+W");
687  REG_KEY("TV Playback", "TOGGLEFILL", QT_TRANSLATE_NOOP("MythControls",
688  "Next Preconfigured Zoom mode"), "W");
689  REG_KEY("TV Playback", ACTION_TOGGLESUBS, QT_TRANSLATE_NOOP("MythControls",
690  "Toggle any captions"), "T");
691  REG_KEY("TV Playback", ACTION_ENABLESUBS, QT_TRANSLATE_NOOP("MythControls",
692  "Enable any captions"), "");
693  REG_KEY("TV Playback", ACTION_DISABLESUBS, QT_TRANSLATE_NOOP("MythControls",
694  "Disable any captions"), "");
695  REG_KEY("TV Playback", "TOGGLETTC", QT_TRANSLATE_NOOP("MythControls",
696  "Toggle Teletext Captions"),"");
697  REG_KEY("TV Playback", "TOGGLESUBTITLE", QT_TRANSLATE_NOOP("MythControls",
698  "Toggle Subtitles"), "");
699  REG_KEY("TV Playback", "TOGGLECC608", QT_TRANSLATE_NOOP("MythControls",
700  "Toggle VBI CC"), "");
701  REG_KEY("TV Playback", "TOGGLECC708", QT_TRANSLATE_NOOP("MythControls",
702  "Toggle ATSC CC"), "");
703  REG_KEY("TV Playback", "TOGGLETTM", QT_TRANSLATE_NOOP("MythControls",
704  "Toggle Teletext Menu"), "");
705  REG_KEY("TV Playback", ACTION_TOGGLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
706  "Toggle External Subtitles"), "");
707  REG_KEY("TV Playback", ACTION_ENABLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
708  "Enable External Subtitles"), "");
709  REG_KEY("TV Playback", ACTION_DISABLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
710  "Disable External Subtitles"), "");
711  REG_KEY("TV Playback", "TOGGLERAWTEXT", QT_TRANSLATE_NOOP("MythControls",
712  "Toggle Text Subtitles"), "");
713 
714  REG_KEY("TV Playback", "SELECTAUDIO_0", QT_TRANSLATE_NOOP("MythControls",
715  "Play audio track 1"), "");
716  REG_KEY("TV Playback", "SELECTAUDIO_1", QT_TRANSLATE_NOOP("MythControls",
717  "Play audio track 2"), "");
718  REG_KEY("TV Playback", "SELECTSUBTITLE_0",QT_TRANSLATE_NOOP("MythControls",
719  "Display subtitle 1"), "");
720  REG_KEY("TV Playback", "SELECTSUBTITLE_1",QT_TRANSLATE_NOOP("MythControls",
721  "Display subtitle 2"), "");
722  REG_KEY("TV Playback", "SELECTRAWTEXT_0",QT_TRANSLATE_NOOP("MythControls",
723  "Display Text Subtitle 1"), "");
724  REG_KEY("TV Playback", "SELECTCC608_0", QT_TRANSLATE_NOOP("MythControls",
725  "Display VBI CC1"), "");
726  REG_KEY("TV Playback", "SELECTCC608_1", QT_TRANSLATE_NOOP("MythControls",
727  "Display VBI CC2"), "");
728  REG_KEY("TV Playback", "SELECTCC608_2", QT_TRANSLATE_NOOP("MythControls",
729  "Display VBI CC3"), "");
730  REG_KEY("TV Playback", "SELECTCC608_3", QT_TRANSLATE_NOOP("MythControls",
731  "Display VBI CC4"), "");
732  REG_KEY("TV Playback", "SELECTCC708_0", QT_TRANSLATE_NOOP("MythControls",
733  "Display ATSC CC1"), "");
734  REG_KEY("TV Playback", "SELECTCC708_1", QT_TRANSLATE_NOOP("MythControls",
735  "Display ATSC CC2"), "");
736  REG_KEY("TV Playback", "SELECTCC708_2", QT_TRANSLATE_NOOP("MythControls",
737  "Display ATSC CC3"), "");
738  REG_KEY("TV Playback", "SELECTCC708_3", QT_TRANSLATE_NOOP("MythControls",
739  "Display ATSC CC4"), "");
740  REG_KEY("TV Playback", ACTION_ENABLEFORCEDSUBS, QT_TRANSLATE_NOOP("MythControls",
741  "Enable Forced Subtitles"), "");
742  REG_KEY("TV Playback", ACTION_DISABLEFORCEDSUBS, QT_TRANSLATE_NOOP("MythControls",
743  "Disable Forced Subtitles"), "");
744 
745  REG_KEY("TV Playback", "NEXTAUDIO", QT_TRANSLATE_NOOP("MythControls",
746  "Next audio track"), "+");
747  REG_KEY("TV Playback", "PREVAUDIO", QT_TRANSLATE_NOOP("MythControls",
748  "Previous audio track"), "-");
749  REG_KEY("TV Playback", "NEXTSUBTITLE", QT_TRANSLATE_NOOP("MythControls",
750  "Next subtitle track"), "");
751  REG_KEY("TV Playback", "PREVSUBTITLE", QT_TRANSLATE_NOOP("MythControls",
752  "Previous subtitle track"), "");
753  REG_KEY("TV Playback", "NEXTRAWTEXT", QT_TRANSLATE_NOOP("MythControls",
754  "Next Text track"), "");
755  REG_KEY("TV Playback", "PREVRAWTEXT", QT_TRANSLATE_NOOP("MythControls",
756  "Previous Text track"), "");
757  REG_KEY("TV Playback", "NEXTCC608", QT_TRANSLATE_NOOP("MythControls",
758  "Next VBI CC track"), "");
759  REG_KEY("TV Playback", "PREVCC608", QT_TRANSLATE_NOOP("MythControls",
760  "Previous VBI CC track"), "");
761  REG_KEY("TV Playback", "NEXTCC708", QT_TRANSLATE_NOOP("MythControls",
762  "Next ATSC CC track"), "");
763  REG_KEY("TV Playback", "PREVCC708", QT_TRANSLATE_NOOP("MythControls",
764  "Previous ATSC CC track"), "");
765  REG_KEY("TV Playback", "NEXTCC", QT_TRANSLATE_NOOP("MythControls",
766  "Next of any captions"), "");
767 
768  REG_KEY("TV Playback", "NEXTSCAN", QT_TRANSLATE_NOOP("MythControls",
769  "Next video scan overidemode"), "");
770  REG_KEY("TV Playback", "QUEUETRANSCODE", QT_TRANSLATE_NOOP("MythControls",
771  "Queue the current recording for transcoding"), "X");
772  REG_KEY("TV Playback", "SPEEDINC", QT_TRANSLATE_NOOP("MythControls",
773  "Increase the playback speed"), "U");
774  REG_KEY("TV Playback", "SPEEDDEC", QT_TRANSLATE_NOOP("MythControls",
775  "Decrease the playback speed"), "J");
776  REG_KEY("TV Playback", "ADJUSTSTRETCH", QT_TRANSLATE_NOOP("MythControls",
777  "Turn on time stretch control"), "A");
778  REG_KEY("TV Playback", "STRETCHINC", QT_TRANSLATE_NOOP("MythControls",
779  "Increase time stretch speed"), "");
780  REG_KEY("TV Playback", "STRETCHDEC", QT_TRANSLATE_NOOP("MythControls",
781  "Decrease time stretch speed"), "");
782  REG_KEY("TV Playback", "TOGGLESTRETCH", QT_TRANSLATE_NOOP("MythControls",
783  "Toggle time stretch speed"), "");
784  REG_KEY("TV Playback", ACTION_TOGGELAUDIOSYNC,
785  QT_TRANSLATE_NOOP("MythControls",
786  "Turn on audio sync adjustment controls"), "");
787  REG_KEY("TV Playback", ACTION_SETAUDIOSYNC,
788  QT_TRANSLATE_NOOP("MythControls",
789  "Set the audio sync adjustment"), "");
790  REG_KEY("TV Playback", "TOGGLEPICCONTROLS",
791  QT_TRANSLATE_NOOP("MythControls", "Playback picture adjustments"),
792  "F");
793  REG_KEY("TV Playback", ACTION_TOGGLENIGHTMODE,
794  QT_TRANSLATE_NOOP("MythControls", "Toggle night mode"), "Ctrl+F");
795  REG_KEY("TV Playback", ACTION_SETBRIGHTNESS,
796  QT_TRANSLATE_NOOP("MythControls", "Set the picture brightness"), "");
797  REG_KEY("TV Playback", ACTION_SETCONTRAST,
798  QT_TRANSLATE_NOOP("MythControls", "Set the picture contrast"), "");
799  REG_KEY("TV Playback", ACTION_SETCOLOUR,
800  QT_TRANSLATE_NOOP("MythControls", "Set the picture color"), "");
801  REG_KEY("TV Playback", ACTION_SETHUE,
802  QT_TRANSLATE_NOOP("MythControls", "Set the picture hue"), "");
803  REG_KEY("TV Playback", ACTION_TOGGLESTUDIOLEVELS,
804  QT_TRANSLATE_NOOP("MythControls", "Playback picture adjustments"),
805  "");
806  REG_KEY("TV Playback", ACTION_TOGGLECHANCONTROLS,
807  QT_TRANSLATE_NOOP("MythControls", "Recording picture adjustments "
808  "for this channel"), "Ctrl+G");
809  REG_KEY("TV Playback", ACTION_TOGGLERECCONTROLS,
810  QT_TRANSLATE_NOOP("MythControls", "Recording picture adjustments "
811  "for this recorder"), "G");
812  REG_KEY("TV Playback", "CYCLECOMMSKIPMODE",
813  QT_TRANSLATE_NOOP("MythControls", "Cycle Commercial Skip mode"),
814  "");
815  REG_KEY("TV Playback", ACTION_GUIDE, QT_TRANSLATE_NOOP("MythControls",
816  "Show the Program Guide"), "S");
817  REG_KEY("TV Playback", ACTION_FINDER, QT_TRANSLATE_NOOP("MythControls",
818  "Show the Program Finder"), "#");
819  REG_KEY("TV Playback", ACTION_TOGGLESLEEP, QT_TRANSLATE_NOOP("MythControls",
820  "Toggle the Sleep Timer"), "F8");
821  REG_KEY("TV Playback", ACTION_PLAY, QT_TRANSLATE_NOOP("MythControls", "Play"),
822  "Ctrl+P");
823  REG_KEY("TV Playback", ACTION_JUMPPREV, QT_TRANSLATE_NOOP("MythControls",
824  "Jump to previously played recording"), "");
825  REG_KEY("TV Playback", ACTION_JUMPREC, QT_TRANSLATE_NOOP("MythControls",
826  "Display menu of recorded programs to jump to"), "");
827  REG_KEY("TV Playback", ACTION_VIEWSCHEDULED, QT_TRANSLATE_NOOP("MythControls",
828  "Display scheduled recording list"), "");
829  REG_KEY("TV Playback", ACTION_PREVRECORDED, QT_TRANSLATE_NOOP("MythControls",
830  "Display previously recorded episodes"), "");
831  REG_KEY("TV Playback", ACTION_SIGNALMON, QT_TRANSLATE_NOOP("MythControls",
832  "Monitor Signal Quality"), "Alt+F7");
833  REG_KEY("TV Playback", ACTION_JUMPTODVDROOTMENU,
834  QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Root Menu"), "");
835  REG_KEY("TV Playback", ACTION_JUMPTOPOPUPMENU,
836  QT_TRANSLATE_NOOP("MythControls", "Jump to the Popup Menu"), "");
837  REG_KEY("TV Playback", ACTION_JUMPTODVDCHAPTERMENU,
838  QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Chapter Menu"), "");
839  REG_KEY("TV Playback", ACTION_JUMPTODVDTITLEMENU,
840  QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Title Menu"), "");
841  REG_KEY("TV Playback", ACTION_EXITSHOWNOPROMPTS,
842  QT_TRANSLATE_NOOP("MythControls", "Exit Show without any prompts"),
843  "");
844  REG_KEY("TV Playback", ACTION_JUMPCHAPTER, QT_TRANSLATE_NOOP("MythControls",
845  "Jump to a chapter"), "");
846  REG_KEY("TV Playback", ACTION_SWITCHTITLE, QT_TRANSLATE_NOOP("MythControls",
847  "Switch title"), "");
848  REG_KEY("TV Playback", ACTION_SWITCHANGLE, QT_TRANSLATE_NOOP("MythControls",
849  "Switch angle"), "");
850  REG_KEY("TV Playback", ACTION_OSDNAVIGATION, QT_TRANSLATE_NOOP("MythControls",
851  "OSD Navigation"), "");
852  REG_KEY("TV Playback", ACTION_ZOOMUP, QT_TRANSLATE_NOOP("MythControls",
853  "Zoom mode - shift up"), "");
854  REG_KEY("TV Playback", ACTION_ZOOMDOWN, QT_TRANSLATE_NOOP("MythControls",
855  "Zoom mode - shift down"), "");
856  REG_KEY("TV Playback", ACTION_ZOOMLEFT, QT_TRANSLATE_NOOP("MythControls",
857  "Zoom mode - shift left"), "");
858  REG_KEY("TV Playback", ACTION_ZOOMRIGHT, QT_TRANSLATE_NOOP("MythControls",
859  "Zoom mode - shift right"), "");
860  REG_KEY("TV Playback", ACTION_ZOOMASPECTUP,
861  QT_TRANSLATE_NOOP("MythControls",
862  "Zoom mode - increase aspect ratio"), "");
863  REG_KEY("TV Playback", ACTION_ZOOMASPECTDOWN,
864  QT_TRANSLATE_NOOP("MythControls",
865  "Zoom mode - decrease aspect ratio"), "");
866  REG_KEY("TV Playback", ACTION_ZOOMIN, QT_TRANSLATE_NOOP("MythControls",
867  "Zoom mode - zoom in"), "");
868  REG_KEY("TV Playback", ACTION_ZOOMOUT, QT_TRANSLATE_NOOP("MythControls",
869  "Zoom mode - zoom out"), "");
870  REG_KEY("TV Playback", ACTION_ZOOMVERTICALIN,
871  QT_TRANSLATE_NOOP("MythControls",
872  "Zoom mode - vertical zoom in"), "8");
873  REG_KEY("TV Playback", ACTION_ZOOMVERTICALOUT,
874  QT_TRANSLATE_NOOP("MythControls",
875  "Zoom mode - vertical zoom out"), "2");
876  REG_KEY("TV Playback", ACTION_ZOOMHORIZONTALIN,
877  QT_TRANSLATE_NOOP("MythControls",
878  "Zoom mode - horizontal zoom in"), "6");
879  REG_KEY("TV Playback", ACTION_ZOOMHORIZONTALOUT,
880  QT_TRANSLATE_NOOP("MythControls",
881  "Zoom mode - horizontal zoom out"), "4");
882  REG_KEY("TV Playback", ACTION_ZOOMQUIT, QT_TRANSLATE_NOOP("MythControls",
883  "Zoom mode - quit and abandon changes"), "");
884  REG_KEY("TV Playback", ACTION_ZOOMCOMMIT, QT_TRANSLATE_NOOP("MythControls",
885  "Zoom mode - commit changes"), "");
886 
887  /* Interactive Television keys */
888  REG_KEY("TV Playback", ACTION_MENURED, QT_TRANSLATE_NOOP("MythControls",
889  "Menu Red"), "F2");
890  REG_KEY("TV Playback", ACTION_MENUGREEN, QT_TRANSLATE_NOOP("MythControls",
891  "Menu Green"), "F3");
892  REG_KEY("TV Playback", ACTION_MENUYELLOW, QT_TRANSLATE_NOOP("MythControls",
893  "Menu Yellow"), "F4");
894  REG_KEY("TV Playback", ACTION_MENUBLUE, QT_TRANSLATE_NOOP("MythControls",
895  "Menu Blue"), "F5");
896  REG_KEY("TV Playback", ACTION_TEXTEXIT, QT_TRANSLATE_NOOP("MythControls",
897  "Menu Exit"), "F6");
898  REG_KEY("TV Playback", ACTION_MENUTEXT, QT_TRANSLATE_NOOP("MythControls",
899  "Menu Text"), "F7");
900  REG_KEY("TV Playback", ACTION_MENUEPG, QT_TRANSLATE_NOOP("MythControls",
901  "Menu EPG"), "F12");
902 
903  /* Editing keys */
904  REG_KEY("TV Editing", ACTION_CLEARMAP, QT_TRANSLATE_NOOP("MythControls",
905  "Clear editing cut points"), "C,Q,Home");
906  REG_KEY("TV Editing", ACTION_INVERTMAP, QT_TRANSLATE_NOOP("MythControls",
907  "Invert Begin/End cut points"),"I");
908  REG_KEY("TV Editing", ACTION_SAVEMAP, QT_TRANSLATE_NOOP("MythControls",
909  "Save cuts"),"");
910  REG_KEY("TV Editing", ACTION_LOADCOMMSKIP,QT_TRANSLATE_NOOP("MythControls",
911  "Load cuts from detected commercials"), "Z,End");
912  REG_KEY("TV Editing", ACTION_NEXTCUT, QT_TRANSLATE_NOOP("MythControls",
913  "Jump to the next cut point"), "PgDown");
914  REG_KEY("TV Editing", ACTION_PREVCUT, QT_TRANSLATE_NOOP("MythControls",
915  "Jump to the previous cut point"), "PgUp");
916  REG_KEY("TV Editing", ACTION_BIGJUMPREW, QT_TRANSLATE_NOOP("MythControls",
917  "Jump back 10x the normal amount"), ",,<");
918  REG_KEY("TV Editing", ACTION_BIGJUMPFWD, QT_TRANSLATE_NOOP("MythControls",
919  "Jump forward 10x the normal amount"), ">,.");
920  REG_KEY("TV Editing", ACTION_MENUCOMPACT, QT_TRANSLATE_NOOP("MythControls",
921  "Cut point editor compact menu"), "Alt+M");
922 
923  /* Teletext keys */
924  REG_KEY("Teletext Menu", ACTION_NEXTPAGE, QT_TRANSLATE_NOOP("MythControls",
925  "Next Page"), "Down");
926  REG_KEY("Teletext Menu", ACTION_PREVPAGE, QT_TRANSLATE_NOOP("MythControls",
927  "Previous Page"), "Up");
928  REG_KEY("Teletext Menu", ACTION_NEXTSUBPAGE, QT_TRANSLATE_NOOP("MythControls",
929  "Next Subpage"), "Right");
930  REG_KEY("Teletext Menu", ACTION_PREVSUBPAGE, QT_TRANSLATE_NOOP("MythControls",
931  "Previous Subpage"), "Left");
932  REG_KEY("Teletext Menu", ACTION_TOGGLETT, QT_TRANSLATE_NOOP("MythControls",
933  "Toggle Teletext"), "T");
934  REG_KEY("Teletext Menu", ACTION_MENURED, QT_TRANSLATE_NOOP("MythControls",
935  "Menu Red"), "F2");
936  REG_KEY("Teletext Menu", ACTION_MENUGREEN, QT_TRANSLATE_NOOP("MythControls",
937  "Menu Green"), "F3");
938  REG_KEY("Teletext Menu", ACTION_MENUYELLOW, QT_TRANSLATE_NOOP("MythControls",
939  "Menu Yellow"), "F4");
940  REG_KEY("Teletext Menu", ACTION_MENUBLUE, QT_TRANSLATE_NOOP("MythControls",
941  "Menu Blue"), "F5");
942  REG_KEY("Teletext Menu", ACTION_MENUWHITE, QT_TRANSLATE_NOOP("MythControls",
943  "Menu White"), "F6");
944  REG_KEY("Teletext Menu", ACTION_TOGGLEBACKGROUND,
945  QT_TRANSLATE_NOOP("MythControls", "Toggle Background"), "F7");
946  REG_KEY("Teletext Menu", ACTION_REVEAL, QT_TRANSLATE_NOOP("MythControls",
947  "Reveal hidden Text"), "F8");
948 
949  /* Visualisations */
950  REG_KEY("TV Playback", ACTION_TOGGLEVISUALISATION,
951  QT_TRANSLATE_NOOP("MythControls", "Toggle audio visualisation"), "");
952 
953  /* OSD playback information screen */
954  REG_KEY("TV Playback", ACTION_TOGGLEOSDDEBUG,
955  QT_TRANSLATE_NOOP("MythControls", "Toggle OSD playback information"), "");
956 
957  /* 3D/Frame compatible/Stereoscopic TV */
958  REG_KEY("TV Playback", ACTION_3DNONE,
959  QT_TRANSLATE_NOOP("MythControls", "No 3D"), "");
960  REG_KEY("TV Playback", ACTION_3DSIDEBYSIDE,
961  QT_TRANSLATE_NOOP("MythControls", "3D Side by Side"), "");
962  REG_KEY("TV Playback", ACTION_3DSIDEBYSIDEDISCARD,
963  QT_TRANSLATE_NOOP("MythControls", "Discard 3D Side by Side"), "");
964  REG_KEY("TV Playback", ACTION_3DTOPANDBOTTOM,
965  QT_TRANSLATE_NOOP("MythControls", "3D Top and Bottom"), "");
966  REG_KEY("TV Playback", ACTION_3DTOPANDBOTTOMDISCARD,
967  QT_TRANSLATE_NOOP("MythControls", "Discard 3D Top and Bottom"), "");
968 
969 /*
970  keys already used:
971 
972  Global: I M 0123456789
973  Playback: ABCDEFGH JK NOPQRSTUVWXYZ
974  Frontend: CD OP R U XY 01 3 7 9
975  Editing: C E I Q Z
976  Teletext: T
977 
978  Playback: <>,.?/|[]{}\+-*#^
979  Frontend: <>,.?/
980  Editing: <>,.
981 
982  Global: PgDown, PgUp, Right, Left, Home, End, Up, Down,
983  Playback: PgDown, PgUp, Right, Left, Home, End, Up, Down, Backspace,
984  Frontend: Right, Left, Home, End
985  Editing: PgDown, PgUp, Home, End
986  Teletext: Right, Left, Up, Down,
987 
988  Global: Return, Enter, Space, Esc
989 
990  Global: F1,
991  Playback: F7,F8,F9,F10,F11
992  Teletext F2,F3,F4,F5,F6,F7,F8
993  ITV F2,F3,F4,F5,F6,F7,F12
994 
995  Playback: Ctrl-B,Ctrl-G,Ctrl-Y,Ctrl-U,L
996 */
997 }
998 
999 void TV::ReloadKeys(void)
1000 {
1001  MythMainWindow *mainWindow = GetMythMainWindow();
1002  mainWindow->ClearKeyContext("TV Frontend");
1003  mainWindow->ClearKeyContext("TV Playback");
1004  mainWindow->ClearKeyContext("TV Editing");
1005  mainWindow->ClearKeyContext("Teletext Menu");
1006  InitKeys();
1007 }
1008 
1012 TV::TV(void)
1013 {
1014  LOG(VB_GENERAL, LOG_INFO, LOC + "Creating TV object");
1015  m_ctorTime.start();
1016 
1017  setObjectName("TV");
1018  m_keyRepeatTimer.start();
1019 
1020  m_sleepTimes.emplace_back(tr("Off", "Sleep timer"), 0);
1021  m_sleepTimes.emplace_back(tr("30m", "Sleep timer"), 30*60);
1022  m_sleepTimes.emplace_back(tr("1h", "Sleep timer"), 60*60);
1023  m_sleepTimes.emplace_back(tr("1h30m", "Sleep timer"), 90*60);
1024  m_sleepTimes.emplace_back(tr("2h", "Sleep timer"), 120*60);
1025 
1026  m_playerLock.lockForWrite();
1027  m_player.push_back(new PlayerContext(kPlayerInUseID));
1028  m_playerActive = 0;
1029  m_playerLock.unlock();
1030 
1031  InitFromDB();
1032 
1033 #ifdef Q_OS_ANDROID
1034  connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
1035  this, SLOT(onApplicationStateChange(Qt::ApplicationState)));
1036 #endif
1037 
1038  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Finished creating TV object");
1039 }
1040 
1041 void TV::InitFromDB(void)
1042 {
1043  QMap<QString,QString> kv;
1044  kv["LiveTVIdleTimeout"] = "0";
1045  kv["BrowseMaxForward"] = "240";
1046  kv["PlaybackExitPrompt"] = "0";
1047  kv["AutomaticSetWatched"] = "0";
1048  kv["EndOfRecordingExitPrompt"] = "0";
1049  kv["JumpToProgramOSD"] = "1";
1050  kv["GuiSizeForTV"] = "0";
1051  kv["ClearSavedPosition"] = "1";
1052  kv["JobsRunOnRecordHost"] = "0";
1053  kv["ContinueEmbeddedTVPlay"] = "0";
1054  kv["UseFixedWindowSize"] = "1";
1055  kv["PersistentBrowseMode"] = "0";
1056  kv["BrowseAllTuners"] = "0";
1057  kv["ChannelOrdering"] = "channum";
1058 
1059  kv["CustomFilters"] = "";
1060  kv["ChannelFormat"] = "<num> <sign>";
1061 
1062  kv["TryUnflaggedSkip"] = "0";
1063 
1064  kv["ChannelGroupDefault"] = "-1";
1065  kv["BrowseChannelGroup"] = "0";
1066  kv["SmartForward"] = "0";
1067  kv["FFRewReposTime"] = "100";
1068  kv["FFRewReverse"] = "1";
1069 
1070  kv["BrowseChannelGroup"] = "0";
1071  kv["ChannelGroupDefault"] = "-1";
1072  kv["ChannelGroupRememberLast"] = "0";
1073 
1074  kv["VbiFormat"] = "";
1075  kv["DecodeVBIFormat"] = "";
1076 
1077  // these need exactly 12 items, comma cant be used as it is the delimiter
1078  kv["PlaybackScreenPressKeyMap"] = "P,Up,Z,],Left,Return,Return,Right,A,Down,Q,[";
1079  kv["LiveTVScreenPressKeyMap"] = "P,Up,Z,S,Left,Return,Return,Right,A,Down,Q,F";
1080 
1081  int ff_rew_def[8] = { 3, 5, 10, 20, 30, 60, 120, 180 };
1082  for (size_t i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
1083  kv[QString("FFRewSpeed%1").arg(i)] = QString::number(ff_rew_def[i]);
1084 
1085  MythDB::getMythDB()->GetSettings(kv);
1086 
1087  m_screenPressKeyMapPlayback = ConvertScreenPressKeyMap(kv["PlaybackScreenPressKeyMap"]);
1088  m_screenPressKeyMapLiveTV = ConvertScreenPressKeyMap(kv["LiveTVScreenPressKeyMap"]);
1089 
1090  QString db_channel_ordering;
1091  uint db_browse_max_forward;
1092 
1093  // convert from minutes to ms.
1094  m_dbIdleTimeout = kv["LiveTVIdleTimeout"].toInt() * 60 * 1000;
1095  db_browse_max_forward = kv["BrowseMaxForward"].toInt() * 60;
1096  m_dbPlaybackExitPrompt = kv["PlaybackExitPrompt"].toInt();
1097  m_dbAutoSetWatched = (kv["AutomaticSetWatched"].toInt() != 0);
1098  m_dbEndOfRecExitPrompt = (kv["EndOfRecordingExitPrompt"].toInt() != 0);
1099  m_dbJumpPreferOsd = (kv["JumpToProgramOSD"].toInt() != 0);
1100  m_dbUseGuiSizeForTv = (kv["GuiSizeForTV"].toInt() != 0);
1101  m_dbClearSavedPosition = (kv["ClearSavedPosition"].toInt() != 0);
1102  m_dbRunJobsOnRemote = (kv["JobsRunOnRecordHost"].toInt() != 0);
1103  m_dbContinueEmbedded = (kv["ContinueEmbeddedTVPlay"].toInt() != 0);
1104  m_dbUseFixedSize = (kv["UseFixedWindowSize"].toInt() != 0);
1105  m_dbBrowseAlways = (kv["PersistentBrowseMode"].toInt() != 0);
1106  m_dbBrowseAllTuners = (kv["BrowseAllTuners"].toInt() != 0);
1107  db_channel_ordering = kv["ChannelOrdering"];
1108  m_baseFilters += kv["CustomFilters"];
1109  m_dbChannelFormat = kv["ChannelFormat"];
1110  m_tryUnflaggedSkip = (kv["TryUnflaggedSkip"].toInt() != 0);
1111  m_smartForward = (kv["SmartForward"].toInt() != 0);
1112  m_ffRewRepos = kv["FFRewReposTime"].toFloat() * 0.01F;
1113  m_ffRewReverse = (kv["FFRewReverse"].toInt() != 0);
1114 
1115  m_dbUseChannelGroups = (kv["BrowseChannelGroup"].toInt() != 0);
1116  m_dbRememberLastChannelGroup = (kv["ChannelGroupRememberLast"].toInt() != 0);
1117  m_channelGroupId = kv["ChannelGroupDefault"].toInt();
1118 
1119  QString beVBI = kv["VbiFormat"];
1120  QString feVBI = kv["DecodeVBIFormat"];
1121 
1122  RecordingRule record;
1123  record.LoadTemplate("Default");
1124  m_dbAutoexpireDefault = record.m_autoExpire;
1125 
1126  if (m_dbUseChannelGroups)
1127  {
1128  m_dbChannelGroups = ChannelGroup::GetChannelGroups();
1129  if (m_channelGroupId > -1)
1130  {
1131  m_channelGroupChannelList = ChannelUtil::GetChannels(
1132  0, true, "channum, callsign", m_channelGroupId);
1134  m_channelGroupChannelList, "channum", true);
1135  }
1136  }
1137 
1138  for (size_t i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
1139  m_ffRewSpeeds.push_back(kv[QString("FFRewSpeed%1").arg(i)].toInt());
1140 
1141  // process it..
1142  m_browseHelper = new TVBrowseHelper(
1143  this,
1144  db_browse_max_forward, m_dbBrowseAllTuners,
1145  m_dbUseChannelGroups, db_channel_ordering);
1146 
1147  m_vbimode = VBIMode::Parse(!feVBI.isEmpty() ? feVBI : beVBI);
1148 
1149  gCoreContext->addListener(this);
1150  gCoreContext->RegisterForPlayback(this, SLOT(StopPlayback()));
1151 
1152  QMutexLocker lock(&m_initFromDBLock);
1153  m_initFromDBDone = true;
1154  m_initFromDBWait.wakeAll();
1155 }
1156 
1163 bool TV::Init(bool createWindow)
1164 {
1165  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
1166 
1167  if (createWindow)
1168  {
1169  bool fullscreen = !gCoreContext->GetBoolSetting("GuiSizeForTV", false);
1170  bool switchMode = gCoreContext->GetBoolSetting("UseVideoModes", false);
1171 
1172  m_savedGuiBounds = QRect(GetMythMainWindow()->geometry().topLeft(),
1173  GetMythMainWindow()->size());
1174 
1175  // adjust for window manager wierdness.
1176  {
1177  int xbase, width, ybase, height;
1178  float wmult, hmult;
1179  GetMythUI()->GetScreenSettings(xbase, width, wmult,
1180  ybase, height, hmult);
1181  if ((abs(m_savedGuiBounds.x()-xbase) < 3) &&
1182  (abs(m_savedGuiBounds.y()-ybase) < 3))
1183  {
1184  m_savedGuiBounds = QRect(QPoint(xbase, ybase),
1185  GetMythMainWindow()->size());
1186  }
1187  }
1188 
1189  // if width && height are zero users expect fullscreen playback
1190  if (!fullscreen)
1191  {
1192  int gui_width = 0, gui_height = 0;
1193  gCoreContext->GetResolutionSetting("Gui", gui_width, gui_height);
1194  fullscreen |= (0 == gui_width && 0 == gui_height);
1195  }
1196 
1197  m_playerBounds = m_savedGuiBounds;
1198  if (fullscreen)
1199  {
1200  int xbase, width, ybase, height;
1201  GetMythUI()->GetScreenBounds(xbase, ybase, width, height);
1202  m_playerBounds = QRect(xbase, ybase, width, height);
1203  }
1204 
1205  // main window sizing
1206  if (switchMode)
1207  {
1208  DisplayRes *display_res = DisplayRes::GetDisplayRes();
1209  if(display_res)
1210  {
1211  int maxWidth = 3840, maxHeight = 2160;
1212 
1213  // The very first Resize needs to be the maximum possible
1214  // desired res, because X will mask off anything outside
1215  // the initial dimensions
1216  maxWidth = display_res->GetMaxWidth();
1217  maxHeight = display_res->GetMaxHeight();
1218 
1219  // bit of a hack, but it's ok if the window is too
1220  // big in fullscreen mode
1221  if (fullscreen)
1222  {
1223  m_playerBounds.setSize(QSize(maxWidth, maxHeight));
1224 
1225  // resize possibly avoids a bug on some systems
1226  GetMythMainWindow()->setGeometry(m_playerBounds);
1227  GetMythMainWindow()->ResizePainterWindow(m_playerBounds.size());
1228  }
1229  }
1230  }
1231 
1232  // player window sizing
1234 
1235  m_myWindow = new TvPlayWindow(mainStack, "Playback");
1236 
1237  if (m_myWindow->Create())
1238  {
1239  mainStack->AddScreen(m_myWindow, false);
1240  LOG(VB_GENERAL, LOG_INFO, LOC + "Created TvPlayWindow.");
1241  }
1242  else
1243  {
1244  delete m_myWindow;
1245  m_myWindow = nullptr;
1246  }
1247 
1248  MythMainWindow *mainWindow = GetMythMainWindow();
1249  if (mainWindow->GetPaintWindow())
1250  mainWindow->GetPaintWindow()->update();
1251  mainWindow->installEventFilter(this);
1252  qApp->processEvents();
1253  }
1254 
1255  {
1256  QMutexLocker locker(&m_initFromDBLock);
1257  while (!m_initFromDBDone)
1258  {
1259  qApp->processEvents();
1260  m_initFromDBWait.wait(&m_initFromDBLock, 50);
1261  }
1262  }
1263 
1264  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
1265  mctx->m_ffRewState = 0;
1266  mctx->m_ffRewIndex = kInitFFRWSpeed;
1267  mctx->m_ffRewSpeed = 0;
1268  mctx->m_tsNormal = 1.0F;
1269  ReturnPlayerLock(mctx);
1270 
1271  m_sleepIndex = 0;
1272 
1273  SetUpdateOSDPosition(false);
1274 
1275  const PlayerContext *ctx = GetPlayerReadLock(0, __FILE__, __LINE__);
1276  ClearInputQueues(ctx, false);
1277  ReturnPlayerLock(ctx);
1278 
1279  m_switchToRec = nullptr;
1280  SetExitPlayer(false, false);
1281 
1282  m_errorRecoveryTimerId = StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
1283  m_lcdTimerId = StartTimer(1, __LINE__);
1284  m_speedChangeTimerId = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
1285  m_saveLastPlayPosTimerId = StartTimer(kSaveLastPlayPosTimeout, __LINE__);
1286 
1287  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
1288  return true;
1289 }
1290 
1291 TV::~TV(void)
1292 {
1293  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
1294 
1295  if (m_browseHelper)
1296  m_browseHelper->Stop();
1297 
1300 
1301  if (GetMythMainWindow() && m_weDisabledGUI)
1303 
1304  if (m_myWindow)
1305  {
1306  m_myWindow->Close();
1307  m_myWindow = nullptr;
1308  }
1309 
1310  LOG(VB_PLAYBACK, LOG_INFO, LOC + "-- lock");
1311 
1312  // restore window to gui size and position
1314  mwnd->setGeometry(m_savedGuiBounds);
1315  mwnd->setFixedSize(m_savedGuiBounds.size());
1316  mwnd->ResizePainterWindow(m_savedGuiBounds.size());
1317 #ifdef Q_OS_ANDROID
1318  mwnd->Show();
1319 #else
1320  mwnd->show();
1321 #endif
1322  if (!m_dbUseGuiSizeForTv)
1323  mwnd->move(m_savedGuiBounds.topLeft());
1324 
1325  delete m_lastProgram;
1326 
1327  if (LCD *lcd = LCD::Get())
1328  {
1329  lcd->setFunctionLEDs(FUNC_TV, false);
1330  lcd->setFunctionLEDs(FUNC_MOVIE, false);
1331  lcd->switchToTime();
1332  }
1333 
1334  if (m_browseHelper)
1335  {
1336  delete m_browseHelper;
1337  m_browseHelper = nullptr;
1338  }
1339 
1340  PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
1341  while (!m_player.empty())
1342  {
1343  delete m_player.back();
1344  m_player.pop_back();
1345  }
1346  ReturnPlayerLock(mctx);
1347 
1348  if (m_browseHelper)
1349  {
1350  delete m_browseHelper;
1351  m_browseHelper = nullptr;
1352  }
1353 
1354  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
1355 }
1356 
1361 {
1362  while (true)
1363  {
1364  qApp->processEvents();
1366  {
1367  m_wantsToQuit = true;
1368  return;
1369  }
1370 
1371  TVState state = GetState(0);
1372  if ((kState_Error == state) || (kState_None == state))
1373  return;
1374 
1375  if (kState_ChangingState == state)
1376  continue;
1377 
1378  int count = m_player.size();
1379  int errorCount = 0;
1380  for (int i = 0; i < count; i++)
1381  {
1382  const PlayerContext *mctx = GetPlayerReadLock(i, __FILE__, __LINE__);
1383  if (mctx)
1384  {
1385  mctx->LockDeletePlayer(__FILE__, __LINE__);
1386  if (mctx->m_player && !mctx->m_player->IsErrored())
1387  {
1388  mctx->m_player->EventLoop();
1389  mctx->m_player->VideoLoop();
1390  }
1391  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
1392 
1393  if (mctx->m_errored || !mctx->m_player)
1394  errorCount++;
1395  }
1396  ReturnPlayerLock(mctx);
1397  }
1398 
1399  // break out of the loop if there are no valid players
1400  // or all PlayerContexts are errored
1401  if (errorCount == count)
1402  return;
1403  }
1404 }
1405 
1409 void TV::UpdateChannelList(int groupID)
1410 {
1411  if (!m_dbUseChannelGroups)
1412  return;
1413 
1414  QMutexLocker locker(&m_channelGroupLock);
1415  if (groupID == m_channelGroupId)
1416  return;
1417 
1418  ChannelInfoList list;
1419  if (groupID != -1)
1420  {
1421  list = ChannelUtil::GetChannels(
1422  0, true, "channum, callsign", groupID);
1423  ChannelUtil::SortChannels(list, "channum", true);
1424  }
1425 
1426  m_channelGroupId = groupID;
1427  m_channelGroupChannelList = list;
1428 
1429  if (m_dbRememberLastChannelGroup)
1430  gCoreContext->SaveSetting("ChannelGroupDefault", m_channelGroupId);
1431 }
1432 
1436 TVState TV::GetState(int player_idx) const
1437 {
1438  const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
1439  TVState ret = GetState(ctx);
1440  ReturnPlayerLock(ctx);
1441  return ret;
1442 }
1443 
1444 // XXX what about subtitlezoom?
1445 void TV::GetStatus(void)
1446 {
1447  QVariantMap status;
1448 
1449  const PlayerContext *ctx = GetPlayerReadLock(-1, __FILE__, __LINE__);
1450 
1451  status.insert("state", StateToString(GetState(ctx)));
1452  ctx->LockPlayingInfo(__FILE__, __LINE__);
1453  if (ctx->m_playingInfo)
1454  {
1455  status.insert("title", ctx->m_playingInfo->GetTitle());
1456  status.insert("subtitle", ctx->m_playingInfo->GetSubtitle());
1457  status.insert("starttime",
1459  .toUTC().toString("yyyy-MM-ddThh:mm:ssZ"));
1460  status.insert("chanid",
1461  QString::number(ctx->m_playingInfo->GetChanID()));
1462  status.insert("programid", ctx->m_playingInfo->GetProgramID());
1463  status.insert("pathname", ctx->m_playingInfo->GetPathname());
1464  }
1465  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
1466  osdInfo info;
1467  ctx->CalcPlayerSliderPosition(info);
1468  ctx->LockDeletePlayer(__FILE__, __LINE__);
1469  if (ctx->m_player)
1470  {
1471  if (!info.text["totalchapters"].isEmpty())
1472  {
1473  QList<long long> chapters;
1474  ctx->m_player->GetChapterTimes(chapters);
1475  QVariantList var;
1476  foreach (long long chapter, chapters)
1477  var << QVariant(chapter);
1478  status.insert("chaptertimes", var);
1479  }
1480 
1481  uint capmode = ctx->m_player->GetCaptionMode();
1482  QVariantMap tracks;
1483 
1484  QStringList list = ctx->m_player->GetTracks(kTrackTypeSubtitle);
1485  int currenttrack = -1;
1486  if (!list.isEmpty() && (kDisplayAVSubtitle == capmode))
1487  currenttrack = ctx->m_player->GetTrack(kTrackTypeSubtitle);
1488  for (int i = 0; i < list.size(); i++)
1489  {
1490  if (i == currenttrack)
1491  status.insert("currentsubtitletrack", list[i]);
1492  tracks.insert("SELECTSUBTITLE_" + QString::number(i), list[i]);
1493  }
1494 
1496  currenttrack = -1;
1497  if (!list.isEmpty() && (kDisplayTeletextCaptions == capmode))
1498  currenttrack = ctx->m_player->GetTrack(kTrackTypeTeletextCaptions);
1499  for (int i = 0; i < list.size(); i++)
1500  {
1501  if (i == currenttrack)
1502  status.insert("currentsubtitletrack", list[i]);
1503  tracks.insert("SELECTTTC_" + QString::number(i), list[i]);
1504  }
1505 
1506  list = ctx->m_player->GetTracks(kTrackTypeCC708);
1507  currenttrack = -1;
1508  if (!list.isEmpty() && (kDisplayCC708 == capmode))
1509  currenttrack = ctx->m_player->GetTrack(kTrackTypeCC708);
1510  for (int i = 0; i < list.size(); i++)
1511  {
1512  if (i == currenttrack)
1513  status.insert("currentsubtitletrack", list[i]);
1514  tracks.insert("SELECTCC708_" + QString::number(i), list[i]);
1515  }
1516 
1517  list = ctx->m_player->GetTracks(kTrackTypeCC608);
1518  currenttrack = -1;
1519  if (!list.isEmpty() && (kDisplayCC608 == capmode))
1520  currenttrack = ctx->m_player->GetTrack(kTrackTypeCC608);
1521  for (int i = 0; i < list.size(); i++)
1522  {
1523  if (i == currenttrack)
1524  status.insert("currentsubtitletrack", list[i]);
1525  tracks.insert("SELECTCC608_" + QString::number(i), list[i]);
1526  }
1527 
1528  list = ctx->m_player->GetTracks(kTrackTypeRawText);
1529  currenttrack = -1;
1530  if (!list.isEmpty() && (kDisplayRawTextSubtitle == capmode))
1531  currenttrack = ctx->m_player->GetTrack(kTrackTypeRawText);
1532  for (int i = 0; i < list.size(); i++)
1533  {
1534  if (i == currenttrack)
1535  status.insert("currentsubtitletrack", list[i]);
1536  tracks.insert("SELECTRAWTEXT_" + QString::number(i), list[i]);
1537  }
1538 
1539  if (ctx->m_player->HasTextSubtitles())
1540  {
1541  if (kDisplayTextSubtitle == capmode)
1542  status.insert("currentsubtitletrack", tr("External Subtitles"));
1543  tracks.insert(ACTION_ENABLEEXTTEXT, tr("External Subtitles"));
1544  }
1545 
1546  status.insert("totalsubtitletracks", tracks.size());
1547  if (!tracks.isEmpty())
1548  status.insert("subtitletracks", tracks);
1549 
1550  tracks.clear();
1551  list = ctx->m_player->GetTracks(kTrackTypeAudio);
1552  currenttrack = ctx->m_player->GetTrack(kTrackTypeAudio);
1553  for (int i = 0; i < list.size(); i++)
1554  {
1555  if (i == currenttrack)
1556  status.insert("currentaudiotrack", list[i]);
1557  tracks.insert("SELECTAUDIO_" + QString::number(i), list[i]);
1558  }
1559 
1560  status.insert("totalaudiotracks", tracks.size());
1561  if (!tracks.isEmpty())
1562  status.insert("audiotracks", tracks);
1563 
1564  status.insert("playspeed", ctx->m_player->GetPlaySpeed());
1565  status.insert("audiosyncoffset", (long long)ctx->m_player->GetAudioTimecodeOffset());
1566  if (ctx->m_player->GetAudio()->ControlsVolume())
1567  {
1568  status.insert("volume", ctx->m_player->GetVolume());
1569  status.insert("mute", ctx->m_player->GetMuteState());
1570  }
1571  if (ctx->m_player->GetVideoOutput())
1572  {
1573  VideoOutput *vo = ctx->m_player->GetVideoOutput();
1577  {
1578  status.insert("brightness",
1580  }
1582  {
1583  status.insert("contrast",
1585  }
1587  {
1588  status.insert("colour",
1590  }
1591  if (supp & kPictureAttributeSupported_Hue)
1592  {
1593  status.insert("hue",
1595  }
1597  {
1598  status.insert("studiolevels",
1600  }
1601  }
1602  }
1603  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
1604 
1605  ReturnPlayerLock(ctx);
1606 
1607  InfoMap::const_iterator tit =info.text.begin();
1608  for (; tit != info.text.end(); ++tit)
1609  {
1610  status.insert(tit.key(), tit.value());
1611  }
1612 
1613  QHashIterator<QString,int> vit(info.values);
1614  while (vit.hasNext())
1615  {
1616  vit.next();
1617  status.insert(vit.key(), vit.value());
1618  }
1619 
1621 }
1622 
1627 {
1629  if (!actx->InStateChange())
1630  ret = actx->GetState();
1631  return ret;
1632 }
1633 
1639 bool TV::LiveTV(bool showDialogs, const ChannelInfoList &selection)
1640 {
1641  m_requestDelete = false;
1642  m_allowRerecord = false;
1643  m_jumpToProgram = false;
1644 
1645  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
1646  if (actx->GetState() == kState_None &&
1647  RequestNextRecorder(actx, showDialogs, selection))
1648  {
1649  actx->SetInitialTVState(true);
1650  HandleStateChange(actx, actx);
1651  m_switchToRec = nullptr;
1652 
1653  // Start Idle Timer
1654  if (m_dbIdleTimeout > 0)
1655  {
1656  m_idleTimerId = StartTimer(m_dbIdleTimeout, __LINE__);
1657  LOG(VB_GENERAL, LOG_INFO, QString("Using Idle Timer. %1 minutes")
1658  .arg(m_dbIdleTimeout*(1.0F/60000.0F)));
1659  }
1660 
1661  ReturnPlayerLock(actx);
1662  return true;
1663  }
1664  ReturnPlayerLock(actx);
1665  return false;
1666 }
1667 
1668 int TV::GetLastRecorderNum(int player_idx) const
1669 {
1670  const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
1671  int ret = ctx->GetCardID();
1672  ReturnPlayerLock(ctx);
1673  return ret;
1674 }
1675 
1676 bool TV::RequestNextRecorder(PlayerContext *ctx, bool showDialogs,
1677  const ChannelInfoList &selection)
1678 {
1679  if (!ctx)
1680  return false;
1681 
1682  ctx->SetRecorder(nullptr);
1683 
1684  RemoteEncoder *testrec = nullptr;
1685  if (m_switchToRec)
1686  {
1687  // If this is set we, already got a new recorder in SwitchCards()
1688  testrec = m_switchToRec;
1689  m_switchToRec = nullptr;
1690  }
1691  else if (!selection.empty())
1692  {
1693  for (size_t i = 0; i < selection.size(); i++)
1694  {
1695  uint chanid = selection[i].m_chanid;
1696  QString channum = selection[i].m_channum;
1697  if (!chanid || channum.isEmpty())
1698  continue;
1699  QSet<uint> cards = IsTunableOn(ctx, chanid);
1700 
1701  if (chanid && !channum.isEmpty() && !cards.isEmpty())
1702  {
1703  testrec = RemoteGetExistingRecorder(*(cards.begin()));
1704  m_initialChanID = chanid;
1705  break;
1706  }
1707  }
1708  }
1709  else
1710  {
1711  // When starting LiveTV we just get the next free recorder
1712  testrec = RemoteRequestNextFreeRecorder(-1);
1713  }
1714 
1715  if (!testrec)
1716  return false;
1717 
1718  if (!testrec->IsValidRecorder())
1719  {
1720  if (showDialogs)
1721  ShowNoRecorderDialog(ctx);
1722 
1723  delete testrec;
1724 
1725  return false;
1726  }
1727 
1728  ctx->SetRecorder(testrec);
1729 
1730  return true;
1731 }
1732 
1733 void TV::FinishRecording(int player_ctx)
1734 {
1735  PlayerContext *ctx = GetPlayerReadLock(player_ctx, __FILE__, __LINE__);
1736  if (StateIsRecording(GetState(ctx)) && ctx->m_recorder)
1737  ctx->m_recorder->FinishRecording();
1738  ReturnPlayerLock(ctx);
1739 }
1740 
1742  const QStringList &msg, int timeuntil,
1743  bool hasrec, bool haslater)
1744 {
1745 #if 0
1746  LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording");
1747 #endif
1748  if (!StateIsLiveTV(GetState(ctx)))
1749  return;
1750 
1751  ProgramInfo *info = new ProgramInfo(msg);
1752  if (!info->GetChanID())
1753  {
1754  delete info;
1755  return;
1756  }
1757 
1758  QMutexLocker locker(&m_askAllowLock);
1759  QString key = info->MakeUniqueKey();
1760  if (timeuntil > 0)
1761  {
1762  // add program to list
1763 #if 0
1764  LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording -- " +
1765  QString("adding '%1'").arg(info->m_title));
1766 #endif
1767  QDateTime expiry = MythDate::current().addSecs(timeuntil);
1768  m_askAllowPrograms[key] = AskProgramInfo(expiry, hasrec, haslater, info);
1769  }
1770  else
1771  {
1772  // remove program from list
1773  LOG(VB_GENERAL, LOG_INFO, LOC + "-- " +
1774  QString("removing '%1'").arg(info->GetTitle()));
1775  QMap<QString,AskProgramInfo>::iterator it = m_askAllowPrograms.find(key);
1776  if (it != m_askAllowPrograms.end())
1777  {
1778  delete (*it).info;
1779  m_askAllowPrograms.erase(it);
1780  }
1781  delete info;
1782  }
1783 
1784  ShowOSDAskAllow(ctx);
1785 }
1786 
1788 {
1789  QMutexLocker locker(&m_askAllowLock);
1790  if (!ctx->m_recorder)
1791  return;
1792 
1793  uint cardid = ctx->GetCardID();
1794 
1795  QString single_rec =
1796  tr("MythTV wants to record \"%1\" on %2 in %d seconds. "
1797  "Do you want to:");
1798 
1799  QString record_watch = tr("Record and watch while it records");
1800  QString let_record1 = tr("Let it record and go back to the Main Menu");
1801  QString let_recordm = tr("Let them record and go back to the Main Menu");
1802  QString record_later1 = tr("Record it later, I want to watch TV");
1803  QString record_laterm = tr("Record them later, I want to watch TV");
1804  QString do_not_record1= tr("Don't let it record, I want to watch TV");
1805  QString do_not_recordm= tr("Don't let them record, I want to watch TV");
1806 
1807  // eliminate timed out programs
1808  QDateTime timeNow = MythDate::current();
1809  QMap<QString,AskProgramInfo>::iterator it = m_askAllowPrograms.begin();
1810  QMap<QString,AskProgramInfo>::iterator next = it;
1811  while (it != m_askAllowPrograms.end())
1812  {
1813  next = it; ++next;
1814  if ((*it).expiry <= timeNow)
1815  {
1816 #if 0
1817  LOG(VB_GENERAL, LOG_DEBUG, LOC + "-- " +
1818  QString("removing '%1'").arg((*it).info->m_title));
1819 #endif
1820  delete (*it).info;
1821  m_askAllowPrograms.erase(it);
1822  }
1823  it = next;
1824  }
1825  int timeuntil = 0;
1826  QString message;
1827  uint conflict_count = m_askAllowPrograms.size();
1828 
1829  it = m_askAllowPrograms.begin();
1830  if ((1 == m_askAllowPrograms.size()) && ((*it).info->GetInputID() == cardid))
1831  {
1832  (*it).is_in_same_input_group = (*it).is_conflicting = true;
1833  }
1834  else if (!m_askAllowPrograms.empty())
1835  {
1836  // get the currently used input on our card
1837  bool busy_input_grps_loaded = false;
1838  vector<uint> busy_input_grps;
1839  InputInfo busy_input;
1840  RemoteIsBusy(cardid, busy_input);
1841 
1842  // check if current input can conflict
1843  it = m_askAllowPrograms.begin();
1844  for (; it != m_askAllowPrograms.end(); ++it)
1845  {
1846  (*it).is_in_same_input_group =
1847  (cardid == (*it).info->GetInputID());
1848 
1849  if ((*it).is_in_same_input_group)
1850  continue;
1851 
1852  // is busy_input in same input group as recording
1853  if (!busy_input_grps_loaded)
1854  {
1855  busy_input_grps = CardUtil::GetInputGroups(busy_input.m_inputid);
1856  busy_input_grps_loaded = true;
1857  }
1858 
1859  vector<uint> input_grps =
1860  CardUtil::GetInputGroups((*it).info->GetInputID());
1861 
1862  for (size_t i = 0; i < input_grps.size(); i++)
1863  {
1864  if (find(busy_input_grps.begin(), busy_input_grps.end(),
1865  input_grps[i]) != busy_input_grps.end())
1866  {
1867  (*it).is_in_same_input_group = true;
1868  break;
1869  }
1870  }
1871  }
1872 
1873  // check if inputs that can conflict are ok
1874  conflict_count = 0;
1875  it = m_askAllowPrograms.begin();
1876  for (; it != m_askAllowPrograms.end(); ++it)
1877  {
1878  if (!(*it).is_in_same_input_group)
1879  (*it).is_conflicting = false;
1880  else if (cardid == (*it).info->GetInputID())
1881  (*it).is_conflicting = true;
1882  else if (!CardUtil::IsTunerShared(cardid, (*it).info->GetInputID()))
1883  (*it).is_conflicting = true;
1884  else if ((busy_input.m_mplexid &&
1885  (busy_input.m_mplexid == (*it).info->QueryMplexID())) ||
1886  (!busy_input.m_mplexid &&
1887  (busy_input.m_chanid == (*it).info->GetChanID())))
1888  (*it).is_conflicting = false;
1889  else
1890  (*it).is_conflicting = true;
1891 
1892  conflict_count += (*it).is_conflicting ? 1 : 0;
1893  }
1894  }
1895 
1896  it = m_askAllowPrograms.begin();
1897  for (; it != m_askAllowPrograms.end() && !(*it).is_conflicting; ++it);
1898 
1899  if (conflict_count == 0)
1900  {
1901  LOG(VB_GENERAL, LOG_INFO, LOC + "The scheduler wants to make "
1902  "a non-conflicting recording.");
1903  // TODO take down mplexid and inform user of problem
1904  // on channel changes.
1905  }
1906  else if (conflict_count == 1 && ((*it).info->GetInputID() == cardid))
1907  {
1908 #if 0
1909  LOG(VB_GENERAL, LOG_DEBUG, LOC + "UpdateOSDAskAllowDialog -- " +
1910  "kAskAllowOneRec");
1911 #endif
1912 
1913  it = m_askAllowPrograms.begin();
1914 
1915  QString channel = m_dbChannelFormat;
1916  channel
1917  .replace("<num>", (*it).info->GetChanNum())
1918  .replace("<sign>", (*it).info->GetChannelSchedulingID())
1919  .replace("<name>", (*it).info->GetChannelName());
1920 
1921  message = single_rec.arg((*it).info->GetTitle()).arg(channel);
1922 
1923  OSD *osd = GetOSDLock(ctx);
1924  if (osd)
1925  {
1926  m_browseHelper->BrowseEnd(ctx, false);
1927  timeuntil = MythDate::current().secsTo((*it).expiry) * 1000;
1928  osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
1929  osd->DialogAddButton(record_watch, "DIALOG_ASKALLOW_WATCH_0",
1930  false, !((*it).has_rec));
1931  osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0");
1932  osd->DialogAddButton(((*it).has_later) ? record_later1 : do_not_record1,
1933  "DIALOG_ASKALLOW_CANCELRECORDING_0",
1934  false, ((*it).has_rec));
1935  }
1936  ReturnOSDLock(ctx, osd);
1937  }
1938  else
1939  {
1940  if (conflict_count > 1)
1941  {
1942  message = tr(
1943  "MythTV wants to record these programs in %d seconds:");
1944  message += "\n";
1945  }
1946 
1947  bool has_rec = false;
1948  it = m_askAllowPrograms.begin();
1949  for (; it != m_askAllowPrograms.end(); ++it)
1950  {
1951  if (!(*it).is_conflicting)
1952  continue;
1953 
1954  QString title = (*it).info->GetTitle();
1955  if ((title.length() < 10) && !(*it).info->GetSubtitle().isEmpty())
1956  title += ": " + (*it).info->GetSubtitle();
1957  if (title.length() > 20)
1958  title = title.left(17) + "...";
1959 
1960  QString channel = m_dbChannelFormat;
1961  channel
1962  .replace("<num>", (*it).info->GetChanNum())
1963  .replace("<sign>", (*it).info->GetChannelSchedulingID())
1964  .replace("<name>", (*it).info->GetChannelName());
1965 
1966  if (conflict_count > 1)
1967  {
1968  message += tr("\"%1\" on %2").arg(title).arg(channel);
1969  message += "\n";
1970  }
1971  else
1972  {
1973  message = single_rec.arg((*it).info->GetTitle()).arg(channel);
1974  has_rec = (*it).has_rec;
1975  }
1976  }
1977 
1978  if (conflict_count > 1)
1979  {
1980  message += "\n";
1981  message += tr("Do you want to:");
1982  }
1983 
1984  bool all_have_later = true;
1985  timeuntil = 9999999;
1986  it = m_askAllowPrograms.begin();
1987  for (; it != m_askAllowPrograms.end(); ++it)
1988  {
1989  if ((*it).is_conflicting)
1990  {
1991  all_have_later &= (*it).has_later;
1992  int tmp = MythDate::current().secsTo((*it).expiry);
1993  tmp *= 1000;
1994  timeuntil = min(timeuntil, max(tmp, 0));
1995  }
1996  }
1997  timeuntil = (9999999 == timeuntil) ? 0 : timeuntil;
1998 
1999  OSD *osd = GetOSDLock(ctx);
2000  if (osd && conflict_count > 1)
2001  {
2002  m_browseHelper->BrowseEnd(ctx, false);
2003  osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
2004  osd->DialogAddButton(let_recordm, "DIALOG_ASKALLOW_EXIT_0",
2005  false, true);
2006  osd->DialogAddButton((all_have_later) ? record_laterm : do_not_recordm,
2007  "DIALOG_ASKALLOW_CANCELCONFLICTING_0");
2008  }
2009  else if (osd)
2010  {
2011  m_browseHelper->BrowseEnd(ctx, false);
2012  osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
2013  osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0",
2014  false, !has_rec);
2015  osd->DialogAddButton((all_have_later) ? record_later1 : do_not_record1,
2016  "DIALOG_ASKALLOW_CANCELRECORDING_0",
2017  false, has_rec);
2018  }
2019  ReturnOSDLock(ctx, osd);
2020  }
2021 }
2022 
2023 void TV::HandleOSDAskAllow(PlayerContext *ctx, const QString& action)
2024 {
2025  if (!DialogIsVisible(ctx, OSD_DLG_ASKALLOW))
2026  return;
2027 
2028  if (!m_askAllowLock.tryLock())
2029  {
2030  LOG(VB_GENERAL, LOG_ERR, "allowrecordingbox : askAllowLock is locked");
2031  return;
2032  }
2033 
2034  if (action == "CANCELRECORDING")
2035  {
2036  if (ctx->m_recorder)
2037  ctx->m_recorder->CancelNextRecording(true);
2038  }
2039  else if (action == "CANCELCONFLICTING")
2040  {
2041  QMap<QString,AskProgramInfo>::iterator it =
2042  m_askAllowPrograms.begin();
2043  for (; it != m_askAllowPrograms.end(); ++it)
2044  {
2045  if ((*it).is_conflicting)
2046  RemoteCancelNextRecording((*it).info->GetInputID(), true);
2047  }
2048  }
2049  else if (action == "WATCH")
2050  {
2051  if (ctx->m_recorder)
2052  ctx->m_recorder->CancelNextRecording(false);
2053  }
2054  else // if (action == "EXIT")
2055  {
2056  PrepareToExitPlayer(ctx, __LINE__);
2057  SetExitPlayer(true, true);
2058  }
2059 
2060  m_askAllowLock.unlock();
2061 }
2062 
2063 int TV::Playback(const ProgramInfo &rcinfo)
2064 {
2065  m_wantsToQuit = false;
2066  m_jumpToProgram = false;
2067  m_allowRerecord = false;
2068  m_requestDelete = false;
2070 
2071  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2072  if (mctx->GetState() != kState_None)
2073  {
2074  ReturnPlayerLock(mctx);
2075  return 0;
2076  }
2077 
2078  mctx->SetPlayingInfo(&rcinfo);
2079  mctx->SetInitialTVState(false);
2080  HandleStateChange(mctx, mctx);
2081 
2082  ReturnPlayerLock(mctx);
2083 
2084  if (LCD *lcd = LCD::Get())
2085  {
2086  lcd->switchToChannel(rcinfo.GetChannelSchedulingID(),
2087  rcinfo.GetTitle(), rcinfo.GetSubtitle());
2088  lcd->setFunctionLEDs((rcinfo.IsRecording())?FUNC_TV:FUNC_MOVIE, true);
2089  }
2090 
2091  return 1;
2092 }
2093 
2095 {
2096  return (state == kState_RecordingOnly ||
2097  state == kState_WatchingRecording);
2098 }
2099 
2101 {
2102  return (state == kState_WatchingPreRecorded ||
2103  state == kState_WatchingRecording ||
2104  state == kState_WatchingVideo ||
2105  state == kState_WatchingDVD ||
2106  state == kState_WatchingBD);
2107 }
2108 
2110 {
2111  return (state == kState_WatchingLiveTV);
2112 }
2113 
2115 {
2116  if (StateIsRecording(state))
2117  {
2118  if (state == kState_RecordingOnly)
2119  return kState_None;
2121  }
2122  return kState_Error;
2123 }
2124 
2125 #define TRANSITION(ASTATE,BSTATE) \
2126  ((ctxState == (ASTATE)) && (desiredNextState == (BSTATE)))
2127 
2128 #define SET_NEXT() do { nextState = desiredNextState; changed = true; } while(false)
2129 #define SET_LAST() do { nextState = ctxState; changed = true; } while(false)
2130 
2131 static QString tv_i18n(const QString &msg)
2132 {
2133  QByteArray msg_arr = msg.toLatin1();
2134  QString msg_i18n = TV::tr(msg_arr.constData());
2135  QByteArray msg_i18n_arr = msg_i18n.toLatin1();
2136  return (msg_arr == msg_i18n_arr) ? msg_i18n : msg;
2137 }
2138 
2148 {
2149  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1) -- begin")
2150  .arg(find_player_index(ctx)));
2151 
2152  if (!ctx) // can never happen, but keep coverity happy
2153  return;
2154 
2155  if (ctx->IsErrored())
2156  {
2157  LOG(VB_GENERAL, LOG_ERR, LOC +
2158  "Called after fatal error detected.");
2159  return;
2160  }
2161 
2162  bool changed = false;
2163 
2164  ctx->LockState();
2165  TVState nextState = ctx->GetState();
2166  if (ctx->m_nextState.empty())
2167  {
2168  LOG(VB_GENERAL, LOG_WARNING, LOC +
2169  "Warning, called with no state to change to.");
2170  ctx->UnlockState();
2171  return;
2172  }
2173 
2174  TVState ctxState = ctx->GetState();
2175  TVState desiredNextState = ctx->DequeueNextState();
2176 
2177  LOG(VB_GENERAL, LOG_INFO, LOC +
2178  QString("Attempting to change from %1 to %2")
2179  .arg(StateToString(nextState))
2180  .arg(StateToString(desiredNextState)));
2181 
2182  if (desiredNextState == kState_Error)
2183  {
2184  LOG(VB_GENERAL, LOG_ERR, LOC + "Attempting to set to an error state!");
2185  SetErrored(ctx);
2186  ctx->UnlockState();
2187  return;
2188  }
2189 
2190  bool ok = false;
2192  {
2193  ctx->m_lastSignalUIInfo.clear();
2194 
2195  ctx->m_recorder->Setup();
2196 
2197  QDateTime timerOffTime = MythDate::current();
2198  m_lockTimerOn = false;
2199 
2200  SET_NEXT();
2201 
2202  uint chanid = m_initialChanID;
2203  if (!chanid)
2204  chanid = gCoreContext->GetNumSetting("DefaultChanid", 0);
2205 
2206  if (chanid && !IsTunable(ctx, chanid))
2207  chanid = 0;
2208 
2209  QString channum = "";
2210 
2211  if (chanid)
2212  {
2213  QStringList reclist;
2214 
2215  MSqlQuery query(MSqlQuery::InitCon());
2216  query.prepare("SELECT channum FROM channel "
2217  "WHERE chanid = :CHANID");
2218  query.bindValue(":CHANID", chanid);
2219  if (query.exec() && query.isActive() && query.size() > 0 && query.next())
2220  channum = query.value(0).toString();
2221  else
2222  channum = QString::number(chanid);
2223 
2224  bool getit = ctx->m_recorder->ShouldSwitchToAnotherCard(
2225  QString::number(chanid));
2226 
2227  if (getit)
2228  reclist = ChannelUtil::GetValidRecorderList(chanid, channum);
2229 
2230  if (!reclist.empty())
2231  {
2232  RemoteEncoder *testrec = RemoteRequestFreeRecorderFromList(reclist, 0);
2233  if (testrec && testrec->IsValidRecorder())
2234  {
2235  ctx->SetRecorder(testrec);
2236  ctx->m_recorder->Setup();
2237  }
2238  else
2239  delete testrec; // If testrec isn't a valid recorder ...
2240  }
2241  else if (getit)
2242  chanid = 0;
2243  }
2244 
2245  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- begin");
2246 
2247  if (chanid && !channum.isEmpty())
2248  ctx->m_recorder->SpawnLiveTV(ctx->m_tvchain->GetID(), false, channum);
2249  else
2250  ctx->m_recorder->SpawnLiveTV(ctx->m_tvchain->GetID(), false, "");
2251 
2252  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- end");
2253 
2254  if (!ctx->ReloadTVChain())
2255  {
2256  LOG(VB_GENERAL, LOG_ERR, LOC +
2257  "LiveTV not successfully started");
2258  RestoreScreenSaver(ctx);
2259  ctx->SetRecorder(nullptr);
2260  SetErrored(ctx);
2261  SET_LAST();
2262  }
2263  else
2264  {
2265  ctx->LockPlayingInfo(__FILE__, __LINE__);
2266  QString playbackURL = ctx->m_playingInfo->GetPlaybackURL(true);
2267  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2268 
2269  bool opennow = (ctx->m_tvchain->GetInputType(-1) != "DUMMY");
2270 
2271  LOG(VB_GENERAL, LOG_INFO, LOC +
2272  QString("playbackURL(%1) inputtype(%2)")
2273  .arg(playbackURL).arg(ctx->m_tvchain->GetInputType(-1)));
2274 
2275  ctx->SetRingBuffer(
2277  playbackURL, false, true,
2278  opennow ? RingBuffer::kLiveTVOpenTimeout : -1));
2279 
2280  if (ctx->m_buffer)
2281  ctx->m_buffer->SetLiveMode(ctx->m_tvchain);
2282  }
2283 
2284 
2285  if (ctx->m_playingInfo && StartRecorder(ctx,-1))
2286  {
2287  ok = StartPlayer(mctx, ctx, desiredNextState);
2288  }
2289  if (!ok)
2290  {
2291  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
2292  RestoreScreenSaver(ctx);
2293  ctx->SetRecorder(nullptr);
2294  SetErrored(ctx);
2295  SET_LAST();
2296  }
2297  else if (!ctx->IsPIP())
2298  {
2299  if (!m_lastLockSeenTime.isValid() ||
2300  (m_lastLockSeenTime < timerOffTime))
2301  {
2302  m_lockTimer.start();
2303  m_lockTimerOn = true;
2304  }
2305  }
2306 
2307  if (mctx != ctx)
2308  SetActive(ctx, find_player_index(ctx), false);
2309  }
2311  {
2312  SET_NEXT();
2313  RestoreScreenSaver(ctx);
2314  StopStuff(mctx, ctx, true, true, true);
2315 
2316  if ((mctx != ctx) && (GetPlayer(ctx,-1) == ctx))
2317  SetActive(mctx, 0, true);
2318  }
2320  {
2321  SET_NEXT();
2322  }
2328  {
2329  ctx->LockPlayingInfo(__FILE__, __LINE__);
2330  QString playbackURL = ctx->m_playingInfo->GetPlaybackURL(true);
2331  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2332 
2333  RingBuffer *buffer = RingBuffer::Create(playbackURL, false);
2334  if (buffer && !buffer->GetLastError().isEmpty())
2335  {
2336  ShowNotificationError(tr("Can't start playback"),
2337  TV::tr( "TV Player" ), buffer->GetLastError());
2338  delete buffer;
2339  buffer = nullptr;
2340  }
2341  ctx->SetRingBuffer(buffer);
2342 
2343  if (ctx->m_buffer && ctx->m_buffer->IsOpen())
2344  {
2345  if (desiredNextState == kState_WatchingRecording)
2346  {
2347  ctx->LockPlayingInfo(__FILE__, __LINE__);
2349  ctx->m_playingInfo);
2350  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2351 
2352  ctx->SetRecorder(rec);
2353 
2354  if (!ctx->m_recorder ||
2355  !ctx->m_recorder->IsValidRecorder())
2356  {
2357  LOG(VB_GENERAL, LOG_ERR, LOC +
2358  "Couldn't find recorder for in-progress recording");
2359  desiredNextState = kState_WatchingPreRecorded;
2360  ctx->SetRecorder(nullptr);
2361  }
2362  else
2363  {
2364  ctx->m_recorder->Setup();
2365  }
2366  }
2367 
2368  ok = StartPlayer(mctx, ctx, desiredNextState);
2369 
2370  if (ok)
2371  {
2372  SET_NEXT();
2373 
2374  ctx->LockPlayingInfo(__FILE__, __LINE__);
2375  if (ctx->m_playingInfo->IsRecording())
2376  {
2377  QString message = "COMMFLAG_REQUEST ";
2378  message += ctx->m_playingInfo->MakeUniqueKey();
2379  gCoreContext->SendMessage(message);
2380  }
2381  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2382  }
2383  }
2384 
2385  if (!ok)
2386  {
2387  SET_LAST();
2388  SetErrored(ctx);
2389  if (ctx->IsPlayerErrored())
2390  {
2392  TV::tr( "TV Player" ),
2393  playbackURL);
2394  // We're going to display this error as notification
2395  // no need to display it later as popup
2396  ctx->m_player->ResetErrored();
2397  }
2398  }
2399  else if (mctx != ctx)
2400  {
2401  SetActive(ctx, find_player_index(ctx), false);
2402  }
2403  }
2409  {
2410  SET_NEXT();
2411 
2412  RestoreScreenSaver(ctx);
2413  StopStuff(mctx, ctx, true, true, false);
2414 
2415  if ((mctx != ctx) && (GetPlayer(ctx,-1) == ctx))
2416  SetActive(mctx, 0, true);
2417  }
2418  else if (TRANSITION(kState_None, kState_None))
2419  {
2420  SET_NEXT();
2421  }
2422 
2423  // Print state changed message...
2424  if (!changed)
2425  {
2426  LOG(VB_GENERAL, LOG_ERR, LOC +
2427  QString("Unknown state transition: %1 to %2")
2428  .arg(StateToString(ctx->GetState()))
2429  .arg(StateToString(desiredNextState)));
2430  }
2431  else if (ctx->GetState() != nextState)
2432  {
2433  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Changing from %1 to %2")
2434  .arg(StateToString(ctx->GetState()))
2435  .arg(StateToString(nextState)));
2436  }
2437 
2438  // update internal state variable
2439  TVState lastState = ctx->GetState();
2440  ctx->m_playingState = nextState;
2441  ctx->UnlockState();
2442 
2443  if (mctx == ctx)
2444  {
2445  if (StateIsLiveTV(ctx->GetState()))
2446  {
2447  LOG(VB_GENERAL, LOG_INFO, LOC + "State is LiveTV & mctx == ctx");
2448  UpdateOSDInput(ctx);
2449  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateOSDInput done");
2450  UpdateLCD();
2451  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateLCD done");
2452  ITVRestart(ctx, true);
2453  LOG(VB_GENERAL, LOG_INFO, LOC + "ITVRestart done");
2454  }
2455  else if (StateIsPlaying(ctx->GetState()) && lastState == kState_None)
2456  {
2457  ctx->LockPlayingInfo(__FILE__, __LINE__);
2458  int count = PlayGroup::GetCount();
2459  QString msg = tr("%1 Settings")
2460  .arg(tv_i18n(ctx->m_playingInfo->GetPlaybackGroup()));
2461  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2462  if (count > 0)
2463  SetOSDMessage(ctx, msg);
2464  ITVRestart(ctx, false);
2465  }
2466 
2467  if (ctx->m_buffer && ctx->m_buffer->IsDVD())
2468  {
2469  UpdateLCD();
2470  }
2471 
2472  if (ctx->m_recorder)
2473  ctx->m_recorder->FrontendReady();
2474 
2475  QMutexLocker locker(&m_timerIdLock);
2476  if (m_endOfRecPromptTimerId)
2477  KillTimer(m_endOfRecPromptTimerId);
2478  m_endOfRecPromptTimerId = 0;
2479  if (m_dbEndOfRecExitPrompt && !m_inPlaylist && !m_underNetworkControl)
2480  {
2481  m_endOfRecPromptTimerId =
2482  StartTimer(kEndOfRecPromptCheckFrequency, __LINE__);
2483  }
2484 
2485  if (m_endOfPlaybackTimerId)
2486  KillTimer(m_endOfPlaybackTimerId);
2487  m_endOfPlaybackTimerId = 0;
2488 
2489  if (StateIsPlaying(ctx->GetState()))
2490  {
2491  m_endOfPlaybackTimerId =
2492  StartTimer(kEndOfPlaybackFirstCheckTimer, __LINE__);
2493 
2494  }
2495 
2496  }
2497 
2504  {
2505  if (!ctx->IsPIP())
2507  bool switchMode = gCoreContext->GetBoolSetting("UseVideoModes", false);
2508  // m_playerBounds is not applicable when switching modes so
2509  // skip this logic in that case.
2510  if (!switchMode)
2511  {
2512  MythMainWindow *mainWindow = GetMythMainWindow();
2513  mainWindow->setBaseSize(m_playerBounds.size());
2514  mainWindow->setMinimumSize(
2515  (m_dbUseFixedSize) ? m_playerBounds.size() : QSize(16, 16));
2516  mainWindow->setMaximumSize(
2517  (m_dbUseFixedSize) ? m_playerBounds.size() :
2518  QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
2519  mainWindow->setGeometry(m_playerBounds);
2520  mainWindow->ResizePainterWindow(m_playerBounds.size());
2521  }
2522  // PGB Do not disable the GUI when using openmax renderer,
2523  // to ensure that space next to letterbox pictures
2524  // is painted.
2525  bool isOpenMaxRender = false;
2526  if (ctx && ctx->m_player)
2527  {
2528  VideoOutput *vo = ctx->m_player->GetVideoOutput();
2529  isOpenMaxRender = vo && vo->GetName() == "openmax";
2530  }
2531  if (!isOpenMaxRender && !m_weDisabledGUI)
2532  {
2533  m_weDisabledGUI = true;
2535  }
2536  DrawUnusedRects();
2537  // we no longer need the contents of myWindow
2538  if (m_myWindow)
2539  m_myWindow->DeleteAllChildren();
2540 
2541  LOG(VB_GENERAL, LOG_INFO, LOC + "Main UI disabled.");
2542  }
2543 
2544  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2545  QString("(%1) -- end")
2546  .arg(find_player_index(ctx)));
2547 }
2548 #undef TRANSITION
2549 #undef SET_NEXT
2550 #undef SET_LAST
2551 
2559 bool TV::StartRecorder(PlayerContext *ctx, int maxWait)
2560 {
2561  RemoteEncoder *rec = ctx->m_recorder;
2562  maxWait = (maxWait <= 0) ? 40000 : maxWait;
2563  MythTimer t;
2564  t.start();
2565  bool recording = false, ok = true;
2566  if (!rec) {
2567  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid Remote Encoder");
2568  SetErrored(ctx);
2569  return false;
2570  }
2571  while (!(recording = rec->IsRecording(&ok)) &&
2572  !m_exitPlayerTimerId && t.elapsed() < maxWait)
2573  {
2574  if (!ok)
2575  {
2576  LOG(VB_GENERAL, LOG_ERR, LOC + "Lost contact with backend");
2577  SetErrored(ctx);
2578  return false;
2579  }
2580  std::this_thread::sleep_for(std::chrono::microseconds(5));
2581  }
2582 
2583  if (!recording || m_exitPlayerTimerId)
2584  {
2585  if (!m_exitPlayerTimerId)
2586  LOG(VB_GENERAL, LOG_ERR, LOC +
2587  "Timed out waiting for recorder to start");
2588  return false;
2589  }
2590 
2591  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2592  QString("Took %1 ms to start recorder.")
2593  .arg(t.elapsed()));
2594 
2595  return true;
2596 }
2597 
2614  bool stopRingBuffer, bool stopPlayer, bool stopRecorder)
2615 {
2616  LOG(VB_PLAYBACK, LOG_DEBUG,
2617  LOC + QString("For player ctx %1 -- begin")
2618  .arg(find_player_index(ctx)));
2619 
2620  SetActive(mctx, 0, false);
2621 
2622  if (ctx->m_buffer)
2623  ctx->m_buffer->IgnoreWaitStates(true);
2624 
2625  ctx->LockDeletePlayer(__FILE__, __LINE__);
2626  if (stopPlayer)
2627  ctx->StopPlaying();
2628  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
2629 
2630  if (stopRingBuffer)
2631  {
2632  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopping ring buffer");
2633  if (ctx->m_buffer)
2634  {
2635  ctx->m_buffer->StopReads();
2636  ctx->m_buffer->Pause();
2637  ctx->m_buffer->WaitForPause();
2638  }
2639  }
2640 
2641  if (stopPlayer)
2642  {
2643  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopping player");
2644  if (ctx == mctx)
2645  {
2646  for (uint i = 1; mctx && (i < m_player.size()); i++)
2647  StopStuff(mctx, GetPlayer(mctx,i), true, true, true);
2648  }
2649  }
2650 
2651  if (stopRecorder)
2652  {
2653  LOG(VB_PLAYBACK, LOG_INFO, LOC + "stopping recorder");
2654  if (ctx->m_recorder)
2655  ctx->m_recorder->StopLiveTV();
2656  }
2657 
2658  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
2659 }
2660 
2662 {
2663  int ctx_index = find_player_index(ctx);
2664 
2665  QString loc = LOC + QString("player ctx %1")
2666  .arg(ctx_index);
2667 
2668  if (!mctx || !ctx || ctx_index < 0)
2669  {
2670  LOG(VB_GENERAL, LOG_ERR, loc + "-- error");
2671  return;
2672  }
2673 
2674  LOG(VB_PLAYBACK, LOG_INFO, loc);
2675 
2676  if (mctx != ctx)
2677  {
2678  if (ctx->HasPlayer())
2679  {
2680  PIPRemovePlayer(mctx, ctx);
2681  ctx->SetPlayer(nullptr);
2682  }
2683 
2684  m_player.erase(m_player.begin() + ctx_index);
2685  delete ctx;
2686  if (mctx->IsPBP())
2687  PBPRestartMainPlayer(mctx);
2688  SetActive(mctx, m_playerActive, false);
2689  return;
2690  }
2691 
2692  ctx->TeardownPlayer();
2693 }
2694 
2695 void TV::timerEvent(QTimerEvent *te)
2696 {
2697  const int timer_id = te->timerId();
2698 
2699  PlayerContext *mctx2 = GetPlayerReadLock(0, __FILE__, __LINE__);
2700  if (mctx2->IsErrored())
2701  {
2702  ReturnPlayerLock(mctx2);
2703  return;
2704  }
2705  ReturnPlayerLock(mctx2);
2706 
2707  bool ignore = false;
2708  {
2709  QMutexLocker locker(&m_timerIdLock);
2710  ignore =
2711  (!m_stateChangeTimerId.empty() &&
2712  m_stateChangeTimerId.find(timer_id) == m_stateChangeTimerId.end());
2713  }
2714  if (ignore)
2715  return; // Always handle state changes first...
2716 
2717  bool handled = true;
2718  if (timer_id == m_lcdTimerId)
2719  HandleLCDTimerEvent();
2720  else if (timer_id == m_lcdVolumeTimerId)
2721  HandleLCDVolumeTimerEvent();
2722  else if (timer_id == m_sleepTimerId)
2723  ShowOSDSleep();
2724  else if (timer_id == m_sleepDialogTimerId)
2725  SleepDialogTimeout();
2726  else if (timer_id == m_idleTimerId)
2727  ShowOSDIdle();
2728  else if (timer_id == m_idleDialogTimerId)
2729  IdleDialogTimeout();
2730  else if (timer_id == m_endOfPlaybackTimerId)
2731  HandleEndOfPlaybackTimerEvent();
2732  else if (timer_id == m_embedCheckTimerId)
2733  HandleIsNearEndWhenEmbeddingTimerEvent();
2734  else if (timer_id == m_endOfRecPromptTimerId)
2735  HandleEndOfRecordingExitPromptTimerEvent();
2736  else if (timer_id == m_videoExitDialogTimerId)
2737  HandleVideoExitDialogTimerEvent();
2738  else if (timer_id == m_pseudoChangeChanTimerId)
2739  HandlePseudoLiveTVTimerEvent();
2740  else if (timer_id == m_speedChangeTimerId)
2741  HandleSpeedChangeTimerEvent();
2742  else if (timer_id == m_pipChangeTimerId)
2743  HandlePxPTimerEvent();
2744  else if (timer_id == m_saveLastPlayPosTimerId)
2745  HandleSaveLastPlayPosEvent();
2746  else
2747  handled = false;
2748 
2749  if (handled)
2750  return;
2751 
2752  // Check if it matches a stateChangeTimerId
2753  PlayerContext *ctx = nullptr;
2754  {
2755  QMutexLocker locker(&m_timerIdLock);
2756  TimerContextMap::iterator it = m_stateChangeTimerId.find(timer_id);
2757  if (it != m_stateChangeTimerId.end())
2758  {
2759  KillTimer(timer_id);
2760  ctx = *it;
2761  m_stateChangeTimerId.erase(it);
2762  }
2763  }
2764 
2765  if (ctx)
2766  {
2767  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2768  bool still_exists = find_player_index(ctx) >= 0;
2769 
2770  while (still_exists && !ctx->m_nextState.empty())
2771  {
2772  HandleStateChange(mctx, ctx);
2773  if ((kState_None == ctx->GetState() ||
2774  kState_Error == ctx->GetState()) &&
2775  ((mctx != ctx) || m_jumpToProgram))
2776  {
2777  ReturnPlayerLock(mctx);
2778  mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
2779  TeardownPlayer(mctx, ctx);
2780  still_exists = false;
2781  }
2782  }
2783  ReturnPlayerLock(mctx);
2784  handled = true;
2785  }
2786 
2787  if (handled)
2788  return;
2789 
2790  // Check if it matches a signalMonitorTimerId
2791  ctx = nullptr;
2792  {
2793  QMutexLocker locker(&m_timerIdLock);
2794  TimerContextMap::iterator it = m_signalMonitorTimerId.find(timer_id);
2795  if (it != m_signalMonitorTimerId.end())
2796  {
2797  KillTimer(timer_id);
2798  ctx = *it;
2799  m_signalMonitorTimerId.erase(it);
2800  }
2801  }
2802 
2803  if (ctx)
2804  {
2805  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2806  bool still_exists = find_player_index(ctx) >= 0;
2807 
2808  if (still_exists && !ctx->m_lastSignalMsg.empty())
2809  { // set last signal msg, so we get some feedback...
2810  UpdateOSDSignal(ctx, ctx->m_lastSignalMsg);
2811  ctx->m_lastSignalMsg.clear();
2812  }
2813  UpdateOSDTimeoutMessage(ctx);
2814 
2815  ReturnPlayerLock(mctx);
2816  handled = true;
2817  }
2818 
2819  if (handled)
2820  return;
2821 
2822  // Check if it matches networkControlTimerId
2823  QString netCmd;
2824  {
2825  QMutexLocker locker(&m_timerIdLock);
2826  if (timer_id == m_networkControlTimerId)
2827  {
2828  if (!networkControlCommands.empty())
2829  netCmd = networkControlCommands.dequeue();
2830  if (networkControlCommands.empty())
2831  {
2832  KillTimer(m_networkControlTimerId);
2833  m_networkControlTimerId = 0;
2834  }
2835  }
2836  }
2837 
2838  if (!netCmd.isEmpty())
2839  {
2840  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2841  ProcessNetworkControlCommand(actx, netCmd);
2842  ReturnPlayerLock(actx);
2843  handled = true;
2844  }
2845 
2846  if (handled)
2847  return;
2848 
2849  // Check if it matches exitPlayerTimerId
2850  if (timer_id == m_exitPlayerTimerId)
2851  {
2852  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2853 
2854  OSD *osd = GetOSDLock(mctx);
2855  if (osd)
2856  {
2857  osd->DialogQuit();
2858  osd->HideAll();
2859  }
2860  ReturnOSDLock(mctx, osd);
2861 
2862  if (m_jumpToProgram && m_lastProgram)
2863  {
2864  if (!m_lastProgram->IsFileReadable())
2865  {
2866  SetOSDMessage(mctx, tr("Last Program: %1 Doesn't Exist")
2867  .arg(m_lastProgram->GetTitle()));
2868  lastProgramStringList.clear();
2869  SetLastProgram(nullptr);
2870  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2871  "Last Program File does not exist");
2872  m_jumpToProgram = false;
2873  }
2874  else
2875  ForceNextStateNone(mctx);
2876  }
2877  else
2878  ForceNextStateNone(mctx);
2879 
2880  ReturnPlayerLock(mctx);
2881 
2882  QMutexLocker locker(&m_timerIdLock);
2883  KillTimer(m_exitPlayerTimerId);
2884  m_exitPlayerTimerId = 0;
2885  handled = true;
2886  }
2887 
2888  if (handled)
2889  return;
2890 
2891  if (timer_id == m_jumpMenuTimerId)
2892  {
2893  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2894  if (actx)
2895  FillOSDMenuJumpRec(actx);
2896  ReturnPlayerLock(actx);
2897 
2898  QMutexLocker locker(&m_timerIdLock);
2899  KillTimer(m_jumpMenuTimerId);
2900  m_jumpMenuTimerId = 0;
2901  handled = true;
2902  }
2903 
2904  if (handled)
2905  return;
2906 
2907  if (timer_id == m_switchToInputTimerId)
2908  {
2909  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2910  if (m_switchToInputId)
2911  {
2912  uint tmp = m_switchToInputId;
2913  m_switchToInputId = 0;
2914  SwitchInputs(actx, 0, QString(), tmp);
2915  }
2916  ReturnPlayerLock(actx);
2917 
2918  QMutexLocker locker(&m_timerIdLock);
2919  KillTimer(m_switchToInputTimerId);
2920  m_switchToInputTimerId = 0;
2921  handled = true;
2922  }
2923 
2924  if (handled)
2925  return;
2926 
2927  if (timer_id == m_ccInputTimerId)
2928  {
2929  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2930  // Clear closed caption input mode when timer expires
2931  if (m_ccInputMode)
2932  {
2933  m_ccInputMode = false;
2934  ClearInputQueues(actx, true);
2935  }
2936  ReturnPlayerLock(actx);
2937 
2938  QMutexLocker locker(&m_timerIdLock);
2939  KillTimer(m_ccInputTimerId);
2940  m_ccInputTimerId = 0;
2941  handled = true;
2942  }
2943 
2944  if (handled)
2945  return;
2946 
2947  if (timer_id == m_asInputTimerId)
2948  {
2949  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2950  // Clear closed caption input mode when timer expires
2951  if (m_asInputMode)
2952  {
2953  m_asInputMode = false;
2954  ClearInputQueues(actx, true);
2955  }
2956  ReturnPlayerLock(actx);
2957 
2958  QMutexLocker locker(&m_timerIdLock);
2959  KillTimer(m_asInputTimerId);
2960  m_asInputTimerId = 0;
2961  handled = true;
2962  }
2963 
2964  if (handled)
2965  return;
2966 
2967  if (timer_id == m_queueInputTimerId)
2968  {
2969  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2970  // Commit input when the OSD fades away
2971  if (HasQueuedChannel())
2972  {
2973  OSD *osd = GetOSDLock(actx);
2974  if (osd && !osd->IsWindowVisible("osd_input"))
2975  {
2976  ReturnOSDLock(actx, osd);
2977  CommitQueuedInput(actx);
2978  }
2979  else
2980  ReturnOSDLock(actx, osd);
2981  }
2982  ReturnPlayerLock(actx);
2983 
2984  QMutexLocker locker(&m_timerIdLock);
2985  if (!m_queuedChanID && m_queuedChanNum.isEmpty() && m_queueInputTimerId)
2986  {
2987  KillTimer(m_queueInputTimerId);
2988  m_queueInputTimerId = 0;
2989  }
2990  handled = true;
2991  }
2992 
2993  if (handled)
2994  return;
2995 
2996  if (timer_id == m_browseTimerId)
2997  {
2998  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2999  m_browseHelper->BrowseEnd(actx, false);
3000  ReturnPlayerLock(actx);
3001  handled = true;
3002  }
3003 
3004  if (handled)
3005  return;
3006 
3007  if (timer_id == m_updateOSDDebugTimerId)
3008  {
3009  bool update = false;
3010  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3011  OSD *osd = GetOSDLock(actx);
3012  if (osd && osd->IsWindowVisible("osd_debug") &&
3013  (StateIsLiveTV(actx->GetState()) ||
3014  StateIsPlaying(actx->GetState())))
3015  {
3016  update = true;
3017  }
3018  else
3019  {
3020  QMutexLocker locker(&m_timerIdLock);
3021  KillTimer(m_updateOSDDebugTimerId);
3022  m_updateOSDDebugTimerId = 0;
3023  if (actx->m_buffer)
3024  actx->m_buffer->EnableBitrateMonitor(false);
3025  if (actx->m_player)
3026  actx->m_player->EnableFrameRateMonitor(false);
3027  }
3028  ReturnOSDLock(actx, osd);
3029  if (update)
3030  UpdateOSDDebug(actx);
3031  ReturnPlayerLock(actx);
3032  handled = true;
3033  }
3034  if (timer_id == m_updateOSDPosTimerId)
3035  {
3036  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3037  OSD *osd = GetOSDLock(actx);
3038  if (osd && osd->IsWindowVisible("osd_status") &&
3039  (StateIsLiveTV(actx->GetState()) ||
3040  StateIsPlaying(actx->GetState())))
3041  {
3042  osdInfo info;
3043  if (actx->CalcPlayerSliderPosition(info))
3044  {
3045  osd->SetText("osd_status", info.text, kOSDTimeout_Ignore);
3046  osd->SetValues("osd_status", info.values, kOSDTimeout_Ignore);
3047  }
3048  }
3049  else
3050  SetUpdateOSDPosition(false);
3051  ReturnOSDLock(actx, osd);
3052  ReturnPlayerLock(actx);
3053  handled = true;
3054  }
3055 
3056  if (handled)
3057  return;
3058 
3059  if (timer_id == m_errorRecoveryTimerId)
3060  {
3061  bool error = false;
3062  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3063 
3064  if (mctx->IsPlayerErrored())
3065  {
3066  if (mctx->IsPlayerRecoverable())
3067  RestartMainPlayer(mctx);
3068 
3069  if (mctx->IsPlayerDecoderErrored())
3070  {
3071  LOG(VB_GENERAL, LOG_EMERG, LOC +
3072  QString("Serious hardware decoder error detected. "
3073  "Disabling hardware decoders."));
3074  m_noHardwareDecoders = true;
3075  for (size_t i = 0; i < m_player.size(); i++)
3076  m_player[i]->SetNoHardwareDecoders();
3077  RestartMainPlayer(mctx);
3078  }
3079  }
3080 
3081  if (mctx->IsRecorderErrored() ||
3082  mctx->IsPlayerErrored() ||
3083  mctx->IsErrored())
3084  {
3085  SetExitPlayer(true, false);
3086  ForceNextStateNone(mctx);
3087  error = true;
3088  }
3089 
3090  for (size_t i = 0; i < m_player.size(); i++)
3091  {
3092  PlayerContext *ctx2 = GetPlayer(mctx, i);
3093  if (error || ctx2->IsErrored())
3094  ForceNextStateNone(ctx2);
3095  }
3096  ReturnPlayerLock(mctx);
3097 
3098  QMutexLocker locker(&m_timerIdLock);
3099  if (m_errorRecoveryTimerId)
3100  KillTimer(m_errorRecoveryTimerId);
3101  m_errorRecoveryTimerId =
3102  StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
3103  }
3104 }
3105 
3107 {
3108  QString cmd;
3109 
3110  {
3111  QMutexLocker locker(&m_timerIdLock);
3112  if (m_changePxP.empty())
3113  {
3114  if (m_pipChangeTimerId)
3115  KillTimer(m_pipChangeTimerId);
3116  m_pipChangeTimerId = 0;
3117  return true;
3118  }
3119  cmd = m_changePxP.dequeue();
3120  }
3121 
3122  PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
3123  PlayerContext *actx = GetPlayer(mctx, -1);
3124 
3125  if (cmd == "TOGGLEPIPMODE")
3126  PxPToggleView(actx, false);
3127  else if (cmd == "TOGGLEPBPMODE")
3128  PxPToggleView(actx, true);
3129  else if (cmd == "CREATEPIPVIEW")
3130  PxPCreateView(actx, false);
3131  else if (cmd == "CREATEPBPVIEW")
3132  PxPCreateView(actx, true);
3133  else if (cmd == "SWAPPIP")
3134  {
3135  if (mctx != actx)
3136  PxPSwap(mctx, actx);
3137  else if (mctx && m_player.size() == 2)
3138  PxPSwap(mctx, GetPlayer(mctx,1));
3139  }
3140  else if (cmd == "TOGGLEPIPSTATE")
3141  PxPToggleType(mctx, !mctx->IsPBP());
3142 
3143  ReturnPlayerLock(mctx);
3144 
3145  QMutexLocker locker(&m_timerIdLock);
3146 
3147  if (m_pipChangeTimerId)
3148  KillTimer(m_pipChangeTimerId);
3149 
3150  if (m_changePxP.empty())
3151  m_pipChangeTimerId = 0;
3152  else
3153  m_pipChangeTimerId = StartTimer(20, __LINE__);
3154 
3155  return true;
3156 }
3157 
3159 {
3160  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3161  LCD *lcd = LCD::Get();
3162  if (lcd)
3163  {
3164  float progress = 0.0F;
3165  QString lcd_time_string;
3166  bool showProgress = true;
3167 
3168  if (StateIsLiveTV(GetState(actx)))
3169  ShowLCDChannelInfo(actx);
3170 
3171  if (actx->m_buffer && actx->m_buffer->IsDVD())
3172  {
3173  ShowLCDDVDInfo(actx);
3174  showProgress = !actx->m_buffer->IsInDiscMenuOrStillFrame();
3175  }
3176 
3177  if (showProgress)
3178  {
3179  osdInfo info;
3180  if (actx->CalcPlayerSliderPosition(info)) {
3181  progress = info.values["position"] * 0.001F;
3182 
3183  lcd_time_string = info.text["playedtime"] + " / " + info.text["totaltime"];
3184  // if the string is longer than the LCD width, remove all spaces
3185  if (lcd_time_string.length() > (int)lcd->getLCDWidth())
3186  lcd_time_string.remove(' ');
3187  }
3188  }
3189  lcd->setChannelProgress(lcd_time_string, progress);
3190  }
3191  ReturnPlayerLock(actx);
3192 
3193  QMutexLocker locker(&m_timerIdLock);
3194  KillTimer(m_lcdTimerId);
3195  m_lcdTimerId = StartTimer(kLCDTimeout, __LINE__);
3196 
3197  return true;
3198 }
3199 
3201 {
3202  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3203  LCD *lcd = LCD::Get();
3204  if (lcd)
3205  {
3206  ShowLCDChannelInfo(actx);
3207  lcd->switchToChannel(m_lcdCallsign, m_lcdTitle, m_lcdSubtitle);
3208  }
3209  ReturnPlayerLock(actx);
3210 
3211  QMutexLocker locker(&m_timerIdLock);
3212  KillTimer(m_lcdVolumeTimerId);
3213  m_lcdVolumeTimerId = 0;
3214 }
3215 
3216 int TV::StartTimer(int interval, int line)
3217 {
3218  int x = QObject::startTimer(interval);
3219  if (!x)
3220  {
3221  LOG(VB_GENERAL, LOG_ERR, LOC +
3222  QString("Failed to start timer on line %1 of %2")
3223  .arg(line).arg(__FILE__));
3224  }
3225  return x;
3226 }
3227 
3228 void TV::KillTimer(int id)
3229 {
3230  QObject::killTimer(id);
3231 }
3232 
3234 {
3235  ctx->ForceNextStateNone();
3236  ScheduleStateChange(ctx);
3237 }
3238 
3240 {
3241  QMutexLocker locker(&m_timerIdLock);
3242  m_stateChangeTimerId[StartTimer(1, __LINE__)] = ctx;
3243 }
3244 
3246 {
3247  if (!ctx)
3248  return;
3249  QMutexLocker locker(&m_timerIdLock);
3250  ctx->m_errored = true;
3251  KillTimer(m_errorRecoveryTimerId);
3252  m_errorRecoveryTimerId = StartTimer(1, __LINE__);
3253 }
3254 
3256  const ProgramInfo &p)
3257 {
3258  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switching to program: %1")
3260  SetLastProgram(&p);
3261  PrepareToExitPlayer(ctx,__LINE__);
3262  m_jumpToProgram = true;
3263  SetExitPlayer(true, true);
3264 }
3265 
3267 {
3268  bool bm_allowed = IsBookmarkAllowed(ctx);
3269  ctx->LockDeletePlayer(__FILE__, line);
3270  if (ctx->m_player)
3271  {
3272  if (bm_allowed)
3273  {
3274  // If we're exiting in the middle of the recording, we
3275  // automatically save a bookmark when "Action on playback
3276  // exit" is set to "Save position and exit".
3277  bool allow_set_before_end =
3278  (bookmark == kBookmarkAlways ||
3279  (bookmark == kBookmarkAuto &&
3280  m_dbPlaybackExitPrompt == 2));
3281  // If we're exiting at the end of the recording, we
3282  // automatically clear the bookmark when "Action on
3283  // playback exit" is set to "Save position and exit" and
3284  // "Clear bookmark on playback" is set to true.
3285  bool allow_clear_at_end =
3286  (bookmark == kBookmarkAlways ||
3287  (bookmark == kBookmarkAuto &&
3288  m_dbPlaybackExitPrompt == 2 &&
3289  m_dbClearSavedPosition));
3290  // Whether to set/clear a bookmark depends on whether we're
3291  // exiting at the end of a recording.
3292  bool at_end = (ctx->m_player->IsNearEnd() || getEndOfRecording());
3293  // Don't consider ourselves at the end if the recording is
3294  // in-progress.
3295  at_end &= !StateIsRecording(GetState(ctx));
3296  bool clear_lastplaypos = false;
3297  if (at_end && allow_clear_at_end)
3298  {
3299  SetBookmark(ctx, true);
3300  // Tidy up the lastplaypos mark only when we clear the
3301  // bookmark due to exiting at the end.
3302  clear_lastplaypos = true;
3303  }
3304  else if (!at_end && allow_set_before_end)
3305  {
3306  SetBookmark(ctx, false);
3307  }
3308  if (clear_lastplaypos && ctx->m_playingInfo)
3310  }
3311  if (m_dbAutoSetWatched)
3312  ctx->m_player->SetWatched();
3313  }
3314  ctx->UnlockDeletePlayer(__FILE__, line);
3315 }
3316 
3317 void TV::SetExitPlayer(bool set_it, bool wants_to)
3318 {
3319  QMutexLocker locker(&m_timerIdLock);
3320  if (set_it)
3321  {
3322  m_wantsToQuit = wants_to;
3323  if (!m_exitPlayerTimerId)
3324  m_exitPlayerTimerId = StartTimer(1, __LINE__);
3325  }
3326  else
3327  {
3328  if (m_exitPlayerTimerId)
3329  KillTimer(m_exitPlayerTimerId);
3330  m_exitPlayerTimerId = 0;
3331  m_wantsToQuit = wants_to;
3332  }
3333 }
3334 
3335 void TV::SetUpdateOSDPosition(bool set_it)
3336 {
3337  QMutexLocker locker(&m_timerIdLock);
3338  if (set_it)
3339  {
3340  if (!m_updateOSDPosTimerId)
3341  m_updateOSDPosTimerId = StartTimer(500, __LINE__);
3342  }
3343  else
3344  {
3345  if (m_updateOSDPosTimerId)
3346  KillTimer(m_updateOSDPosTimerId);
3347  m_updateOSDPosTimerId = 0;
3348  }
3349 }
3350 
3352 {
3353  {
3354  QMutexLocker locker(&m_timerIdLock);
3355  if (m_endOfPlaybackTimerId)
3356  KillTimer(m_endOfPlaybackTimerId);
3357  m_endOfPlaybackTimerId = 0;
3358  }
3359 
3360  bool is_playing = false;
3361  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3362  for (uint i = 0; mctx && (i < m_player.size()); i++)
3363  {
3364  PlayerContext *ctx = GetPlayer(mctx, i);
3365  if (!StateIsPlaying(ctx->GetState()))
3366  continue;
3367 
3368  if (ctx->IsPlayerPlaying())
3369  {
3370  is_playing = true;
3371  continue;
3372  }
3373 
3374  // If the end of playback is destined to pop up the end of
3375  // recording delete prompt, then don't exit the player here.
3376  if (ctx->GetState() == kState_WatchingPreRecorded &&
3377  m_dbEndOfRecExitPrompt && !m_inPlaylist && !m_underNetworkControl)
3378  continue;
3379 
3380  ForceNextStateNone(ctx);
3381  if (mctx == ctx)
3382  {
3383  m_endOfRecording = true;
3384  PrepareToExitPlayer(mctx, __LINE__);
3385  SetExitPlayer(true, true);
3386  }
3387  }
3388  ReturnPlayerLock(mctx);
3389 
3390  if (is_playing)
3391  {
3392  QMutexLocker locker(&m_timerIdLock);
3393  m_endOfPlaybackTimerId =
3394  StartTimer(kEndOfPlaybackCheckFrequency, __LINE__);
3395  }
3396 }
3397 
3399 {
3400  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3401  if (!StateIsLiveTV(GetState(actx)))
3402  {
3403  actx->LockDeletePlayer(__FILE__, __LINE__);
3404  bool toggle = actx->m_player && actx->m_player->IsEmbedding() &&
3405  actx->m_player->IsNearEnd() && !actx->m_player->IsPaused();
3406  actx->UnlockDeletePlayer(__FILE__, __LINE__);
3407  if (toggle)
3408  DoTogglePause(actx, true);
3409  }
3410  ReturnPlayerLock(actx);
3411 }
3412 
3414 {
3415  if (m_endOfRecording || m_inPlaylist || m_editMode || m_underNetworkControl ||
3416  m_exitPlayerTimerId)
3417  {
3418  return;
3419  }
3420 
3421  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3422  OSD *osd = GetOSDLock(mctx);
3423  if (osd && osd->DialogVisible())
3424  {
3425  ReturnOSDLock(mctx, osd);
3426  ReturnPlayerLock(mctx);
3427  return;
3428  }
3429  ReturnOSDLock(mctx, osd);
3430 
3431  bool do_prompt;
3432  mctx->LockDeletePlayer(__FILE__, __LINE__);
3433  do_prompt = (mctx->GetState() == kState_WatchingPreRecorded &&
3434  mctx->m_player &&
3435  !mctx->m_player->IsEmbedding() &&
3436  !mctx->m_player->IsPlaying());
3437  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
3438 
3439  if (do_prompt)
3440  ShowOSDPromptDeleteRecording(mctx, tr("End Of Recording"));
3441 
3442  ReturnPlayerLock(mctx);
3443 }
3444 
3446 {
3447  {
3448  QMutexLocker locker(&m_timerIdLock);
3449  if (m_videoExitDialogTimerId)
3450  KillTimer(m_videoExitDialogTimerId);
3451  m_videoExitDialogTimerId = 0;
3452  }
3453 
3454  // disable dialog and exit playback after timeout
3455  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3456  OSD *osd = GetOSDLock(mctx);
3457  if (!osd || !osd->DialogVisible(OSD_DLG_VIDEOEXIT))
3458  {
3459  ReturnOSDLock(mctx, osd);
3460  ReturnPlayerLock(mctx);
3461  return;
3462  }
3463  if (osd)
3464  osd->DialogQuit();
3465  ReturnOSDLock(mctx, osd);
3466  DoTogglePause(mctx, true);
3467  ClearOSD(mctx);
3468  PrepareToExitPlayer(mctx, __LINE__);
3469  ReturnPlayerLock(mctx);
3470 
3471  m_requestDelete = false;
3472  SetExitPlayer(true, true);
3473 }
3474 
3476 {
3477  {
3478  QMutexLocker locker(&m_timerIdLock);
3479  KillTimer(m_pseudoChangeChanTimerId);
3480  m_pseudoChangeChanTimerId = 0;
3481  }
3482 
3483  bool restartTimer = false;
3484  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3485  for (uint i = 0; mctx && (i < m_player.size()); i++)
3486  {
3487  PlayerContext *ctx = GetPlayer(mctx, i);
3489  continue;
3490 
3491  if (ctx->InStateChange())
3492  {
3493  restartTimer = true;
3494  continue;
3495  }
3496 
3497  LOG(VB_CHANNEL, LOG_INFO,
3498  QString("REC_PROGRAM -- channel change %1").arg(i));
3499 
3500  uint chanid = ctx->m_pseudoLiveTVRec->GetChanID();
3501  QString channum = ctx->m_pseudoLiveTVRec->GetChanNum();
3502  StringDeque tmp = ctx->m_prevChan;
3503 
3504  ctx->m_prevChan.clear();
3505  ChangeChannel(ctx, chanid, channum);
3506  ctx->m_prevChan = tmp;
3508  }
3509  ReturnPlayerLock(mctx);
3510 
3511  if (restartTimer)
3512  {
3513  QMutexLocker locker(&m_timerIdLock);
3514  if (!m_pseudoChangeChanTimerId)
3515  m_pseudoChangeChanTimerId = StartTimer(25, __LINE__);
3516  }
3517 }
3518 
3519 void TV::SetSpeedChangeTimer(uint when, int line)
3520 {
3521  QMutexLocker locker(&m_timerIdLock);
3522  if (m_speedChangeTimerId)
3523  KillTimer(m_speedChangeTimerId);
3524  m_speedChangeTimerId = StartTimer(when, line);
3525 }
3526 
3528 {
3529  {
3530  QMutexLocker locker(&m_timerIdLock);
3531  if (m_speedChangeTimerId)
3532  KillTimer(m_speedChangeTimerId);
3533  m_speedChangeTimerId = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
3534  }
3535 
3536  bool update_msg = false;
3537  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3538  for (uint i = 0; actx && (i < m_player.size()); i++)
3539  {
3540  PlayerContext *ctx = GetPlayer(actx, i);
3541  update_msg |= ctx->HandlePlayerSpeedChangeFFRew() && (ctx == actx);
3542  }
3543  ReturnPlayerLock(actx);
3544 
3545  actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3546  for (uint i = 0; actx && (i < m_player.size()); i++)
3547  {
3548  PlayerContext *ctx = GetPlayer(actx, i);
3549  update_msg |= ctx->HandlePlayerSpeedChangeEOF() && (ctx == actx);
3550  }
3551 
3552  if (actx && update_msg)
3553  {
3554  UpdateOSDSeekMessage(actx, actx->GetPlayMessage(), kOSDTimeout_Med);
3555  }
3556  ReturnPlayerLock(actx);
3557 }
3558 
3560 bool TV::eventFilter(QObject *o, QEvent *e)
3561 {
3562  // We want to intercept all resize events sent to the main window
3563  if ((e->type() == QEvent::Resize))
3564  return (GetMythMainWindow()!= o) ? false : event(e);
3565 
3566  // Intercept keypress events unless they need to be handled by a main UI
3567  // screen (e.g. GuideGrid, ProgramFinder)
3568 
3569  if ( (QEvent::KeyPress == e->type() || QEvent::KeyRelease == e->type())
3570  && m_ignoreKeyPresses )
3571  return false;
3572 
3573  QScopedPointer<QEvent> sNewEvent(nullptr);
3574  if (GetMythMainWindow()->keyLongPressFilter(&e, sNewEvent))
3575  return true;
3576 
3577  if (QEvent::KeyPress == e->type())
3578  return event(e);
3579 
3580  if (MythGestureEvent::kEventType == e->type())
3581  return m_ignoreKeyPresses ? false : event(e);
3582 
3583  if (e->type() == MythEvent::MythEventMessage ||
3584  e->type() == MythEvent::MythUserMessage ||
3586  e->type() == MythMediaEvent::kEventType)
3587  {
3588  customEvent(e);
3589  return true;
3590  }
3591 
3592  switch (e->type())
3593  {
3594  case QEvent::Paint:
3595  case QEvent::UpdateRequest:
3596  case QEvent::Enter:
3597  {
3598  event(e);
3599  return false;
3600  }
3601  default:
3602  return false;
3603  }
3604 }
3605 
3607 bool TV::event(QEvent *e)
3608 {
3609  if (QEvent::Resize == e->type())
3610  {
3611  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3612  mctx->LockDeletePlayer(__FILE__, __LINE__);
3613  if (mctx->m_player)
3614  mctx->m_player->WindowResized(((const QResizeEvent*) e)->size());
3615  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
3616  ReturnPlayerLock(mctx);
3617  return true;
3618  }
3619 
3620  if (QEvent::KeyPress == e->type() ||
3621  MythGestureEvent::kEventType == e->type())
3622  {
3623 #if DEBUG_ACTIONS
3624  if (QEvent::KeyPress == e->type())
3625  {
3626  LOG(VB_GENERAL, LOG_INFO, LOC + QString("keypress: %1 '%2'")
3627  .arg(((QKeyEvent*)e)->key())
3628  .arg(((QKeyEvent*)e)->text()));
3629  }
3630  else
3631  {
3632  LOG(VB_GENERAL, LOG_INFO, LOC + QString("mythgesture: g:%1 pos:%2,%3 b:%4")
3633  .arg(((MythGestureEvent*)e)->gesture())
3634  .arg(((MythGestureEvent*)e)->GetPosition().x())
3635  .arg(((MythGestureEvent*)e)->GetPosition().y())
3636  .arg(((MythGestureEvent*)e)->GetButton())
3637  );
3638  }
3639 #endif // DEBUG_ACTIONS
3640  bool handled = false;
3641  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3642  if (actx->HasPlayer())
3643  handled = ProcessKeypressOrGesture(actx, e);
3644  ReturnPlayerLock(actx);
3645  if (handled)
3646  return true;
3647  }
3648 
3649  switch (e->type())
3650  {
3651  case QEvent::Paint:
3652  case QEvent::UpdateRequest:
3653  case QEvent::Enter:
3654  DrawUnusedRects();
3655  return true;
3656  default:
3657  break;
3658  }
3659 
3660  return QObject::event(e);
3661 }
3662 
3663 bool TV::HandleTrackAction(PlayerContext *ctx, const QString &action)
3664 {
3665  ctx->LockDeletePlayer(__FILE__, __LINE__);
3666  if (!ctx->m_player)
3667  {
3668  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
3669  return false;
3670  }
3671 
3672  bool handled = true;
3673 
3676  else if (ACTION_ENABLEEXTTEXT == action)
3678  else if (ACTION_DISABLEEXTTEXT == action)
3680  else if (ACTION_ENABLEFORCEDSUBS == action)
3681  ctx->m_player->SetAllowForcedSubtitles(true);
3682  else if (ACTION_DISABLEFORCEDSUBS == action)
3683  ctx->m_player->SetAllowForcedSubtitles(false);
3684  else if (action == ACTION_ENABLESUBS)
3685  ctx->m_player->SetCaptionsEnabled(true, true);
3686  else if (action == ACTION_DISABLESUBS)
3687  ctx->m_player->SetCaptionsEnabled(false, true);
3688  else if (action == ACTION_TOGGLESUBS && !m_browseHelper->IsBrowsing())
3689  {
3690  if (m_ccInputMode)
3691  {
3692  bool valid = false;
3693  int page = GetQueuedInputAsInt(&valid, 16);
3694  if (m_vbimode == VBIMode::PAL_TT && valid)
3695  ctx->m_player->SetTeletextPage(page);
3696  else if (m_vbimode == VBIMode::NTSC_CC)
3698  max(min(page - 1, 1), 0));
3699 
3700  ClearInputQueues(ctx, true);
3701 
3702  QMutexLocker locker(&m_timerIdLock);
3703  m_ccInputMode = false;
3704  if (m_ccInputTimerId)
3705  {
3706  KillTimer(m_ccInputTimerId);
3707  m_ccInputTimerId = 0;
3708  }
3709  }
3711  {
3712  ClearInputQueues(ctx, false);
3713  AddKeyToInputQueue(ctx, 0);
3714 
3715  QMutexLocker locker(&m_timerIdLock);
3716  m_ccInputMode = true;
3717  m_asInputMode = false;
3718  m_ccInputTimerId = StartTimer(kInputModeTimeout, __LINE__);
3719  if (m_asInputTimerId)
3720  {
3721  KillTimer(m_asInputTimerId);
3722  m_asInputTimerId = 0;
3723  }
3724  }
3725  else
3726  {
3727  ctx->m_player->ToggleCaptions();
3728  }
3729  }
3730  else if (action.startsWith("TOGGLE"))
3731  {
3732  int type = to_track_type(action.mid(6));
3734  ctx->m_player->EnableTeletext();
3735  else if (type >= kTrackTypeSubtitle)
3736  ctx->m_player->ToggleCaptions(type);
3737  else
3738  handled = false;
3739  }
3740  else if (action.startsWith("SELECT"))
3741  {
3742  int type = to_track_type(action.mid(6));
3743  int num = action.section("_", -1).toInt();
3744  if (type >= kTrackTypeAudio)
3745  ctx->m_player->SetTrack(type, num);
3746  else
3747  handled = false;
3748  }
3749  else if (action.startsWith("NEXT") || action.startsWith("PREV"))
3750  {
3751  int dir = (action.startsWith("NEXT")) ? +1 : -1;
3752  int type = to_track_type(action.mid(4));
3753  if (type >= kTrackTypeAudio)
3754  ctx->m_player->ChangeTrack(type, dir);
3755  else if (action.endsWith("CC"))
3756  ctx->m_player->ChangeCaptionTrack(dir);
3757  else
3758  handled = false;
3759  }
3760  else
3761  handled = false;
3762 
3763  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
3764 
3765  return handled;
3766 }
3767 
3768 static bool has_action(const QString& action, const QStringList &actions)
3769 {
3770  QStringList::const_iterator it;
3771  for (it = actions.begin(); it != actions.end(); ++it)
3772  {
3773  if (action == *it)
3774  return true;
3775  }
3776  return false;
3777 }
3778 
3779 // Make a special check for global system-related events.
3780 //
3781 // This check needs to be done early in the keypress event processing,
3782 // because FF/REW processing causes unknown events to stop FF/REW, and
3783 // manual zoom mode processing consumes all but a few event types.
3784 // Ideally, we would just call MythScreenType::keyPressEvent()
3785 // unconditionally, but we only want certain keypresses handled by
3786 // that method.
3787 //
3788 // As a result, some of the MythScreenType::keyPressEvent() string
3789 // compare logic is copied here.
3790 static bool SysEventHandleAction(QKeyEvent *e, const QStringList &actions)
3791 {
3792  QStringList::const_iterator it;
3793  for (it = actions.begin(); it != actions.end(); ++it)
3794  {
3795  if ((*it).startsWith("SYSEVENT") ||
3796  *it == ACTION_SCREENSHOT ||
3797  *it == ACTION_TVPOWERON ||
3798  *it == ACTION_TVPOWEROFF)
3799  {
3801  keyPressEvent(e);
3802  }
3803  }
3804  return false;
3805 }
3806 
3807 QList<QKeyEvent> TV::ConvertScreenPressKeyMap(const QString &keyList)
3808 {
3809  QList<QKeyEvent> keyPressList;
3810  int i = 0;
3811  QStringList stringKeyList = keyList.split(',');
3812  QStringList::const_iterator it;
3813  for (it = stringKeyList.begin(); it != stringKeyList.end(); ++it)
3814  {
3815  QKeySequence keySequence(*it);
3816  for(i = 0; i < keySequence.count(); i++)
3817  {
3818  unsigned int keynum = keySequence[i];
3819  QKeyEvent keyEvent(QEvent::None,
3820  (int)(keynum & ~Qt::KeyboardModifierMask),
3821  (Qt::KeyboardModifiers)(keynum & Qt::KeyboardModifierMask));
3822  keyPressList.append(keyEvent);
3823  }
3824  }
3825  if (stringKeyList.count() < s_screenPressRegionCount)
3826  {
3827  // add default remainders
3828  for(; i < s_screenPressRegionCount; i++)
3829  {
3830  QKeyEvent keyEvent(QEvent::None, Qt::Key_Escape, Qt::NoModifier);
3831  keyPressList.append(keyEvent);
3832  }
3833  }
3834  return keyPressList;
3835 }
3836 
3837 bool TV::TranslateGesture(const QString &context, MythGestureEvent *e,
3838  QStringList &actions, bool isLiveTV)
3839 {
3840  if (context == "TV Playback")
3841  {
3842  // TODO make this configuable via a similar mechanism to
3843  // TranslateKeyPress
3844  // possibly with configurable hot zones of various sizes in a theme
3845  // TODO enhance gestures to support other non Click types too
3846  if ((e->gesture() == MythGestureEvent::Click) &&
3848  {
3849  // divide screen into 12 regions
3850  QSize size = GetMythMainWindow()->size();
3851  QPoint pos = e->GetPosition();
3852  int region = 0;
3853  const int widthDivider = 4;
3854  int w4 = size.width() / widthDivider;
3855  region = pos.x() / w4;
3856  int h3 = size.height() / 3;
3857  region += (pos.y() / h3) * widthDivider;
3858 
3859  if (isLiveTV)
3860  {
3862  context, &(m_screenPressKeyMapLiveTV[region]), actions, true);
3863  }
3865  context, &(m_screenPressKeyMapPlayback[region]), actions, true);
3866  }
3867  return false;
3868  }
3869  return false;
3870 }
3871 
3872 bool TV::TranslateKeyPressOrGesture(const QString &context,
3873  QEvent *e, QStringList &actions,
3874  bool isLiveTV, bool allowJumps)
3875 {
3876  if (QEvent::KeyPress == e->type())
3877  {
3879  context, (QKeyEvent*)e, actions, allowJumps);
3880  }
3881  if (MythGestureEvent::kEventType == e->type())
3882  {
3883  return TranslateGesture(context, (MythGestureEvent*)e, actions, isLiveTV);
3884  }
3885 
3886  return false;
3887 }
3888 
3890 {
3891  bool ignoreKeys = actx->IsPlayerChangingBuffers();
3892 #if DEBUG_ACTIONS
3893  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ignoreKeys: %1")
3894  .arg(ignoreKeys));
3895 #endif // DEBUG_ACTIONS
3896 
3897  if (m_idleTimerId)
3898  {
3899  KillTimer(m_idleTimerId);
3900  m_idleTimerId = StartTimer(m_dbIdleTimeout, __LINE__);
3901  }
3902 
3903 #ifdef Q_OS_LINUX
3904  // Fixups for _some_ linux native codes that QT doesn't know
3905  QKeyEvent* eKeyEvent = dynamic_cast<QKeyEvent*>(e);
3906  if (eKeyEvent) {
3907  if (eKeyEvent->key() <= 0)
3908  {
3909  int keycode = 0;
3910  switch(eKeyEvent->nativeScanCode())
3911  {
3912  case 209: // XF86AudioPause
3913  keycode = Qt::Key_MediaPause;
3914  break;
3915  default:
3916  break;
3917  }
3918 
3919  if (keycode > 0)
3920  {
3921  QKeyEvent *key = new QKeyEvent(QEvent::KeyPress, keycode,
3922  eKeyEvent->modifiers());
3923  QCoreApplication::postEvent(this, key);
3924  }
3925  }
3926  }
3927 #endif
3928 
3929  QStringList actions;
3930  bool handled = false;
3931  bool alreadyTranslatedPlayback = false;
3932 
3933  TVState state = GetState(actx);
3934  bool isLiveTV = StateIsLiveTV(state);
3935 
3936  if (ignoreKeys)
3937  {
3938  handled = TranslateKeyPressOrGesture("TV Playback", e, actions, isLiveTV);
3939  alreadyTranslatedPlayback = true;
3940 
3941  if (handled || actions.isEmpty())
3942  return handled;
3943 
3944  bool esc = has_action("ESCAPE", actions) ||
3945  has_action("BACK", actions);
3946  bool pause = has_action(ACTION_PAUSE, actions);
3947  bool play = has_action(ACTION_PLAY, actions);
3948 
3949  if ((!esc || m_browseHelper->IsBrowsing()) && !pause && !play)
3950  return false;
3951  }
3952 
3953  OSD *osd = GetOSDLock(actx);
3954  if (osd && osd->DialogVisible())
3955  {
3956  if (QEvent::KeyPress == e->type())
3957  {
3958  handled = osd->DialogHandleKeypress((QKeyEvent*)e);
3959  }
3960  if (MythGestureEvent::kEventType == e->type())
3961  {
3962  handled = osd->DialogHandleGesture((MythGestureEvent*)e);
3963  }
3964  }
3965  ReturnOSDLock(actx, osd);
3966 
3967  if (m_editMode && !handled)
3968  {
3969  handled |= TranslateKeyPressOrGesture(
3970  "TV Editing", e, actions, isLiveTV);
3971 
3972  if (!handled && actx->m_player)
3973  {
3974  if (has_action("MENU", actions))
3975  {
3976  ShowOSDCutpoint(actx, "EDIT_CUT_POINTS");
3977  handled = true;
3978  }
3979  if (has_action(ACTION_MENUCOMPACT, actions))
3980  {
3981  ShowOSDCutpoint(actx, "EDIT_CUT_POINTS_COMPACT");
3982  handled = true;
3983  }
3984  if (has_action("ESCAPE", actions))
3985  {
3986  if (!actx->m_player->IsCutListSaved())
3987  ShowOSDCutpoint(actx, "EXIT_EDIT_MODE");
3988  else
3989  {
3990  actx->LockDeletePlayer(__FILE__, __LINE__);
3991  actx->m_player->DisableEdit(0);
3992  actx->UnlockDeletePlayer(__FILE__, __LINE__);
3993  }
3994  handled = true;
3995  }
3996  else
3997  {
3998  actx->LockDeletePlayer(__FILE__, __LINE__);
3999  int64_t current_frame = actx->m_player->GetFramesPlayed();
4000  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4001  if ((has_action(ACTION_SELECT, actions)) &&
4002  (actx->m_player->IsInDelete(current_frame)) &&
4003  (!(actx->m_player->HasTemporaryMark())))
4004  {
4005  ShowOSDCutpoint(actx, "EDIT_CUT_POINTS");
4006  handled = true;
4007  }
4008  else
4009  handled |=
4010  actx->m_player->HandleProgramEditorActions(actions);
4011  }
4012  }
4013  if (handled)
4014  m_editMode = (actx->m_player && actx->m_player->GetEditMode());
4015  }
4016 
4017  if (handled)
4018  return true;
4019 
4020  // If text is already queued up, be more lax on what is ok.
4021  // This allows hex teletext entry and minor channel entry.
4022  if (QEvent::KeyPress == e->type())
4023  {
4024  const QString txt = ((QKeyEvent*)e)->text();
4025  if (HasQueuedInput() && (1 == txt.length()))
4026  {
4027  bool ok = false;
4028  (void)txt.toInt(&ok, 16);
4029  if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
4030  {
4031  AddKeyToInputQueue(actx, txt.at(0).toLatin1());
4032  return true;
4033  }
4034  }
4035  }
4036 
4037  // Teletext menu
4038  actx->LockDeletePlayer(__FILE__, __LINE__);
4039  if (actx->m_player && (actx->m_player->GetCaptionMode() == kDisplayTeletextMenu))
4040  {
4041  QStringList tt_actions;
4042  handled = TranslateKeyPressOrGesture(
4043  "Teletext Menu", e, tt_actions, isLiveTV);
4044 
4045  if (!handled && !tt_actions.isEmpty())
4046  {
4047  for (int i = 0; i < tt_actions.size(); i++)
4048  {
4049  if (actx->m_player->HandleTeletextAction(tt_actions[i]))
4050  {
4051  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4052  return true;
4053  }
4054  }
4055  }
4056  }
4057 
4058  // Interactive television
4059  if (actx->m_player && actx->m_player->GetInteractiveTV())
4060  {
4061  if (!alreadyTranslatedPlayback)
4062  {
4063  handled = TranslateKeyPressOrGesture(
4064  "TV Playback", e, actions, isLiveTV);
4065  alreadyTranslatedPlayback = true;
4066  }
4067  if (!handled && !actions.isEmpty())
4068  {
4069  for (int i = 0; i < actions.size(); i++)
4070  {
4071  if (actx->m_player->ITVHandleAction(actions[i]))
4072  {
4073  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4074  return true;
4075  }
4076  }
4077  }
4078  }
4079  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4080 
4081  if (!alreadyTranslatedPlayback)
4082  {
4083  handled = TranslateKeyPressOrGesture(
4084  "TV Playback", e, actions, isLiveTV);
4085  }
4086  if (handled || actions.isEmpty())
4087  return handled;
4088 
4089  handled = false;
4090 
4091  bool isDVD = actx->m_buffer && actx->m_buffer->IsDVD();
4092  bool isMenuOrStill = actx->m_buffer && actx->m_buffer->IsInDiscMenuOrStillFrame();
4093 
4094  if (QEvent::KeyPress == e->type())
4095  {
4096  handled = handled || SysEventHandleAction((QKeyEvent*)e, actions);
4097  }
4098  handled = handled || BrowseHandleAction(actx, actions);
4099  handled = handled || ManualZoomHandleAction(actx, actions);
4100  handled = handled || PictureAttributeHandleAction(actx, actions);
4101  handled = handled || TimeStretchHandleAction(actx, actions);
4102  handled = handled || AudioSyncHandleAction(actx, actions);
4103  handled = handled || SubtitleZoomHandleAction(actx, actions);
4104  handled = handled || SubtitleDelayHandleAction(actx, actions);
4105  handled = handled || DiscMenuHandleAction(actx, actions);
4106  handled = handled || ActiveHandleAction(
4107  actx, actions, isDVD, isMenuOrStill);
4108  handled = handled || ToggleHandleAction(actx, actions, isDVD);
4109  handled = handled || PxPHandleAction(actx, actions);
4110  handled = handled || FFRewHandleAction(actx, actions);
4111  handled = handled || ActivePostQHandleAction(actx, actions);
4112 
4113 #if DEBUG_ACTIONS
4114  for (int i = 0; i < actions.size(); ++i)
4115  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("handled(%1) actions[%2](%3)")
4116  .arg(handled).arg(i).arg(actions[i]));
4117 #endif // DEBUG_ACTIONS
4118 
4119  if (handled)
4120  return true;
4121 
4122  if (!handled)
4123  {
4124  for (int i = 0; i < actions.size() && !handled; i++)
4125  {
4126  QString action = actions[i];
4127  bool ok = false;
4128  int val = action.toInt(&ok);
4129 
4130  if (ok)
4131  {
4132  AddKeyToInputQueue(actx, '0' + val);
4133  handled = true;
4134  }
4135  }
4136  }
4137 
4138  return true;
4139 }
4140 
4141 bool TV::BrowseHandleAction(PlayerContext *ctx, const QStringList &actions)
4142 {
4143  if (!m_browseHelper->IsBrowsing())
4144  return false;
4145 
4146  bool handled = true;
4147 
4148  if (has_action(ACTION_UP, actions) || has_action(ACTION_CHANNELUP, actions))
4149  m_browseHelper->BrowseDispInfo(ctx, BROWSE_UP);
4150  else if (has_action(ACTION_DOWN, actions) || has_action(ACTION_CHANNELDOWN, actions))
4151  m_browseHelper->BrowseDispInfo(ctx, BROWSE_DOWN);
4152  else if (has_action(ACTION_LEFT, actions))
4153  m_browseHelper->BrowseDispInfo(ctx, BROWSE_LEFT);
4154  else if (has_action(ACTION_RIGHT, actions))
4155  m_browseHelper->BrowseDispInfo(ctx, BROWSE_RIGHT);
4156  else if (has_action("NEXTFAV", actions))
4157  m_browseHelper->BrowseDispInfo(ctx, BROWSE_FAVORITE);
4158  else if (has_action(ACTION_SELECT, actions))
4159  {
4160  m_browseHelper->BrowseEnd(ctx, true);
4161  }
4162  else if (has_action(ACTION_CLEAROSD, actions) ||
4163  has_action("ESCAPE", actions) ||
4164  has_action("BACK", actions) ||
4165  has_action("TOGGLEBROWSE", actions))
4166  {
4167  m_browseHelper->BrowseEnd(ctx, false);
4168  }
4169  else if (has_action(ACTION_TOGGLERECORD, actions))
4170  QuickRecord(ctx);
4171  else
4172  {
4173  handled = false;
4174  QStringList::const_iterator it = actions.begin();
4175  for (; it != actions.end(); ++it)
4176  {
4177  if ((*it).length() == 1 && (*it)[0].isDigit())
4178  {
4179  AddKeyToInputQueue(ctx, (*it)[0].toLatin1());
4180  handled = true;
4181  }
4182  }
4183  }
4184 
4185  // only pass-through actions listed below
4186  return handled ||
4187  !(has_action(ACTION_VOLUMEDOWN, actions) ||
4188  has_action(ACTION_VOLUMEUP, actions) ||
4189  has_action("STRETCHINC", actions) ||
4190  has_action("STRETCHDEC", actions) ||
4191  has_action(ACTION_MUTEAUDIO, actions) ||
4192  has_action("CYCLEAUDIOCHAN", actions) ||
4193  has_action("BOTTOMLINEMOVE", actions) ||
4194  has_action("BOTTOMLINESAVE", actions) ||
4195  has_action("TOGGLEASPECT", actions) ||
4196  has_action("TOGGLEPIPMODE", actions) ||
4197  has_action("TOGGLEPIPSTATE", actions) ||
4198  has_action("NEXTPIPWINDOW", actions) ||
4199  has_action("CREATEPIPVIEW", actions) ||
4200  has_action("CREATEPBPVIEW", actions) ||
4201  has_action("SWAPPIP", actions));
4202 }
4203 
4204 bool TV::ManualZoomHandleAction(PlayerContext *actx, const QStringList &actions)
4205 {
4206  if (!m_zoomMode)
4207  return false;
4208 
4209  actx->LockDeletePlayer(__FILE__, __LINE__);
4210  if (!actx->m_player)
4211  {
4212  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4213  return false;
4214  }
4215 
4216  bool end_manual_zoom = false;
4217  bool handled = true;
4218  bool updateOSD = true;
4219  ZoomDirection zoom = kZoom_END;
4220  if (has_action(ACTION_ZOOMUP, actions))
4221  zoom = kZoomUp;
4222  else if (has_action(ACTION_ZOOMDOWN, actions))
4223  zoom = kZoomDown;
4224  else if (has_action(ACTION_ZOOMLEFT, actions))
4225  zoom = kZoomLeft;
4226  else if (has_action(ACTION_ZOOMRIGHT, actions))
4227  zoom = kZoomRight;
4228  else if (has_action(ACTION_ZOOMASPECTUP, actions))
4229  zoom = kZoomAspectUp;
4230  else if (has_action(ACTION_ZOOMASPECTDOWN, actions))
4231  zoom = kZoomAspectDown;
4232  else if (has_action(ACTION_ZOOMIN, actions))
4233  zoom = kZoomIn;
4234  else if (has_action(ACTION_ZOOMOUT, actions))
4235  zoom = kZoomOut;
4236  else if (has_action(ACTION_ZOOMVERTICALIN, actions))
4237  zoom = kZoomVerticalIn;
4238  else if (has_action(ACTION_ZOOMVERTICALOUT, actions))
4239  zoom = kZoomVerticalOut;
4240  else if (has_action(ACTION_ZOOMHORIZONTALIN, actions))
4241  zoom = kZoomHorizontalIn;
4242  else if (has_action(ACTION_ZOOMHORIZONTALOUT, actions))
4243  zoom = kZoomHorizontalOut;
4244  else if (has_action(ACTION_ZOOMQUIT, actions))
4245  {
4246  zoom = kZoomHome;
4247  end_manual_zoom = true;
4248  }
4249  else if (has_action(ACTION_ZOOMCOMMIT, actions))
4250  {
4251  end_manual_zoom = true;
4252  SetManualZoom(actx, false, tr("Zoom Committed"));
4253  }
4254  else if (has_action(ACTION_UP, actions) ||
4255  has_action(ACTION_CHANNELUP, actions))
4256  {
4257  zoom = kZoomUp;
4258  }
4259  else if (has_action(ACTION_DOWN, actions) ||
4260  has_action(ACTION_CHANNELDOWN, actions))
4261  {
4262  zoom = kZoomDown;
4263  }
4264  else if (has_action(ACTION_LEFT, actions))
4265  zoom = kZoomLeft;
4266  else if (has_action(ACTION_RIGHT, actions))
4267  zoom = kZoomRight;
4268  else if (has_action(ACTION_VOLUMEUP, actions))
4269  zoom = kZoomAspectUp;
4270  else if (has_action(ACTION_VOLUMEDOWN, actions))
4271  zoom = kZoomAspectDown;
4272  else if (has_action("ESCAPE", actions) ||
4273  has_action("BACK", actions))
4274  {
4275  zoom = kZoomHome;
4276  end_manual_zoom = true;
4277  }
4278  else if (has_action(ACTION_SELECT, actions))
4279  {
4280  end_manual_zoom = true;
4281  SetManualZoom(actx, false, tr("Zoom Committed"));
4282  }
4283  else if (has_action(ACTION_JUMPFFWD, actions))
4284  zoom = kZoomIn;
4285  else if (has_action(ACTION_JUMPRWND, actions))
4286  zoom = kZoomOut;
4287  else
4288  {
4289  updateOSD = false;
4290  // only pass-through actions listed below
4291  handled = !(has_action("STRETCHINC", actions) ||
4292  has_action("STRETCHDEC", actions) ||
4293  has_action(ACTION_MUTEAUDIO, actions) ||
4294  has_action("CYCLEAUDIOCHAN", actions) ||
4295  has_action(ACTION_PAUSE, actions) ||
4296  has_action(ACTION_CLEAROSD, actions));
4297  }
4298  QString msg = tr("Zoom Committed");
4299  if (zoom != kZoom_END)
4300  {
4301  actx->m_player->Zoom(zoom);
4302  if (end_manual_zoom)
4303  msg = tr("Zoom Ignored");
4304  else
4305  msg = actx->m_player->GetVideoOutput()->GetZoomString();
4306  }
4307  else if (end_manual_zoom)
4308  msg = tr("%1 Committed")
4309  .arg(actx->m_player->GetVideoOutput()->GetZoomString());
4310  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4311 
4312  if (updateOSD)
4313  SetManualZoom(actx, !end_manual_zoom, msg);
4314 
4315  return handled;
4316 }
4317 
4319  const QStringList &actions)
4320 {
4321  if (!m_adjustingPicture)
4322  return false;
4323 
4324  bool handled = true;
4325  if (has_action(ACTION_LEFT, actions))
4326  {
4327  DoChangePictureAttribute(ctx, m_adjustingPicture,
4328  m_adjustingPictureAttribute, false);
4329  }
4330  else if (has_action(ACTION_RIGHT, actions))
4331  {
4332  DoChangePictureAttribute(ctx, m_adjustingPicture,
4333  m_adjustingPictureAttribute, true);
4334  }
4335  else
4336  handled = false;
4337 
4338  return handled;
4339 }
4340 
4342  const QStringList &actions)
4343 {
4344  if (!m_stretchAdjustment)
4345  return false;
4346 
4347  bool handled = true;
4348 
4349  if (has_action(ACTION_LEFT, actions))
4350  ChangeTimeStretch(ctx, -1);
4351  else if (has_action(ACTION_RIGHT, actions))
4352  ChangeTimeStretch(ctx, 1);
4353  else if (has_action(ACTION_DOWN, actions))
4354  ChangeTimeStretch(ctx, -5);
4355  else if (has_action(ACTION_UP, actions))
4356  ChangeTimeStretch(ctx, 5);
4357  else if (has_action("ADJUSTSTRETCH", actions))
4358  ToggleTimeStretch(ctx);
4359  else if (has_action(ACTION_SELECT, actions))
4360  ClearOSD(ctx);
4361  else
4362  handled = false;
4363 
4364  return handled;
4365 }
4366 
4368  const QStringList &actions)
4369 {
4370  if (!m_audiosyncAdjustment)
4371  return false;
4372 
4373  bool handled = true;
4374 
4375  if (has_action(ACTION_LEFT, actions))
4376  ChangeAudioSync(ctx, -1);
4377  else if (has_action(ACTION_RIGHT, actions))
4378  ChangeAudioSync(ctx, 1);
4379  else if (has_action(ACTION_UP, actions))
4380  ChangeAudioSync(ctx, 10);
4381  else if (has_action(ACTION_DOWN, actions))
4382  ChangeAudioSync(ctx, -10);
4383  else if (has_action(ACTION_TOGGELAUDIOSYNC, actions))
4384  ClearOSD(ctx);
4385  else if (has_action(ACTION_SELECT, actions))
4386  ClearOSD(ctx);
4387  else
4388  handled = false;
4389 
4390  return handled;
4391 }
4392 
4394  const QStringList &actions)
4395 {
4396  if (!m_subtitleZoomAdjustment)
4397  return false;
4398 
4399  bool handled = true;
4400 
4401  if (has_action(ACTION_LEFT, actions))
4402  ChangeSubtitleZoom(ctx, -1);
4403  else if (has_action(ACTION_RIGHT, actions))
4404  ChangeSubtitleZoom(ctx, 1);
4405  else if (has_action(ACTION_UP, actions))
4406  ChangeSubtitleZoom(ctx, 10);
4407  else if (has_action(ACTION_DOWN, actions))
4408  ChangeSubtitleZoom(ctx, -10);
4409  else if (has_action(ACTION_TOGGLESUBTITLEZOOM, actions))
4410  ClearOSD(ctx);
4411  else if (has_action(ACTION_SELECT, actions))
4412  ClearOSD(ctx);
4413  else
4414  handled = false;
4415 
4416  return handled;
4417 }
4418 
4420  const QStringList &actions)
4421 {
4422  if (!m_subtitleDelayAdjustment)
4423  return false;
4424 
4425  bool handled = true;
4426 
4427  if (has_action(ACTION_LEFT, actions))
4428  ChangeSubtitleDelay(ctx, -5);
4429  else if (has_action(ACTION_RIGHT, actions))
4430  ChangeSubtitleDelay(ctx, 5);
4431  else if (has_action(ACTION_UP, actions))
4432  ChangeSubtitleDelay(ctx, 25);
4433  else if (has_action(ACTION_DOWN, actions))
4434  ChangeSubtitleDelay(ctx, -25);
4435  else if (has_action(ACTION_TOGGLESUBTITLEDELAY, actions))
4436  ClearOSD(ctx);
4437  else if (has_action(ACTION_SELECT, actions))
4438  ClearOSD(ctx);
4439  else
4440  handled = false;
4441 
4442  return handled;
4443 }
4444 
4445 bool TV::DiscMenuHandleAction(PlayerContext *ctx, const QStringList &actions)
4446 {
4447  int64_t pts = 0;
4449  if (output)
4450  {
4451  VideoFrame *frame = output->GetLastShownFrame();
4452  if (frame)
4453  {
4454  // convert timecode (msec) to pts (90kHz)
4455  pts = (int64_t)(frame->timecode * 90);
4456  }
4457  }
4458  return ctx->m_buffer->HandleAction(actions, pts);
4459 }
4460 
4461 bool TV::Handle3D(PlayerContext *ctx, const QString &action)
4462 {
4463  ctx->LockDeletePlayer(__FILE__, __LINE__);
4464  if (ctx->m_player && ctx->m_player->GetVideoOutput() &&
4466  {
4468  if (ACTION_3DSIDEBYSIDE == action)
4470  else if (ACTION_3DSIDEBYSIDEDISCARD == action)
4472  else if (ACTION_3DTOPANDBOTTOM == action)
4477  SetOSDMessage(ctx, StereoscopictoString(mode));
4478  }
4479  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4480  return true;
4481 }
4482 
4484  const QStringList &actions,
4485  bool isDVD, bool isDVDStill)
4486 {
4487  bool handled = true;
4488 
4489  if (has_action("SKIPCOMMERCIAL", actions) && !isDVD)
4490  DoSkipCommercials(ctx, 1);
4491  else if (has_action("SKIPCOMMBACK", actions) && !isDVD)
4492  DoSkipCommercials(ctx, -1);
4493  else if (has_action("QUEUETRANSCODE", actions) && !isDVD)
4494  DoQueueTranscode(ctx, "Default");
4495  else if (has_action("QUEUETRANSCODE_AUTO", actions) && !isDVD)
4496  DoQueueTranscode(ctx, "Autodetect");
4497  else if (has_action("QUEUETRANSCODE_HIGH", actions) && !isDVD)
4498  DoQueueTranscode(ctx, "High Quality");
4499  else if (has_action("QUEUETRANSCODE_MEDIUM", actions) && !isDVD)
4500  DoQueueTranscode(ctx, "Medium Quality");
4501  else if (has_action("QUEUETRANSCODE_LOW", actions) && !isDVD)
4502  DoQueueTranscode(ctx, "Low Quality");
4503  else if (has_action(ACTION_PLAY, actions))
4504  DoPlay(ctx);
4505  else if (has_action(ACTION_PAUSE, actions))
4506  DoTogglePause(ctx, true);
4507  else if (has_action("SPEEDINC", actions) && !isDVDStill)
4508  ChangeSpeed(ctx, 1);
4509  else if (has_action("SPEEDDEC", actions) && !isDVDStill)
4510  ChangeSpeed(ctx, -1);
4511  else if (has_action("ADJUSTSTRETCH", actions))
4512  ChangeTimeStretch(ctx, 0); // just display
4513  else if (has_action("CYCLECOMMSKIPMODE",actions) && !isDVD)
4514  SetAutoCommercialSkip(ctx, kCommSkipIncr);
4515  else if (has_action("NEXTSCAN", actions))
4516  {
4517  QString msg;
4518  ctx->LockDeletePlayer(__FILE__, __LINE__);
4519  if (ctx->m_player)
4520  {
4521  ctx->m_player->NextScanType();
4522  msg = toString(ctx->m_player->GetScanType());
4523  }
4524  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4525 
4526  if (!msg.isEmpty())
4527  SetOSDMessage(ctx, msg);
4528  }
4529  else if (has_action(ACTION_SEEKARB, actions) && !isDVD)
4530  {
4531  if (m_asInputMode)
4532  {
4533  ClearInputQueues(ctx, true);
4534  SetOSDText(ctx, "osd_input", "osd_number_entry", tr("Seek:"),
4535  kOSDTimeout_Med);
4536 
4537  QMutexLocker locker(&m_timerIdLock);
4538  m_asInputMode = false;
4539  if (m_asInputTimerId)
4540  {
4541  KillTimer(m_asInputTimerId);
4542  m_asInputTimerId = 0;
4543  }
4544  }
4545  else
4546  {
4547  ClearInputQueues(ctx, false);
4548  AddKeyToInputQueue(ctx, 0);
4549 
4550  QMutexLocker locker(&m_timerIdLock);
4551  m_asInputMode = true;
4552  m_ccInputMode = false;
4553  m_asInputTimerId = StartTimer(kInputModeTimeout, __LINE__);
4554  if (m_ccInputTimerId)
4555  {
4556  KillTimer(m_ccInputTimerId);
4557  m_ccInputTimerId = 0;
4558  }
4559  }
4560  }
4561  else if (has_action(ACTION_JUMPRWND, actions))
4562  DoJumpRWND(ctx);
4563  else if (has_action(ACTION_JUMPFFWD, actions))
4564  DoJumpFFWD(ctx);
4565  else if (has_action(ACTION_JUMPBKMRK, actions))
4566  {
4567  ctx->LockDeletePlayer(__FILE__, __LINE__);
4568  uint64_t bookmark = ctx->m_player->GetBookmark();
4569  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4570 
4571  if (bookmark)
4572  {
4573  DoPlayerSeekToFrame(ctx, bookmark);
4574  ctx->LockDeletePlayer(__FILE__, __LINE__);
4575  UpdateOSDSeekMessage(ctx, tr("Jump to Bookmark"), kOSDTimeout_Med);
4576  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4577  }
4578  }
4579  else if (has_action(ACTION_JUMPSTART,actions))
4580  {
4581  DoSeek(ctx, 0, tr("Jump to Beginning"),
4582  /*timeIsOffset*/false,
4583  /*honorCutlist*/true);
4584  }
4585  else if (has_action(ACTION_CLEAROSD, actions))
4586  {
4587  ClearOSD(ctx);
4588  }
4589  else if (has_action(ACTION_VIEWSCHEDULED, actions))
4590  EditSchedule(ctx, kViewSchedule);
4591  else if (HandleJumpToProgramAction(ctx, actions))
4592  {
4593  }
4594  else if (has_action(ACTION_SIGNALMON, actions))
4595  {
4596  if ((GetState(ctx) == kState_WatchingLiveTV) && ctx->m_recorder)
4597  {
4598  QString input = ctx->m_recorder->GetInput();
4600 
4601  if (timeout == 0xffffffff)
4602  {
4603  SetOSDMessage(ctx, "No Signal Monitor");
4604  return false;
4605  }
4606 
4607  int rate = m_sigMonMode ? 0 : 100;
4608  bool notify = m_sigMonMode ? false : true;
4609 
4610  PauseLiveTV(ctx);
4611  ctx->m_recorder->SetSignalMonitoringRate(rate, notify);
4612  UnpauseLiveTV(ctx);
4613 
4614  m_lockTimerOn = false;
4615  m_sigMonMode = !m_sigMonMode;
4616  }
4617  }
4618  else if (has_action(ACTION_SCREENSHOT, actions))
4619  {
4620  ctx->LockDeletePlayer(__FILE__, __LINE__);
4621  if (ctx->m_player && ctx->m_player->GetScreenShot())
4622  {
4623  // VideoOutput has saved screenshot
4624  }
4625  else
4626  {
4628  }
4629  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4630  }
4631  else if (has_action(ACTION_STOP, actions))
4632  {
4633  PrepareToExitPlayer(ctx, __LINE__);
4634  SetExitPlayer(true, true);
4635  }
4636  else if (has_action(ACTION_EXITSHOWNOPROMPTS, actions))
4637  {
4638  m_requestDelete = false;
4639  PrepareToExitPlayer(ctx, __LINE__);
4640  SetExitPlayer(true, true);
4641  }
4642  else if (has_action("ESCAPE", actions) ||
4643  has_action("BACK", actions))
4644  {
4645  if (StateIsLiveTV(ctx->GetState()) &&
4646  (ctx->m_lastSignalMsgTime.elapsed() <
4648  {
4649  ClearOSD(ctx);
4650  }
4651  else
4652  {
4653  OSD *osd = GetOSDLock(ctx);
4654  if (osd && osd->IsVisible())
4655  {
4656  ClearOSD(ctx);
4657  ReturnOSDLock(ctx, osd);
4658  return handled;
4659  }
4660  ReturnOSDLock(ctx, osd);
4661  }
4662 
4663  NormalSpeed(ctx);
4664 
4665  StopFFRew(ctx);
4666 
4667  bool do_exit = false;
4668 
4669  if (StateIsLiveTV(GetState(ctx)))
4670  {
4671  if (ctx->HasPlayer() && (12 & m_dbPlaybackExitPrompt))
4672  {
4673  ShowOSDStopWatchingRecording(ctx);
4674  return handled;
4675  }
4676  do_exit = true;
4677  }
4678  else
4679  {
4680  if (ctx->HasPlayer() && (5 & m_dbPlaybackExitPrompt) &&
4681  !m_underNetworkControl && !isDVDStill)
4682  {
4683  ShowOSDStopWatchingRecording(ctx);
4684  return handled;
4685  }
4686  PrepareToExitPlayer(ctx, __LINE__);
4687  m_requestDelete = false;
4688  do_exit = true;
4689  }
4690 
4691  if (do_exit)
4692  {
4693  PlayerContext *mctx = GetPlayer(ctx, 0);
4694  if (mctx != ctx)
4695  { // A PIP is active, just tear it down..
4696  PxPTeardownView(ctx);
4697  return handled;
4698  }
4699 
4700  // If it's a DVD, and we're not trying to execute a
4701  // jumppoint, try to back up.
4702  if (isDVD &&
4703  !GetMythMainWindow()->IsExitingToMain() &&
4704  has_action("BACK", actions) &&
4705  ctx->m_buffer && ctx->m_buffer->DVD()->GoBack())
4706  {
4707  return handled;
4708  }
4709  SetExitPlayer(true, true);
4710  }
4711 
4712  SetActive(ctx, 0, false);
4713  }
4714  else if (has_action(ACTION_ENABLEUPMIX, actions))
4715  EnableUpmix(ctx, true);
4716  else if (has_action(ACTION_DISABLEUPMIX, actions))
4717  EnableUpmix(ctx, false);
4718  else if (has_action(ACTION_VOLUMEDOWN, actions))
4719  ChangeVolume(ctx, false);
4720  else if (has_action(ACTION_VOLUMEUP, actions))
4721  ChangeVolume(ctx, true);
4722  else if (has_action("CYCLEAUDIOCHAN", actions))
4723  ToggleMute(ctx, true);
4724  else if (has_action(ACTION_MUTEAUDIO, actions))
4725  ToggleMute(ctx);
4726  else if (has_action("STRETCHINC", actions))
4727  ChangeTimeStretch(ctx, 1);
4728  else if (has_action("STRETCHDEC", actions))
4729  ChangeTimeStretch(ctx, -1);
4730  else if (has_action("MENU", actions))
4731  ShowOSDMenu();
4732  else if (has_action(ACTION_MENUCOMPACT, actions))
4733  ShowOSDMenu(true);
4734  else if (has_action("INFO", actions) ||
4735  has_action("INFOWITHCUTLIST", actions))
4736  {
4737  if (HasQueuedInput())
4738  {
4739  DoArbSeek(ctx, ARBSEEK_SET,
4740  has_action("INFOWITHCUTLIST", actions));
4741  }
4742  else
4743  ToggleOSD(ctx, true);
4744  }
4745  else if (has_action(ACTION_TOGGLEOSDDEBUG, actions))
4746  ToggleOSDDebug(ctx);
4747  else if (!isDVDStill && SeekHandleAction(ctx, actions, isDVD))
4748  {
4749  }
4750  else
4751  {
4752  handled = false;
4753  QStringList::const_iterator it = actions.begin();
4754  for (; it != actions.end() && !handled; ++it)
4755  handled = HandleTrackAction(ctx, *it);
4756  }
4757 
4758  return handled;
4759 }
4760 
4761 bool TV::FFRewHandleAction(PlayerContext *ctx, const QStringList &actions)
4762 {
4763  bool handled = false;
4764 
4765  if (ctx->m_ffRewState)
4766  {
4767  for (int i = 0; i < actions.size() && !handled; i++)
4768  {
4769  QString action = actions[i];
4770  bool ok = false;
4771  int val = action.toInt(&ok);
4772 
4773  if (ok && val < (int)m_ffRewSpeeds.size())
4774  {
4775  SetFFRew(ctx, val);
4776  handled = true;
4777  }
4778  }
4779 
4780  if (!handled)
4781  {
4782  DoPlayerSeek(ctx, StopFFRew(ctx));
4783  UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Short);
4784  handled = true;
4785  }
4786  }
4787 
4788  if (ctx->m_ffRewSpeed)
4789  {
4790  NormalSpeed(ctx);
4791  UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Short);
4792  handled = true;
4793  }
4794 
4795  return handled;
4796 }
4797 
4799  const QStringList &actions, bool isDVD)
4800 {
4801  bool handled = true;
4802  bool islivetv = StateIsLiveTV(GetState(ctx));
4803 
4804  if (has_action(ACTION_BOTTOMLINEMOVE, actions))
4805  ToggleMoveBottomLine(ctx);
4806  else if (has_action(ACTION_BOTTOMLINESAVE, actions))
4807  SaveBottomLine(ctx);
4808  else if (has_action("TOGGLEASPECT", actions))
4809  ToggleAspectOverride(ctx);
4810  else if (has_action("TOGGLEFILL", actions))
4811  ToggleAdjustFill(ctx);
4812  else if (has_action(ACTION_TOGGELAUDIOSYNC, actions))
4813  ChangeAudioSync(ctx, 0); // just display
4814  else if (has_action(ACTION_TOGGLESUBTITLEZOOM, actions))
4815  ChangeSubtitleZoom(ctx, 0); // just display
4816  else if (has_action(ACTION_TOGGLESUBTITLEDELAY, actions))
4817  ChangeSubtitleDelay(ctx, 0); // just display
4818  else if (has_action(ACTION_TOGGLEVISUALISATION, actions))
4819  EnableVisualisation(ctx, false, true /*toggle*/);
4820  else if (has_action(ACTION_ENABLEVISUALISATION, actions))
4821  EnableVisualisation(ctx, true);
4822  else if (has_action(ACTION_DISABLEVISUALISATION, actions))
4823  EnableVisualisation(ctx, false);
4824  else if (has_action("TOGGLEPICCONTROLS", actions))
4825  DoTogglePictureAttribute(ctx, kAdjustingPicture_Playback);
4826  else if (has_action(ACTION_TOGGLESTUDIOLEVELS, actions))
4827  DoToggleStudioLevels(ctx);
4828  else if (has_action(ACTION_TOGGLENIGHTMODE, actions))
4829  DoToggleNightMode(ctx);
4830  else if (has_action("TOGGLESTRETCH", actions))
4831  ToggleTimeStretch(ctx);
4832  else if (has_action(ACTION_TOGGLEUPMIX, actions))
4833  EnableUpmix(ctx, false, true);
4834  else if (has_action(ACTION_TOGGLESLEEP, actions))
4835  ToggleSleepTimer(ctx);
4836  else if (has_action(ACTION_TOGGLERECORD, actions) && islivetv)
4837  QuickRecord(ctx);
4838  else if (has_action(ACTION_TOGGLEFAV, actions) && islivetv)
4839  ToggleChannelFavorite(ctx);
4840  else if (has_action(ACTION_TOGGLECHANCONTROLS, actions) && islivetv)
4841  DoTogglePictureAttribute(ctx, kAdjustingPicture_Channel);
4842  else if (has_action(ACTION_TOGGLERECCONTROLS, actions) && islivetv)
4843  DoTogglePictureAttribute(ctx, kAdjustingPicture_Recording);
4844  else if (has_action("TOGGLEBROWSE", actions))
4845  {
4846  if (islivetv)
4847  m_browseHelper->BrowseStart(ctx);
4848  else if (!isDVD)
4849  ShowOSDMenu();
4850  else
4851  handled = false;
4852  }
4853  else if (has_action("EDIT", actions))
4854  {
4855  if (islivetv)
4856  StartChannelEditMode(ctx);
4857  else if (!isDVD)
4858  StartProgramEditMode(ctx);
4859  }
4860  else if (has_action(ACTION_OSDNAVIGATION, actions))
4861  StartOsdNavigation(ctx);
4862  else
4863  handled = false;
4864 
4865  return handled;
4866 }
4867 
4868 void TV::EnableVisualisation(const PlayerContext *ctx, bool enable,
4869  bool toggle, const QString &action)
4870 {
4871  QString visualiser = QString("");
4872  if (action.startsWith("VISUALISER"))
4873  visualiser = action.mid(11);
4874 
4875  ctx->LockDeletePlayer(__FILE__, __LINE__);
4876  if (ctx->m_player && ctx->m_player->CanVisualise())
4877  {
4878  bool want = enable || !visualiser.isEmpty();
4879  if (toggle && visualiser.isEmpty())
4880  want = !ctx->m_player->IsVisualising();
4881  bool on = ctx->m_player->EnableVisualisation(want, visualiser);
4882  SetOSDMessage(ctx, on ? ctx->m_player->GetVisualiserName() :
4883  tr("Visualisation Off"));
4884  }
4885  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4886 }
4887 
4888 bool TV::PxPHandleAction(PlayerContext *ctx, const QStringList &actions)
4889 {
4890  if (!IsPIPSupported(ctx) && !IsPBPSupported(ctx))
4891  return false;
4892 
4893  bool handled = true;
4894  {
4895  QMutexLocker locker(&m_timerIdLock);
4896 
4897  if (has_action("TOGGLEPIPMODE", actions))
4898  m_changePxP.enqueue("TOGGLEPIPMODE");
4899  else if (has_action("TOGGLEPBPMODE", actions))
4900  m_changePxP.enqueue("TOGGLEPBPMODE");
4901  else if (has_action("CREATEPIPVIEW", actions))
4902  m_changePxP.enqueue("CREATEPIPVIEW");
4903  else if (has_action("CREATEPBPVIEW", actions))
4904  m_changePxP.enqueue("CREATEPBPVIEW");
4905  else if (has_action("SWAPPIP", actions))
4906  m_changePxP.enqueue("SWAPPIP");
4907  else if (has_action("TOGGLEPIPSTATE", actions))
4908  m_changePxP.enqueue("TOGGLEPIPSTATE");
4909  else
4910  handled = false;
4911 
4912  if (!m_changePxP.empty() && !m_pipChangeTimerId)
4913  m_pipChangeTimerId = StartTimer(1, __LINE__);
4914  }
4915 
4916  if (has_action("NEXTPIPWINDOW", actions))
4917  {
4918  SetActive(ctx, -1, true);
4919  handled = true;
4920  }
4921 
4922  return handled;
4923 }
4924 
4926 {
4927  ctx->LockDeletePlayer(__FILE__, __LINE__);
4928  if (ctx->m_player)
4929  {
4930  if (clear)
4931  {
4932  ctx->m_player->SetBookmark(true);
4933  SetOSDMessage(ctx, tr("Bookmark Cleared"));
4934  }
4935  else // if (IsBookmarkAllowed(ctx))
4936  {
4937  ctx->m_player->SetBookmark();
4938  osdInfo info;
4939  ctx->CalcPlayerSliderPosition(info);
4940  info.text["title"] = tr("Position");
4941  UpdateOSDStatus(ctx, info, kOSDFunctionalType_Default,
4942  kOSDTimeout_Med);
4943  SetOSDMessage(ctx, tr("Bookmark Saved"));
4944  }
4945  }
4946  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4947 }
4948 
4949 bool TV::ActivePostQHandleAction(PlayerContext *ctx, const QStringList &actions)
4950 {
4951  bool handled = true;
4952  TVState state = GetState(ctx);
4953  bool islivetv = StateIsLiveTV(state);
4954  bool isdvd = state == kState_WatchingDVD;
4955  bool isdisc = isdvd || state == kState_WatchingBD;
4956 
4957  if (has_action(ACTION_SETBOOKMARK, actions))
4958  {
4959  if (!CommitQueuedInput(ctx))
4960  {
4961  ctx->LockDeletePlayer(__FILE__, __LINE__);
4962  SetBookmark(ctx, false);
4963  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4964  }
4965  }
4966  if (has_action(ACTION_TOGGLEBOOKMARK, actions))
4967  {
4968  if (!CommitQueuedInput(ctx))
4969  {
4970  ctx->LockDeletePlayer(__FILE__, __LINE__);
4971  SetBookmark(ctx, ctx->m_player->GetBookmark());
4972  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4973  }
4974  }
4975  else if (has_action("NEXTFAV", actions) && islivetv)
4976  ChangeChannel(ctx, CHANNEL_DIRECTION_FAVORITE);
4977  else if (has_action("NEXTSOURCE", actions) && islivetv)
4978  SwitchSource(ctx, kNextSource);
4979  else if (has_action("PREVSOURCE", actions) && islivetv)
4980  SwitchSource(ctx, kPreviousSource);
4981  else if (has_action("NEXTINPUT", actions) && islivetv)
4982  SwitchInputs(ctx);
4983  else if (has_action(ACTION_GUIDE, actions))
4984  EditSchedule(ctx, kScheduleProgramGuide);
4985  else if (has_action("PREVCHAN", actions) && islivetv)
4986  PopPreviousChannel(ctx, false);
4987  else if (has_action(ACTION_CHANNELUP, actions))
4988  {
4989  if (islivetv)
4990  {
4991  if (m_dbBrowseAlways)
4992  m_browseHelper->BrowseDispInfo(ctx, BROWSE_UP);
4993  else
4994  ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
4995  }
4996  else
4997  DoJumpRWND(ctx);
4998  }
4999  else if (has_action(ACTION_CHANNELDOWN, actions))
5000  {
5001  if (islivetv)
5002  {
5003  if (m_dbBrowseAlways)
5004  m_browseHelper->BrowseDispInfo(ctx, BROWSE_DOWN);
5005  else
5006  ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
5007  }
5008  else
5009  DoJumpFFWD(ctx);
5010  }
5011  else if (has_action("DELETE", actions) && !islivetv)
5012  {
5013  NormalSpeed(ctx);
5014  StopFFRew(ctx);
5015  SetBookmark(ctx);
5016  ShowOSDPromptDeleteRecording(ctx, tr("Are you sure you want to delete:"));
5017  }
5018  else if (has_action(ACTION_JUMPTODVDROOTMENU, actions) && isdisc)
5019  {
5020  ctx->LockDeletePlayer(__FILE__, __LINE__);
5021  if (ctx->m_player)
5022  ctx->m_player->GoToMenu("root");
5023  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5024  }
5025  else if (has_action(ACTION_JUMPTODVDCHAPTERMENU, actions) && isdisc)
5026  {
5027  ctx->LockDeletePlayer(__FILE__, __LINE__);
5028  if (ctx->m_player)
5029  ctx->m_player->GoToMenu("chapter");
5030  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5031  }
5032  else if (has_action(ACTION_JUMPTODVDTITLEMENU, actions) && isdisc)
5033  {
5034  ctx->LockDeletePlayer(__FILE__, __LINE__);
5035  if (ctx->m_player)
5036  ctx->m_player->GoToMenu("title");
5037  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5038  }
5039  else if (has_action(ACTION_JUMPTOPOPUPMENU, actions) && isdisc)
5040  {
5041  ctx->LockDeletePlayer(__FILE__, __LINE__);
5042  if (ctx->m_player)
5043  ctx->m_player->GoToMenu("popup");
5044  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5045  }
5046  else if (has_action(ACTION_FINDER, actions))
5047  EditSchedule(ctx, kScheduleProgramFinder);
5048  else
5049  handled = false;
5050 
5051  return handled;
5052 }
5053 
5054 
5056  const QString &command)
5057 {
5058  bool ignoreKeys = ctx->IsPlayerChangingBuffers();
5059 #ifdef DEBUG_ACTIONS
5060  LOG(VB_GENERAL, LOG_DEBUG, LOC +
5061  QString("(%1) ignoreKeys: %2").arg(command).arg(ignoreKeys));
5062 #endif
5063 
5064  if (ignoreKeys)
5065  {
5066  LOG(VB_GENERAL, LOG_WARNING, LOC +
5067  "Ignoring network control command"
5068  "\n\t\t\tbecause ignoreKeys is set");
5069  return;
5070  }
5071 
5072  QStringList tokens = command.split(" ", QString::SkipEmptyParts);
5073  if (tokens.size() < 2)
5074  {
5075  LOG(VB_GENERAL, LOG_ERR, LOC + "Not enough tokens"
5076  "in network control command" + "\n\t\t\t" +
5077  QString("'%1'").arg(command));
5078  return;
5079  }
5080 
5081  OSD *osd = GetOSDLock(ctx);
5082  bool dlg = false;
5083  if (osd)
5084  dlg = osd->DialogVisible();
5085  ReturnOSDLock(ctx, osd);
5086 
5087  if (dlg)
5088  {
5089  LOG(VB_GENERAL, LOG_WARNING, LOC +
5090  "Ignoring network control command\n\t\t\t" +
5091  QString("because dialog is waiting for a response"));
5092  return;
5093  }
5094 
5095  if (tokens[1] != "QUERY")
5096  ClearOSD(ctx);
5097 
5098  if (tokens.size() == 3 && tokens[1] == "CHANID")
5099  {
5100  m_queuedChanID = tokens[2].toUInt();
5101  m_queuedChanNum.clear();
5102  CommitQueuedInput(ctx);
5103  }
5104  else if (tokens.size() == 3 && tokens[1] == "CHANNEL")
5105  {
5106  if (StateIsLiveTV(GetState(ctx)))
5107  {
5108  if (tokens[2] == "UP")
5109  ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
5110  else if (tokens[2] == "DOWN")
5111  ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
5112  else if (tokens[2].contains(QRegExp("^[-\\.\\d_#]+$")))
5113  ChangeChannel(ctx, 0, tokens[2]);
5114  }
5115  }
5116  else if (tokens.size() == 3 && tokens[1] == "SPEED")
5117  {
5118  bool paused = ContextIsPaused(ctx, __FILE__, __LINE__);
5119 
5120  if (tokens[2] == "0x")
5121  {
5122  NormalSpeed(ctx);
5123  StopFFRew(ctx);
5124  if (!paused)
5125  DoTogglePause(ctx, true);
5126  }
5127  else if (tokens[2] == "normal")
5128  {
5129  NormalSpeed(ctx);
5130  StopFFRew(ctx);
5131  if (paused)
5132  DoTogglePause(ctx, true);
5133  return;
5134  }
5135  else
5136  {
5137  float tmpSpeed = 1.0F;
5138  bool ok = false;
5139 
5140  if (tokens[2].contains(QRegExp("^\\-*\\d+x$")))
5141  {
5142  QString speed = tokens[2].left(tokens[2].length()-1);
5143  tmpSpeed = speed.toFloat(&ok);
5144  }
5145  else if (tokens[2].contains(QRegExp("^\\-*\\d*\\.\\d+x$")))
5146  {
5147  QString speed = tokens[2].left(tokens[2].length() - 1);
5148  tmpSpeed = speed.toFloat(&ok);
5149  }
5150  else
5151  {
5152  QRegExp re = QRegExp("^(\\-*\\d+)\\/(\\d+)x$");
5153  if (tokens[2].contains(re))
5154  {
5155  QStringList matches = re.capturedTexts();
5156 
5157  int numerator, denominator;
5158  numerator = matches[1].toInt(&ok);
5159  denominator = matches[2].toInt(&ok);
5160 
5161  if (ok && denominator != 0)
5162  tmpSpeed = static_cast<float>(numerator) /
5163  static_cast<float>(denominator);
5164  else
5165  ok = false;
5166  }
5167  }
5168 
5169  if (ok)
5170  {
5171  float searchSpeed = fabs(tmpSpeed);
5172  size_t index;
5173 
5174  if (paused)
5175  DoTogglePause(ctx, true);
5176 
5177  if (tmpSpeed == 0.0F)
5178  {
5179  NormalSpeed(ctx);
5180  StopFFRew(ctx);
5181 
5182  if (!paused)
5183  DoTogglePause(ctx, true);
5184  }
5185  else if (tmpSpeed == 1.0F)
5186  {
5187  StopFFRew(ctx);
5188  ctx->m_tsNormal = 1.0F;
5189  ChangeTimeStretch(ctx, 0, false);
5190  return;
5191  }
5192 
5193  NormalSpeed(ctx);
5194 
5195  for (index = 0; index < m_ffRewSpeeds.size(); index++)
5196  if (float(m_ffRewSpeeds[index]) == searchSpeed)
5197  break;
5198 
5199  if ((index < m_ffRewSpeeds.size()) &&
5200  (float(m_ffRewSpeeds[index]) == searchSpeed))
5201  {
5202  if (tmpSpeed < 0)
5203  ctx->m_ffRewState = -1;
5204  else if (tmpSpeed > 1)
5205  ctx->m_ffRewState = 1;
5206  else
5207  StopFFRew(ctx);
5208 
5209  if (ctx->m_ffRewState)
5210  SetFFRew(ctx, index);
5211  }
5212  else if (0.48F <= tmpSpeed && tmpSpeed <= 2.0F) {
5213  StopFFRew(ctx);
5214 
5215  ctx->m_tsNormal = tmpSpeed; // alter speed before display
5216  ChangeTimeStretch(ctx, 0, false);
5217  }
5218  else
5219  {
5220  LOG(VB_GENERAL, LOG_WARNING,
5221  QString("Couldn't find %1 speed. Setting Speed to 1x")
5222  .arg(searchSpeed));
5223 
5224  ctx->m_ffRewState = 0;
5225  SetFFRew(ctx, kInitFFRWSpeed);
5226  }
5227  }
5228  else
5229  {
5230  LOG(VB_GENERAL, LOG_ERR,
5231  QString("Found an unknown speed of %1").arg(tokens[2]));
5232  }
5233  }
5234  }
5235  else if (tokens.size() == 2 && tokens[1] == "STOP")
5236  {
5237  SetBookmark(ctx);
5238  ctx->LockDeletePlayer(__FILE__, __LINE__);
5239  if (ctx->m_player && m_dbAutoSetWatched)
5240  ctx->m_player->SetWatched();
5241  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5242  SetExitPlayer(true, true);
5243  }
5244  else if (tokens.size() >= 3 && tokens[1] == "SEEK" && ctx->HasPlayer())
5245  {
5246  if (ctx->m_buffer && ctx->m_buffer->IsInDiscMenuOrStillFrame())
5247  return;
5248 
5249  if (tokens[2] == "BEGINNING")
5250  DoSeek(ctx, 0, tr("Jump to Beginning"),
5251  /*timeIsOffset*/false,
5252  /*honorCutlist*/true);
5253  else if (tokens[2] == "FORWARD")
5254  DoSeek(ctx, ctx->m_fftime, tr("Skip Ahead"),
5255  /*timeIsOffset*/true,
5256  /*honorCutlist*/true);
5257  else if (tokens[2] == "BACKWARD")
5258  DoSeek(ctx, -ctx->m_rewtime, tr("Skip Back"),
5259  /*timeIsOffset*/true,
5260  /*honorCutlist*/true);
5261  else if ((tokens[2] == "POSITION" ||
5262  tokens[2] == "POSITIONWITHCUTLIST") &&
5263  (tokens.size() == 4) &&
5264  (tokens[3].contains(QRegExp("^\\d+$"))))
5265  {
5266  DoSeekAbsolute(ctx, tokens[3].toInt(),
5267  tokens[2] == "POSITIONWITHCUTLIST");
5268  }
5269  }
5270  else if (tokens.size() >= 3 && tokens[1] == "SUBTITLES")
5271  {
5272  bool ok = false;
5273  uint track = tokens[2].toUInt(&ok);
5274 
5275  if (!ok)
5276  return;
5277 
5278  if (track == 0)
5279  {
5280  ctx->m_player->SetCaptionsEnabled(false, true);
5281  }
5282  else
5283  {
5284  uint start = 1;
5285  QStringList subs = ctx->m_player->GetTracks(kTrackTypeSubtitle);
5286  uint finish = start + subs.size();
5287  if (track >= start && track < finish)
5288  {
5289  ctx->m_player->SetTrack(kTrackTypeSubtitle, track - start);
5291  return;
5292  }
5293 
5294  start = finish + 1;
5295  subs = ctx->m_player->GetTracks(kTrackTypeCC708);
5296  finish = start + subs.size();
5297  if (track >= start && track < finish)
5298  {
5299  ctx->m_player->SetTrack(kTrackTypeCC708, track - start);
5301  return;
5302  }
5303 
5304  start = finish + 1;
5305  subs = ctx->m_player->GetTracks(kTrackTypeCC608);
5306  finish = start + subs.size();
5307  if (track >= start && track < finish)
5308  {
5309  ctx->m_player->SetTrack(kTrackTypeCC608, track - start);
5311  return;
5312  }
5313 
5314  start = finish + 1;
5316  finish = start + subs.size();
5317  if (track >= start && track < finish)
5318  {
5319  ctx->m_player->SetTrack(kTrackTypeTeletextCaptions, track-start);
5321  return;
5322  }
5323 
5324  start = finish + 1;
5326  finish = start + subs.size();
5327  if (track >= start && track < finish)
5328  {
5329  ctx->m_player->SetTrack(kTrackTypeTeletextMenu, track - start);
5331  return;
5332  }
5333 
5334  start = finish + 1;
5335  subs = ctx->m_player->GetTracks(kTrackTypeRawText);
5336  finish = start + subs.size();
5337  if (track >= start && track < finish)
5338  {
5339  ctx->m_player->SetTrack(kTrackTypeRawText, track - start);
5341  return;
5342  }
5343  }
5344  }
5345  else if (tokens.size() >= 3 && tokens[1] == "VOLUME")
5346  {
5347  QRegExp re = QRegExp("(\\d+)%");
5348  if (tokens[2].contains(re))
5349  {
5350  QStringList matches = re.capturedTexts();
5351 
5352  LOG(VB_GENERAL, LOG_INFO, QString("Set Volume to %1%")
5353  .arg(matches[1]));
5354 
5355  bool ok = false;
5356 
5357  int vol = matches[1].toInt(&ok);
5358 
5359  if (!ok)
5360  return;
5361 
5362  if (0 <= vol && vol <= 100)
5363  {
5364  ctx->LockDeletePlayer(__FILE__, __LINE__);
5365  if (!ctx->m_player)
5366  {
5367  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5368  return;
5369  }
5370 
5371  vol -= ctx->m_player->GetVolume();
5372  vol = ctx->m_player->AdjustVolume(vol);
5373  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5374 
5375  if (!m_browseHelper->IsBrowsing() && !m_editMode)
5376  {
5377  UpdateOSDStatus(
5378  ctx, tr("Adjust Volume"), tr("Volume"),
5379  QString::number(vol),
5380  kOSDFunctionalType_PictureAdjust, "%", vol * 10,
5381  kOSDTimeout_Med);
5382  SetUpdateOSDPosition(false);
5383  }
5384  }
5385  }
5386  }
5387  else if (tokens.size() >= 3 && tokens[1] == "QUERY")
5388  {
5389  if (tokens[2] == "POSITION")
5390  {
5391  if (!ctx->m_player)
5392  return;
5393  QString speedStr;
5394  if (ContextIsPaused(ctx, __FILE__, __LINE__))
5395  {
5396  speedStr = "pause";
5397  }
5398  else if (ctx->m_ffRewState)
5399  {
5400  speedStr = QString("%1x").arg(ctx->m_ffRewSpeed);
5401  }
5402  else
5403  {
5404  QRegExp re = QRegExp("Play (.*)x");
5405  if (ctx->GetPlayMessage().contains(re))
5406  {
5407  QStringList matches = re.capturedTexts();
5408  speedStr = QString("%1x").arg(matches[1]);
5409  }
5410  else
5411  {
5412  speedStr = "1x";
5413  }
5414  }
5415 
5416  osdInfo info;
5417  ctx->CalcPlayerSliderPosition(info, true);
5418 
5419  QDateTime respDate = MythDate::current(true);
5420  QString infoStr = "";
5421 
5422  ctx->LockDeletePlayer(__FILE__, __LINE__);
5423  long long fplay = 0;
5424  float rate = 30.0F;
5425  if (ctx->m_player)
5426  {
5427  fplay = ctx->m_player->GetFramesPlayed();
5428  rate = ctx->m_player->GetFrameRate(); // for display only
5429  }
5430  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5431 
5432  ctx->LockPlayingInfo(__FILE__, __LINE__);
5433  if (ctx->GetState() == kState_WatchingLiveTV)
5434  {
5435  infoStr = "LiveTV";
5436  if (ctx->m_playingInfo)
5437  respDate = ctx->m_playingInfo->GetScheduledStartTime();
5438  }
5439  else
5440  {
5441  if (ctx->m_buffer && ctx->m_buffer->IsDVD())
5442  infoStr = "DVD";
5443  else if (ctx->m_playingInfo->IsRecording())
5444  infoStr = "Recorded";
5445  else
5446  infoStr = "Video";
5447 
5448  if (ctx->m_playingInfo)
5449  respDate = ctx->m_playingInfo->GetRecordingStartTime();
5450  }
5451 
5452  QString bufferFilename =
5453  ctx->m_buffer ? ctx->m_buffer->GetFilename() : QString("no buffer");
5454  if ((infoStr == "Recorded") || (infoStr == "LiveTV"))
5455  {
5456  infoStr += QString(" %1 %2 %3 %4 %5 %6 %7")
5457  .arg(info.text["description"])
5458  .arg(speedStr)
5459  .arg(ctx->m_playingInfo != nullptr ?
5460  ctx->m_playingInfo->GetChanID() : 0)
5461  .arg(respDate.toString(Qt::ISODate))
5462  .arg(fplay)
5463  .arg(bufferFilename)
5464  .arg(rate);
5465  }
5466  else
5467  {
5468  QString position = info.text["description"].section(" ",0,0);
5469  infoStr += QString(" %1 %2 %3 %4 %5")
5470  .arg(position)
5471  .arg(speedStr)
5472  .arg(bufferFilename)
5473  .arg(fplay)
5474  .arg(rate);
5475  }
5476 
5477  infoStr += QString(" Subtitles:");
5478 
5479  uint subtype = ctx->m_player->GetCaptionMode();
5480 
5481  if (subtype == kDisplayNone)
5482  infoStr += QString(" *0:[None]*");
5483  else
5484  infoStr += QString(" 0:[None]");
5485 
5486  uint n = 1;
5487 
5488  QStringList subs = ctx->m_player->GetTracks(kTrackTypeSubtitle);
5489  for (uint i = 0; i < (uint)subs.size(); i++)
5490  {
5491  if ((subtype & kDisplayAVSubtitle) &&
5492  (ctx->m_player->GetTrack(kTrackTypeSubtitle) == (int)i))
5493  {
5494  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5495  }
5496  else
5497  {
5498  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5499  }
5500  n++;
5501  }
5502 
5503  subs = ctx->m_player->GetTracks(kTrackTypeCC708);
5504  for (uint i = 0; i < (uint)subs.size(); i++)
5505  {
5506  if ((subtype & kDisplayCC708) &&
5507  (ctx->m_player->GetTrack(kTrackTypeCC708) == (int)i))
5508  {
5509  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5510  }
5511  else
5512  {
5513  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5514  }
5515  n++;
5516  }
5517 
5518  subs = ctx->m_player->GetTracks(kTrackTypeCC608);
5519  for (uint i = 0; i < (uint)subs.size(); i++)
5520  {
5521  if ((subtype & kDisplayCC608) &&
5522  (ctx->m_player->GetTrack(kTrackTypeCC608) == (int)i))
5523  {
5524  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5525  }
5526  else
5527  {
5528  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5529  }
5530  n++;
5531  }
5532 
5534  for (uint i = 0; i < (uint)subs.size(); i++)
5535  {
5536  if ((subtype & kDisplayTeletextCaptions) &&
5537  (ctx->m_player->GetTrack(kTrackTypeTeletextCaptions)==(int)i))
5538  {
5539  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5540  }
5541  else
5542  {
5543  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5544  }
5545  n++;
5546  }
5547 
5549  for (uint i = 0; i < (uint)subs.size(); i++)
5550  {
5551  if ((subtype & kDisplayTeletextMenu) &&
5552  ctx->m_player->GetTrack(kTrackTypeTeletextMenu) == (int)i)
5553  {
5554  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5555  }
5556  else
5557  {
5558  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5559  }
5560  n++;
5561  }
5562 
5563  subs = ctx->m_player->GetTracks(kTrackTypeRawText);
5564  for (uint i = 0; i < (uint)subs.size(); i++)
5565  {
5566  if ((subtype & kDisplayRawTextSubtitle) &&
5567  ctx->m_player->GetTrack(kTrackTypeRawText) == (int)i)
5568  {
5569  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5570  }
5571  else
5572  {
5573  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5574  }
5575  n++;
5576  }
5577 
5578  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
5579 
5580  QString message = QString("NETWORK_CONTROL ANSWER %1")
5581  .arg(infoStr);
5582  MythEvent me(message);
5583  gCoreContext->dispatch(me);
5584  }
5585  else if (tokens[2] == "VOLUME")
5586  {
5587  if (!ctx->m_player)
5588  return;
5589  QString infoStr = QString("%1%").arg(ctx->m_player->GetVolume());
5590 
5591  QString message = QString("NETWORK_CONTROL ANSWER %1")
5592  .arg(infoStr);
5593  MythEvent me(message);
5594  gCoreContext->dispatch(me);
5595  }
5596  }
5597 }
5598 
5604 bool TV::CreatePBP(PlayerContext *ctx, const ProgramInfo *info)
5605 {
5606  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
5607 
5608  if (m_player.size() > 1)
5609  {
5610  LOG(VB_GENERAL, LOG_ERR, LOC + "Only allowed when player.size() == 1");
5611  return false;
5612  }
5613 
5614  PlayerContext *mctx = GetPlayer(ctx, 0);
5615  if (!IsPBPSupported(mctx))
5616  {
5617  LOG(VB_GENERAL, LOG_ERR, LOC + "PBP not supported by video method.");
5618  return false;
5619  }
5620 
5621  if (!mctx->m_player)
5622  return false;
5623  mctx->LockDeletePlayer(__FILE__, __LINE__);
5624  long long mctx_frame = mctx->m_player->GetFramesPlayed();
5625  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
5626 
5627  // This is safe because we are already holding lock for a ctx
5628  m_player.push_back(new PlayerContext(kPBPPlayerInUseID));
5629  PlayerContext *pbpctx = m_player.back();
5630  if (m_noHardwareDecoders)
5631  pbpctx->SetNoHardwareDecoders();
5632  pbpctx->SetPIPState(kPBPRight);
5633 
5634  if (info)
5635  {
5636  pbpctx->SetPlayingInfo(info);
5637  pbpctx->SetInitialTVState(false);
5638  ScheduleStateChange(pbpctx);
5639  }
5640  else if (RequestNextRecorder(pbpctx, false))
5641  {
5642  pbpctx->SetInitialTVState(true);
5643  ScheduleStateChange(pbpctx);
5644  }
5645  else
5646  {
5647  delete m_player.back();
5648  m_player.pop_back();
5649  return false;
5650  }
5651 
5652  mctx->PIPTeardown();
5653  mctx->SetPIPState(kPBPLeft);
5654  if (mctx->m_buffer)
5655  mctx->m_buffer->Seek(0, SEEK_SET);
5656 
5657  if (StateIsLiveTV(mctx->GetState()))
5658  if (mctx->m_buffer)
5659  mctx->m_buffer->Unpause();
5660 
5661  bool ok = mctx->CreatePlayer(
5662  this, GetMythMainWindow(), mctx->GetState(), false);
5663 
5664  if (ok)
5665  {
5666  ScheduleStateChange(mctx);
5667  mctx->LockDeletePlayer(__FILE__, __LINE__);
5668  if (mctx->m_player)
5669  mctx->m_player->JumpToFrame(mctx_frame);
5670  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
5671  SetSpeedChangeTimer(25, __LINE__);
5672  }
5673  else
5674  {
5675  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to restart new main context");
5676  // Make putative PBP context the main context
5677  swap(m_player[0],m_player[1]);
5678  m_player[0]->SetPIPState(kPIPOff);
5679  // End the old main context..
5680  ForceNextStateNone(mctx);
5681  }
5682 
5683  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
5684  QString("-- end : %1").arg(ok));
5685  return ok;
5686 }
5687 
5693 bool TV::CreatePIP(PlayerContext *ctx, const ProgramInfo *info)
5694 {
5695  PlayerContext *mctx = GetPlayer(ctx, 0);
5696  if (!mctx)
5697  return false;
5698 
5699  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
5700 
5701  if (mctx->IsPBP())
5702  {
5703  LOG(VB_GENERAL, LOG_ERR, LOC +
5704  "CreatePIP called, but we're in PBP mode already, ignoring.");
5705  return false;
5706  }
5707 
5708  if (!IsPIPSupported(mctx))
5709  {
5710  LOG(VB_GENERAL, LOG_ERR, LOC + "PiP not supported by video method.");
5711  return false;
5712  }
5713 
5715  if (m_noHardwareDecoders)
5716  pipctx->SetNoHardwareDecoders();
5717  pipctx->SetNullVideo(true);
5718  pipctx->SetPIPState(kPIPonTV);
5719  if (info)
5720  {
5721  pipctx->SetPlayingInfo(info);
5722  pipctx->SetInitialTVState(false);
5723  ScheduleStateChange(pipctx);
5724  }
5725  else if (RequestNextRecorder(pipctx, false))
5726  {
5727  pipctx->SetInitialTVState(true);
5728  ScheduleStateChange(pipctx);
5729  }
5730  else
5731  {
5732  delete pipctx;
5733  return false;
5734  }
5735 
5736  // this is safe because we are already holding lock for ctx
5737  m_player.push_back(pipctx);
5738 
5739  return true;
5740 }
5741 
5743 {
5744  for (size_t i = 0; i < m_player.size(); i++)
5745  if (GetPlayer(ctx, i) == ctx)
5746  return i;
5747  return -1;
5748 }
5749 
5751  TVState desiredState)
5752 {
5753  bool wantPiP = ctx->IsPIP();
5754 
5755  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1, %2, %3) -- begin")
5756  .arg(find_player_index(ctx)).arg(StateToString(desiredState))
5757  .arg((wantPiP) ? "PiP" : "main"));
5758 
5759  LOG(VB_PLAYBACK, LOG_INFO, LOC +
5760  QString("Elapsed time since TV constructor was called: %1 ms")
5761  .arg(m_ctorTime.elapsed()));
5762 
5763  if (wantPiP)
5764  {
5765  if (mctx->HasPlayer() && ctx->StartPIPPlayer(this, desiredState) &&
5766  ctx->HasPlayer() && PIPAddPlayer(mctx, ctx))
5767  {
5768  ScheduleStateChange(ctx);
5769  LOG(VB_GENERAL, LOG_DEBUG, "PiP -- end : ok");
5770  return true;
5771  }
5772 
5773  ForceNextStateNone(ctx);
5774  LOG(VB_GENERAL, LOG_ERR, "PiP -- end : !ok");
5775  return false;
5776  }
5777 
5778  bool ok = false;
5779  if (ctx->IsNullVideoDesired())
5780  {
5781  ok = ctx->CreatePlayer(this, nullptr, desiredState, false);
5782  ScheduleStateChange(ctx);
5783  if (ok)
5784  ok = PIPAddPlayer(mctx, ctx);
5785  }
5786  else
5787  {
5788  ok = ctx->CreatePlayer(this, GetMythMainWindow(), desiredState, false);
5789  ScheduleStateChange(ctx);
5790  }
5791 
5792  if (ok)
5793  {
5794  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created player."));
5795  SetSpeedChangeTimer(25, __LINE__);
5796  }
5797  else
5798  LOG(VB_GENERAL, LOG_CRIT, LOC + QString("Failed to create player."));
5799 
5800  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
5801  QString("(%1, %2, %3) -- end %4")
5802  .arg(find_player_index(ctx)).arg(StateToString(desiredState))
5803  .arg((wantPiP) ? "PiP" : "main").arg((ok) ? "ok" : "error"));
5804 
5805  return ok;
5806 }
5807 
5810 {
5811  if (!mctx || !pipctx)
5812  return false;
5813 
5814  if (!mctx->IsPlayerPlaying())
5815  return false;
5816 
5817  bool ok = false, addCondition = false;
5818  bool is_using_null = false;
5819  pipctx->LockDeletePlayer(__FILE__, __LINE__);
5820  if (pipctx->m_player)
5821  {
5822  is_using_null = pipctx->m_player->UsingNullVideo();
5823  pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
5824 
5825  if (is_using_null)
5826  {
5827  addCondition = true;
5828  multi_lock(&mctx->m_deletePlayerLock, &pipctx->m_deletePlayerLock, nullptr);
5829  if (mctx->m_player && pipctx->m_player)
5830  {
5831  PIPLocation loc = mctx->m_player->GetNextPIPLocation();
5832  if (loc != kPIP_END)
5833  ok = mctx->m_player->AddPIPPlayer(pipctx->m_player, loc);
5834  }
5835  mctx->m_deletePlayerLock.unlock();
5836  pipctx->m_deletePlayerLock.unlock();
5837  }
5838  else if (pipctx->IsPIP())
5839  {
5840  ok = ResizePIPWindow(pipctx);
5841  }
5842  }
5843  else
5844  pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
5845 
5846  LOG(VB_GENERAL, LOG_ERR,
5847  QString("AddPIPPlayer null: %1 IsPIP: %2 addCond: %3 ok: %4")
5848  .arg(is_using_null)
5849  .arg(pipctx->IsPIP()).arg(addCondition).arg(ok));
5850 
5851  return ok;
5852 }
5853 
5856 {
5857  if (!mctx || !pipctx)
5858  return false;
5859 
5860  bool ok = false;
5861  multi_lock(&mctx->m_deletePlayerLock, &pipctx->m_deletePlayerLock, nullptr);
5862  if (mctx->m_player && pipctx->m_player)
5863  ok = mctx->m_player->RemovePIPPlayer(pipctx->m_player);
5864  mctx->m_deletePlayerLock.unlock();
5865  pipctx->m_deletePlayerLock.unlock();
5866 
5867  LOG(VB_GENERAL, LOG_INFO, QString("PIPRemovePlayer ok: %1").arg(ok));
5868 
5869  return ok;
<