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