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