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