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  uint db_browse_max_forward;
1089 
1090  // convert from minutes to ms.
1091  m_dbIdleTimeout = kv["LiveTVIdleTimeout"].toInt() * 60 * 1000;
1092  db_browse_max_forward = kv["BrowseMaxForward"].toInt() * 60;
1093  m_dbPlaybackExitPrompt = kv["PlaybackExitPrompt"].toInt();
1094  m_dbAutoSetWatched = (kv["AutomaticSetWatched"].toInt() != 0);
1095  m_dbEndOfRecExitPrompt = (kv["EndOfRecordingExitPrompt"].toInt() != 0);
1096  m_dbJumpPreferOsd = (kv["JumpToProgramOSD"].toInt() != 0);
1097  m_dbUseGuiSizeForTv = (kv["GuiSizeForTV"].toInt() != 0);
1098  m_dbUseVideoModes = (kv["UseVideoModes"].toInt() != 0);
1099  m_dbClearSavedPosition = (kv["ClearSavedPosition"].toInt() != 0);
1100  m_dbRunJobsOnRemote = (kv["JobsRunOnRecordHost"].toInt() != 0);
1101  m_dbContinueEmbedded = (kv["ContinueEmbeddedTVPlay"].toInt() != 0);
1102  m_dbRunFrontendInWindow= (kv["RunFrontendInWindow"].toInt() != 0);
1103  m_dbBrowseAlways = (kv["PersistentBrowseMode"].toInt() != 0);
1104  m_dbBrowseAllTuners = (kv["BrowseAllTuners"].toInt() != 0);
1105  db_channel_ordering = kv["ChannelOrdering"];
1106  m_baseFilters += kv["CustomFilters"];
1107  m_dbChannelFormat = kv["ChannelFormat"];
1108  m_tryUnflaggedSkip = (kv["TryUnflaggedSkip"].toInt() != 0);
1109  m_smartForward = (kv["SmartForward"].toInt() != 0);
1110  m_ffRewRepos = kv["FFRewReposTime"].toFloat() * 0.01F;
1111  m_ffRewReverse = (kv["FFRewReverse"].toInt() != 0);
1112 
1113  m_dbUseChannelGroups = (kv["BrowseChannelGroup"].toInt() != 0);
1114  m_dbRememberLastChannelGroup = (kv["ChannelGroupRememberLast"].toInt() != 0);
1115  m_channelGroupId = kv["ChannelGroupDefault"].toInt();
1116 
1117  QString beVBI = kv["VbiFormat"];
1118  QString feVBI = kv["DecodeVBIFormat"];
1119 
1120  RecordingRule record;
1121  record.LoadTemplate("Default");
1122  m_dbAutoexpireDefault = record.m_autoExpire;
1123 
1124  if (m_dbUseChannelGroups)
1125  {
1126  m_dbChannelGroups = ChannelGroup::GetChannelGroups();
1127  if (m_channelGroupId > -1)
1128  {
1129  m_channelGroupChannelList = ChannelUtil::GetChannels(
1130  0, true, "channum, callsign", m_channelGroupId);
1132  m_channelGroupChannelList, "channum", true);
1133  }
1134  }
1135 
1136  for (size_t i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
1137  m_ffRewSpeeds.push_back(kv[QString("FFRewSpeed%1").arg(i)].toInt());
1138 
1139  // process it..
1140  m_browseHelper = new TVBrowseHelper(
1141  this,
1142  db_browse_max_forward, m_dbBrowseAllTuners,
1143  m_dbUseChannelGroups, db_channel_ordering);
1144 
1145  m_vbimode = VBIMode::Parse(!feVBI.isEmpty() ? feVBI : beVBI);
1146 
1147  gCoreContext->addListener(this);
1148  gCoreContext->RegisterForPlayback(this, SLOT(StopPlayback()));
1149 
1150  QMutexLocker lock(&m_initFromDBLock);
1151  m_initFromDBDone = true;
1152  m_initFromDBWait.wakeAll();
1153 }
1154 
1161 bool TV::Init(bool createWindow)
1162 {
1163  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
1164 
1165  if (createWindow)
1166  {
1167  MythMainWindow *mainwindow = GetMythMainWindow();
1168  if (!mainwindow)
1169  {
1170  LOG(VB_GENERAL, LOG_ERR, LOC + "No MythMainWindow");
1171  return false;
1172  }
1173 
1174  bool fullscreen = !m_dbUseGuiSizeForTv;
1175  m_savedGuiBounds = QRect(GetMythMainWindow()->geometry().topLeft(),
1176  GetMythMainWindow()->size());
1177 
1178  // adjust for window manager wierdness.
1179  int xbase, width, ybase, height;
1180  float wmult, hmult;
1181  GetMythUI()->GetScreenSettings(xbase, width, wmult,
1182  ybase, height, hmult);
1183  if ((abs(m_savedGuiBounds.x()-xbase) < 3) &&
1184  (abs(m_savedGuiBounds.y()-ybase) < 3))
1185  {
1186  m_savedGuiBounds = QRect(QPoint(xbase, ybase),
1187  mainwindow->size());
1188  }
1189 
1190  // if width && height are zero users expect fullscreen playback
1191  if (!fullscreen)
1192  {
1193  int gui_width = 0, gui_height = 0;
1194  gCoreContext->GetResolutionSetting("Gui", gui_width, gui_height);
1195  fullscreen |= (0 == gui_width && 0 == gui_height);
1196  }
1197 
1198  m_playerBounds = m_savedGuiBounds;
1199  if (fullscreen)
1200  {
1201  GetMythUI()->GetScreenBounds(xbase, ybase, width, height);
1202  m_playerBounds = QRect(xbase, ybase, width, height);
1203  }
1204 
1205  // player window sizing
1206  MythScreenStack *mainStack = mainwindow->GetMainStack();
1207 
1208  m_myWindow = new TvPlayWindow(mainStack, "Playback");
1209 
1210  if (m_myWindow->Create())
1211  {
1212  mainStack->AddScreen(m_myWindow, false);
1213  LOG(VB_GENERAL, LOG_INFO, LOC + "Created TvPlayWindow.");
1214  }
1215  else
1216  {
1217  delete m_myWindow;
1218  m_myWindow = nullptr;
1219  }
1220 
1221  if (mainwindow->GetPaintWindow())
1222  mainwindow->GetPaintWindow()->update();
1223  mainwindow->installEventFilter(this);
1224  qApp->processEvents();
1225  }
1226 
1227  {
1228  QMutexLocker locker(&m_initFromDBLock);
1229  while (!m_initFromDBDone)
1230  {
1231  qApp->processEvents();
1232  m_initFromDBWait.wait(&m_initFromDBLock, 50);
1233  }
1234  }
1235 
1236  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
1237  mctx->m_ffRewState = 0;
1238  mctx->m_ffRewIndex = kInitFFRWSpeed;
1239  mctx->m_ffRewSpeed = 0;
1240  mctx->m_tsNormal = 1.0F;
1241  ReturnPlayerLock(mctx);
1242 
1243  m_sleepIndex = 0;
1244 
1245  SetUpdateOSDPosition(false);
1246 
1247  const PlayerContext *ctx = GetPlayerReadLock(0, __FILE__, __LINE__);
1248  ClearInputQueues(ctx, false);
1249  ReturnPlayerLock(ctx);
1250 
1251  m_switchToRec = nullptr;
1252  SetExitPlayer(false, false);
1253 
1254  m_errorRecoveryTimerId = StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
1255  m_lcdTimerId = StartTimer(1, __LINE__);
1256  m_speedChangeTimerId = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
1257  m_saveLastPlayPosTimerId = StartTimer(kSaveLastPlayPosTimeout, __LINE__);
1258 
1259  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
1260  return true;
1261 }
1262 
1263 TV::~TV(void)
1264 {
1265  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
1266 
1267  if (m_browseHelper)
1268  m_browseHelper->Stop();
1269 
1272 
1274  mwnd->removeEventFilter(this);
1275 
1276  if (m_weDisabledGUI)
1277  mwnd->PopDrawDisabled();
1278 
1279  if (m_myWindow)
1280  {
1281  m_myWindow->Close();
1282  m_myWindow = nullptr;
1283  }
1284 
1285  LOG(VB_PLAYBACK, LOG_INFO, LOC + "-- lock");
1286 
1287  // restore window to gui size and position
1289  if (display->UsingVideoModes())
1290  {
1291  bool hide = display->NextModeIsLarger(MythDisplay::GUI);
1292  if (hide)
1293  mwnd->hide();
1294  display->SwitchToGUI(MythDisplay::GUI, true);
1295  if (hide)
1296  mwnd->Show();
1297  }
1299 
1300  mwnd->MoveResize(m_savedGuiBounds);
1301 #ifdef Q_OS_ANDROID
1302  mwnd->Show();
1303 #else
1304  mwnd->show();
1305 #endif
1306 
1307  delete m_lastProgram;
1308 
1309  if (LCD *lcd = LCD::Get())
1310  {
1311  lcd->setFunctionLEDs(FUNC_TV, false);
1312  lcd->setFunctionLEDs(FUNC_MOVIE, false);
1313  lcd->switchToTime();
1314  }
1315 
1316  if (m_browseHelper)
1317  {
1318  delete m_browseHelper;
1319  m_browseHelper = nullptr;
1320  }
1321 
1322  PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
1323  while (!m_player.empty())
1324  {
1325  delete m_player.back();
1326  m_player.pop_back();
1327  }
1328  ReturnPlayerLock(mctx);
1329 
1330  if (m_browseHelper)
1331  {
1332  delete m_browseHelper;
1333  m_browseHelper = nullptr;
1334  }
1335 
1336  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
1337 }
1338 
1343 {
1344  while (true)
1345  {
1346  qApp->processEvents();
1348  {
1349  m_wantsToQuit = true;
1350  return;
1351  }
1352 
1353  TVState state = GetState(0);
1354  if ((kState_Error == state) || (kState_None == state))
1355  return;
1356 
1357  if (kState_ChangingState == state)
1358  continue;
1359 
1360  int count = m_player.size();
1361  int errorCount = 0;
1362  for (int i = 0; i < count; i++)
1363  {
1364  const PlayerContext *mctx = GetPlayerReadLock(i, __FILE__, __LINE__);
1365  if (mctx)
1366  {
1367  mctx->LockDeletePlayer(__FILE__, __LINE__);
1368  if (mctx->m_player && !mctx->m_player->IsErrored())
1369  {
1370  mctx->m_player->EventLoop();
1371  mctx->m_player->VideoLoop();
1372  }
1373  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
1374 
1375  if (mctx->m_errored || !mctx->m_player)
1376  errorCount++;
1377  }
1378  ReturnPlayerLock(mctx);
1379  }
1380 
1381  // break out of the loop if there are no valid players
1382  // or all PlayerContexts are errored
1383  if (errorCount == count)
1384  return;
1385  }
1386 }
1387 
1391 void TV::UpdateChannelList(int groupID)
1392 {
1393  if (!m_dbUseChannelGroups)
1394  return;
1395 
1396  QMutexLocker locker(&m_channelGroupLock);
1397  if (groupID == m_channelGroupId)
1398  return;
1399 
1400  ChannelInfoList list;
1401  if (groupID != -1)
1402  {
1403  list = ChannelUtil::GetChannels(
1404  0, true, "channum, callsign", groupID);
1405  ChannelUtil::SortChannels(list, "channum", true);
1406  }
1407 
1408  m_channelGroupId = groupID;
1409  m_channelGroupChannelList = list;
1410 
1411  if (m_dbRememberLastChannelGroup)
1412  gCoreContext->SaveSetting("ChannelGroupDefault", m_channelGroupId);
1413 }
1414 
1418 TVState TV::GetState(int player_idx) const
1419 {
1420  const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
1421  TVState ret = GetState(ctx);
1422  ReturnPlayerLock(ctx);
1423  return ret;
1424 }
1425 
1426 // XXX what about subtitlezoom?
1427 void TV::GetStatus(void)
1428 {
1429  QVariantMap status;
1430 
1431  const PlayerContext *ctx = GetPlayerReadLock(-1, __FILE__, __LINE__);
1432 
1433  status.insert("state", StateToString(GetState(ctx)));
1434  ctx->LockPlayingInfo(__FILE__, __LINE__);
1435  if (ctx->m_playingInfo)
1436  {
1437  status.insert("title", ctx->m_playingInfo->GetTitle());
1438  status.insert("subtitle", ctx->m_playingInfo->GetSubtitle());
1439  status.insert("starttime",
1441  .toUTC().toString("yyyy-MM-ddThh:mm:ssZ"));
1442  status.insert("chanid",
1443  QString::number(ctx->m_playingInfo->GetChanID()));
1444  status.insert("programid", ctx->m_playingInfo->GetProgramID());
1445  status.insert("pathname", ctx->m_playingInfo->GetPathname());
1446  }
1447  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
1448  osdInfo info;
1449  ctx->CalcPlayerSliderPosition(info);
1450  ctx->LockDeletePlayer(__FILE__, __LINE__);
1451  if (ctx->m_player)
1452  {
1453  if (!info.text["totalchapters"].isEmpty())
1454  {
1455  QList<long long> chapters;
1456  ctx->m_player->GetChapterTimes(chapters);
1457  QVariantList var;
1458  foreach (long long chapter, chapters)
1459  var << QVariant(chapter);
1460  status.insert("chaptertimes", var);
1461  }
1462 
1463  uint capmode = ctx->m_player->GetCaptionMode();
1464  QVariantMap tracks;
1465 
1466  QStringList list = ctx->m_player->GetTracks(kTrackTypeSubtitle);
1467  int currenttrack = -1;
1468  if (!list.isEmpty() && (kDisplayAVSubtitle == capmode))
1469  currenttrack = ctx->m_player->GetTrack(kTrackTypeSubtitle);
1470  for (int i = 0; i < list.size(); i++)
1471  {
1472  if (i == currenttrack)
1473  status.insert("currentsubtitletrack", list[i]);
1474  tracks.insert("SELECTSUBTITLE_" + QString::number(i), list[i]);
1475  }
1476 
1478  currenttrack = -1;
1479  if (!list.isEmpty() && (kDisplayTeletextCaptions == capmode))
1480  currenttrack = ctx->m_player->GetTrack(kTrackTypeTeletextCaptions);
1481  for (int i = 0; i < list.size(); i++)
1482  {
1483  if (i == currenttrack)
1484  status.insert("currentsubtitletrack", list[i]);
1485  tracks.insert("SELECTTTC_" + QString::number(i), list[i]);
1486  }
1487 
1488  list = ctx->m_player->GetTracks(kTrackTypeCC708);
1489  currenttrack = -1;
1490  if (!list.isEmpty() && (kDisplayCC708 == capmode))
1491  currenttrack = ctx->m_player->GetTrack(kTrackTypeCC708);
1492  for (int i = 0; i < list.size(); i++)
1493  {
1494  if (i == currenttrack)
1495  status.insert("currentsubtitletrack", list[i]);
1496  tracks.insert("SELECTCC708_" + QString::number(i), list[i]);
1497  }
1498 
1499  list = ctx->m_player->GetTracks(kTrackTypeCC608);
1500  currenttrack = -1;
1501  if (!list.isEmpty() && (kDisplayCC608 == capmode))
1502  currenttrack = ctx->m_player->GetTrack(kTrackTypeCC608);
1503  for (int i = 0; i < list.size(); i++)
1504  {
1505  if (i == currenttrack)
1506  status.insert("currentsubtitletrack", list[i]);
1507  tracks.insert("SELECTCC608_" + QString::number(i), list[i]);
1508  }
1509 
1510  list = ctx->m_player->GetTracks(kTrackTypeRawText);
1511  currenttrack = -1;
1512  if (!list.isEmpty() && (kDisplayRawTextSubtitle == capmode))
1513  currenttrack = ctx->m_player->GetTrack(kTrackTypeRawText);
1514  for (int i = 0; i < list.size(); i++)
1515  {
1516  if (i == currenttrack)
1517  status.insert("currentsubtitletrack", list[i]);
1518  tracks.insert("SELECTRAWTEXT_" + QString::number(i), list[i]);
1519  }
1520 
1521  if (ctx->m_player->HasTextSubtitles())
1522  {
1523  if (kDisplayTextSubtitle == capmode)
1524  status.insert("currentsubtitletrack", tr("External Subtitles"));
1525  tracks.insert(ACTION_ENABLEEXTTEXT, tr("External Subtitles"));
1526  }
1527 
1528  status.insert("totalsubtitletracks", tracks.size());
1529  if (!tracks.isEmpty())
1530  status.insert("subtitletracks", tracks);
1531 
1532  tracks.clear();
1533  list = ctx->m_player->GetTracks(kTrackTypeAudio);
1534  currenttrack = ctx->m_player->GetTrack(kTrackTypeAudio);
1535  for (int i = 0; i < list.size(); i++)
1536  {
1537  if (i == currenttrack)
1538  status.insert("currentaudiotrack", list[i]);
1539  tracks.insert("SELECTAUDIO_" + QString::number(i), list[i]);
1540  }
1541 
1542  status.insert("totalaudiotracks", tracks.size());
1543  if (!tracks.isEmpty())
1544  status.insert("audiotracks", tracks);
1545 
1546  status.insert("playspeed", ctx->m_player->GetPlaySpeed());
1547  status.insert("audiosyncoffset", (long long)ctx->m_player->GetAudioTimecodeOffset());
1548  if (ctx->m_player->GetAudio()->ControlsVolume())
1549  {
1550  status.insert("volume", ctx->m_player->GetVolume());
1551  status.insert("mute", ctx->m_player->GetMuteState());
1552  }
1553  if (ctx->m_player->GetVideoOutput())
1554  {
1555  MythVideoOutput *vo = ctx->m_player->GetVideoOutput();
1559  {
1560  status.insert("brightness",
1562  }
1564  {
1565  status.insert("contrast",
1567  }
1569  {
1570  status.insert("colour",
1572  }
1573  if (supp & kPictureAttributeSupported_Hue)
1574  {
1575  status.insert("hue",
1577  }
1578  }
1579  }
1580  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
1581 
1582  ReturnPlayerLock(ctx);
1583 
1584  InfoMap::const_iterator tit =info.text.begin();
1585  for (; tit != info.text.end(); ++tit)
1586  {
1587  status.insert(tit.key(), tit.value());
1588  }
1589 
1590  QHashIterator<QString,int> vit(info.values);
1591  while (vit.hasNext())
1592  {
1593  vit.next();
1594  status.insert(vit.key(), vit.value());
1595  }
1596 
1598 }
1599 
1604 {
1606  if (!actx->InStateChange())
1607  ret = actx->GetState();
1608  return ret;
1609 }
1610 
1616 bool TV::LiveTV(bool showDialogs, const ChannelInfoList &selection)
1617 {
1618  m_requestDelete = false;
1619  m_allowRerecord = false;
1620  m_jumpToProgram = false;
1621 
1622  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
1623  if (actx->GetState() == kState_None &&
1624  RequestNextRecorder(actx, showDialogs, selection))
1625  {
1626  actx->SetInitialTVState(true);
1627  HandleStateChange(actx, actx);
1628  m_switchToRec = nullptr;
1629 
1630  // Start Idle Timer
1631  if (m_dbIdleTimeout > 0)
1632  {
1633  m_idleTimerId = StartTimer(m_dbIdleTimeout, __LINE__);
1634  LOG(VB_GENERAL, LOG_INFO, QString("Using Idle Timer. %1 minutes")
1635  .arg(m_dbIdleTimeout*(1.0F/60000.0F)));
1636  }
1637 
1638  ReturnPlayerLock(actx);
1639  return true;
1640  }
1641  ReturnPlayerLock(actx);
1642  return false;
1643 }
1644 
1645 int TV::GetLastRecorderNum(int player_idx) const
1646 {
1647  const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
1648  int ret = ctx->GetCardID();
1649  ReturnPlayerLock(ctx);
1650  return ret;
1651 }
1652 
1653 bool TV::RequestNextRecorder(PlayerContext *ctx, bool showDialogs,
1654  const ChannelInfoList &selection)
1655 {
1656  if (!ctx)
1657  return false;
1658 
1659  ctx->SetRecorder(nullptr);
1660 
1661  RemoteEncoder *testrec = nullptr;
1662  if (m_switchToRec)
1663  {
1664  // If this is set we, already got a new recorder in SwitchCards()
1665  testrec = m_switchToRec;
1666  m_switchToRec = nullptr;
1667  }
1668  else if (!selection.empty())
1669  {
1670  for (size_t i = 0; i < selection.size(); i++)
1671  {
1672  uint chanid = selection[i].m_chanid;
1673  QString channum = selection[i].m_channum;
1674  if (!chanid || channum.isEmpty())
1675  continue;
1676  QSet<uint> cards = IsTunableOn(ctx, chanid);
1677 
1678  if (chanid && !channum.isEmpty() && !cards.isEmpty())
1679  {
1680  testrec = RemoteGetExistingRecorder(*(cards.begin()));
1681  m_initialChanID = chanid;
1682  break;
1683  }
1684  }
1685  }
1686  else
1687  {
1688  // When starting LiveTV we just get the next free recorder
1689  testrec = RemoteRequestNextFreeRecorder(-1);
1690  }
1691 
1692  if (!testrec)
1693  return false;
1694 
1695  if (!testrec->IsValidRecorder())
1696  {
1697  if (showDialogs)
1698  ShowNoRecorderDialog(ctx);
1699 
1700  delete testrec;
1701 
1702  return false;
1703  }
1704 
1705  ctx->SetRecorder(testrec);
1706 
1707  return true;
1708 }
1709 
1710 void TV::FinishRecording(int player_ctx)
1711 {
1712  PlayerContext *ctx = GetPlayerReadLock(player_ctx, __FILE__, __LINE__);
1713  if (StateIsRecording(GetState(ctx)) && ctx->m_recorder)
1714  ctx->m_recorder->FinishRecording();
1715  ReturnPlayerLock(ctx);
1716 }
1717 
1719  const QStringList &msg, int timeuntil,
1720  bool hasrec, bool haslater)
1721 {
1722 #if 0
1723  LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording");
1724 #endif
1725  if (!StateIsLiveTV(GetState(ctx)))
1726  return;
1727 
1728  auto *info = new ProgramInfo(msg);
1729  if (!info->GetChanID())
1730  {
1731  delete info;
1732  return;
1733  }
1734 
1735  QMutexLocker locker(&m_askAllowLock);
1736  QString key = info->MakeUniqueKey();
1737  if (timeuntil > 0)
1738  {
1739  // add program to list
1740 #if 0
1741  LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording -- " +
1742  QString("adding '%1'").arg(info->m_title));
1743 #endif
1744  QDateTime expiry = MythDate::current().addSecs(timeuntil);
1745  m_askAllowPrograms[key] = AskProgramInfo(expiry, hasrec, haslater, info);
1746  }
1747  else
1748  {
1749  // remove program from list
1750  LOG(VB_GENERAL, LOG_INFO, LOC + "-- " +
1751  QString("removing '%1'").arg(info->GetTitle()));
1752  QMap<QString,AskProgramInfo>::iterator it = m_askAllowPrograms.find(key);
1753  if (it != m_askAllowPrograms.end())
1754  {
1755  delete (*it).m_info;
1756  m_askAllowPrograms.erase(it);
1757  }
1758  delete info;
1759  }
1760 
1761  ShowOSDAskAllow(ctx);
1762 }
1763 
1765 {
1766  QMutexLocker locker(&m_askAllowLock);
1767  if (!ctx->m_recorder)
1768  return;
1769 
1770  uint cardid = ctx->GetCardID();
1771 
1772  QString single_rec =
1773  tr("MythTV wants to record \"%1\" on %2 in %d seconds. "
1774  "Do you want to:");
1775 
1776  QString record_watch = tr("Record and watch while it records");
1777  QString let_record1 = tr("Let it record and go back to the Main Menu");
1778  QString let_recordm = tr("Let them record and go back to the Main Menu");
1779  QString record_later1 = tr("Record it later, I want to watch TV");
1780  QString record_laterm = tr("Record them later, I want to watch TV");
1781  QString do_not_record1= tr("Don't let it record, I want to watch TV");
1782  QString do_not_recordm= tr("Don't let them record, I want to watch TV");
1783 
1784  // eliminate timed out programs
1785  QDateTime timeNow = MythDate::current();
1786  QMap<QString,AskProgramInfo>::iterator it = m_askAllowPrograms.begin();
1787  QMap<QString,AskProgramInfo>::iterator next = it;
1788  while (it != m_askAllowPrograms.end())
1789  {
1790  next = it; ++next;
1791  if ((*it).m_expiry <= timeNow)
1792  {
1793 #if 0
1794  LOG(VB_GENERAL, LOG_DEBUG, LOC + "-- " +
1795  QString("removing '%1'").arg((*it).m_info->m_title));
1796 #endif
1797  delete (*it).m_info;
1798  m_askAllowPrograms.erase(it);
1799  }
1800  it = next;
1801  }
1802  int timeuntil = 0;
1803  QString message;
1804  uint conflict_count = m_askAllowPrograms.size();
1805 
1806  it = m_askAllowPrograms.begin();
1807  if ((1 == m_askAllowPrograms.size()) && ((*it).m_info->GetInputID() == cardid))
1808  {
1809  (*it).m_isInSameInputGroup = (*it).m_isConflicting = true;
1810  }
1811  else if (!m_askAllowPrograms.empty())
1812  {
1813  // get the currently used input on our card
1814  bool busy_input_grps_loaded = false;
1815  vector<uint> busy_input_grps;
1816  InputInfo busy_input;
1817  RemoteIsBusy(cardid, busy_input);
1818 
1819  // check if current input can conflict
1820  it = m_askAllowPrograms.begin();
1821  for (; it != m_askAllowPrograms.end(); ++it)
1822  {
1823  (*it).m_isInSameInputGroup =
1824  (cardid == (*it).m_info->GetInputID());
1825 
1826  if ((*it).m_isInSameInputGroup)
1827  continue;
1828 
1829  // is busy_input in same input group as recording
1830  if (!busy_input_grps_loaded)
1831  {
1832  busy_input_grps = CardUtil::GetInputGroups(busy_input.m_inputid);
1833  busy_input_grps_loaded = true;
1834  }
1835 
1836  vector<uint> input_grps =
1837  CardUtil::GetInputGroups((*it).m_info->GetInputID());
1838 
1839  for (size_t i = 0; i < input_grps.size(); i++)
1840  {
1841  if (find(busy_input_grps.begin(), busy_input_grps.end(),
1842  input_grps[i]) != busy_input_grps.end())
1843  {
1844  (*it).m_isInSameInputGroup = true;
1845  break;
1846  }
1847  }
1848  }
1849 
1850  // check if inputs that can conflict are ok
1851  conflict_count = 0;
1852  it = m_askAllowPrograms.begin();
1853  for (; it != m_askAllowPrograms.end(); ++it)
1854  {
1855  if (!(*it).m_isInSameInputGroup)
1856  (*it).m_isConflicting = false; // NOLINT(bugprone-branch-clone)
1857  else if (cardid == (*it).m_info->GetInputID())
1858  (*it).m_isConflicting = true; // NOLINT(bugprone-branch-clone)
1859  else if (!CardUtil::IsTunerShared(cardid, (*it).m_info->GetInputID()))
1860  (*it).m_isConflicting = true;
1861  else if ((busy_input.m_mplexid &&
1862  (busy_input.m_mplexid == (*it).m_info->QueryMplexID())) ||
1863  (!busy_input.m_mplexid &&
1864  (busy_input.m_chanid == (*it).m_info->GetChanID())))
1865  (*it).m_isConflicting = false;
1866  else
1867  (*it).m_isConflicting = true;
1868 
1869  conflict_count += (*it).m_isConflicting ? 1 : 0;
1870  }
1871  }
1872 
1873  it = m_askAllowPrograms.begin();
1874  for (; it != m_askAllowPrograms.end() && !(*it).m_isConflicting; ++it);
1875 
1876  if (conflict_count == 0)
1877  {
1878  LOG(VB_GENERAL, LOG_INFO, LOC + "The scheduler wants to make "
1879  "a non-conflicting recording.");
1880  // TODO take down mplexid and inform user of problem
1881  // on channel changes.
1882  }
1883  else if (conflict_count == 1 && ((*it).m_info->GetInputID() == cardid))
1884  {
1885 #if 0
1886  LOG(VB_GENERAL, LOG_DEBUG, LOC + "UpdateOSDAskAllowDialog -- " +
1887  "kAskAllowOneRec");
1888 #endif
1889 
1890  it = m_askAllowPrograms.begin();
1891 
1892  QString channel = m_dbChannelFormat;
1893  channel
1894  .replace("<num>", (*it).m_info->GetChanNum())
1895  .replace("<sign>", (*it).m_info->GetChannelSchedulingID())
1896  .replace("<name>", (*it).m_info->GetChannelName());
1897 
1898  message = single_rec.arg((*it).m_info->GetTitle()).arg(channel);
1899 
1900  OSD *osd = GetOSDLock(ctx);
1901  if (osd)
1902  {
1903  m_browseHelper->BrowseEnd(ctx, false);
1904  timeuntil = MythDate::current().secsTo((*it).m_expiry) * 1000;
1905  osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
1906  osd->DialogAddButton(record_watch, "DIALOG_ASKALLOW_WATCH_0",
1907  false, !((*it).m_hasRec));
1908  osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0");
1909  osd->DialogAddButton(((*it).m_hasLater) ? record_later1 : do_not_record1,
1910  "DIALOG_ASKALLOW_CANCELRECORDING_0",
1911  false, ((*it).m_hasRec));
1912  }
1913  ReturnOSDLock(ctx, osd);
1914  }
1915  else
1916  {
1917  if (conflict_count > 1)
1918  {
1919  message = tr(
1920  "MythTV wants to record these programs in %d seconds:");
1921  message += "\n";
1922  }
1923 
1924  bool has_rec = false;
1925  it = m_askAllowPrograms.begin();
1926  for (; it != m_askAllowPrograms.end(); ++it)
1927  {
1928  if (!(*it).m_isConflicting)
1929  continue;
1930 
1931  QString title = (*it).m_info->GetTitle();
1932  if ((title.length() < 10) && !(*it).m_info->GetSubtitle().isEmpty())
1933  title += ": " + (*it).m_info->GetSubtitle();
1934  if (title.length() > 20)
1935  title = title.left(17) + "...";
1936 
1937  QString channel = m_dbChannelFormat;
1938  channel
1939  .replace("<num>", (*it).m_info->GetChanNum())
1940  .replace("<sign>", (*it).m_info->GetChannelSchedulingID())
1941  .replace("<name>", (*it).m_info->GetChannelName());
1942 
1943  if (conflict_count > 1)
1944  {
1945  message += tr("\"%1\" on %2").arg(title).arg(channel);
1946  message += "\n";
1947  }
1948  else
1949  {
1950  message = single_rec.arg((*it).m_info->GetTitle()).arg(channel);
1951  has_rec = (*it).m_hasRec;
1952  }
1953  }
1954 
1955  if (conflict_count > 1)
1956  {
1957  message += "\n";
1958  message += tr("Do you want to:");
1959  }
1960 
1961  bool all_have_later = true;
1962  timeuntil = 9999999;
1963  it = m_askAllowPrograms.begin();
1964  for (; it != m_askAllowPrograms.end(); ++it)
1965  {
1966  if ((*it).m_isConflicting)
1967  {
1968  all_have_later &= (*it).m_hasLater;
1969  int tmp = MythDate::current().secsTo((*it).m_expiry);
1970  tmp *= 1000;
1971  timeuntil = min(timeuntil, max(tmp, 0));
1972  }
1973  }
1974  timeuntil = (9999999 == timeuntil) ? 0 : timeuntil;
1975 
1976  OSD *osd = GetOSDLock(ctx);
1977  if (osd && conflict_count > 1)
1978  {
1979  m_browseHelper->BrowseEnd(ctx, false);
1980  osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
1981  osd->DialogAddButton(let_recordm, "DIALOG_ASKALLOW_EXIT_0",
1982  false, true);
1983  osd->DialogAddButton((all_have_later) ? record_laterm : do_not_recordm,
1984  "DIALOG_ASKALLOW_CANCELCONFLICTING_0");
1985  }
1986  else if (osd)
1987  {
1988  m_browseHelper->BrowseEnd(ctx, false);
1989  osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
1990  osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0",
1991  false, !has_rec);
1992  osd->DialogAddButton((all_have_later) ? record_later1 : do_not_record1,
1993  "DIALOG_ASKALLOW_CANCELRECORDING_0",
1994  false, has_rec);
1995  }
1996  ReturnOSDLock(ctx, osd);
1997  }
1998 }
1999 
2000 void TV::HandleOSDAskAllow(PlayerContext *ctx, const QString& action)
2001 {
2002  if (!DialogIsVisible(ctx, OSD_DLG_ASKALLOW))
2003  return;
2004 
2005  if (!m_askAllowLock.tryLock())
2006  {
2007  LOG(VB_GENERAL, LOG_ERR, "allowrecordingbox : askAllowLock is locked");
2008  return;
2009  }
2010 
2011  if (action == "CANCELRECORDING")
2012  {
2013  if (ctx->m_recorder)
2014  ctx->m_recorder->CancelNextRecording(true);
2015  }
2016  else if (action == "CANCELCONFLICTING")
2017  {
2018  QMap<QString,AskProgramInfo>::iterator it =
2019  m_askAllowPrograms.begin();
2020  for (; it != m_askAllowPrograms.end(); ++it)
2021  {
2022  if ((*it).m_isConflicting)
2023  RemoteCancelNextRecording((*it).m_info->GetInputID(), true);
2024  }
2025  }
2026  else if (action == "WATCH")
2027  {
2028  if (ctx->m_recorder)
2029  ctx->m_recorder->CancelNextRecording(false);
2030  }
2031  else // if (action == "EXIT")
2032  {
2033  PrepareToExitPlayer(ctx, __LINE__);
2034  SetExitPlayer(true, true);
2035  }
2036 
2037  m_askAllowLock.unlock();
2038 }
2039 
2040 int TV::Playback(const ProgramInfo &rcinfo)
2041 {
2042  m_wantsToQuit = false;
2043  m_jumpToProgram = false;
2044  m_allowRerecord = false;
2045  m_requestDelete = false;
2047 
2048  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2049  if (mctx->GetState() != kState_None)
2050  {
2051  ReturnPlayerLock(mctx);
2052  return 0;
2053  }
2054 
2055  mctx->SetPlayingInfo(&rcinfo);
2056  mctx->SetInitialTVState(false);
2057  HandleStateChange(mctx, mctx);
2058 
2059  ReturnPlayerLock(mctx);
2060 
2061  if (LCD *lcd = LCD::Get())
2062  {
2063  lcd->switchToChannel(rcinfo.GetChannelSchedulingID(),
2064  rcinfo.GetTitle(), rcinfo.GetSubtitle());
2065  lcd->setFunctionLEDs((rcinfo.IsRecording())?FUNC_TV:FUNC_MOVIE, true);
2066  }
2067 
2068  return 1;
2069 }
2070 
2072 {
2073  return (state == kState_RecordingOnly ||
2074  state == kState_WatchingRecording);
2075 }
2076 
2078 {
2079  return (state == kState_WatchingPreRecorded ||
2080  state == kState_WatchingRecording ||
2081  state == kState_WatchingVideo ||
2082  state == kState_WatchingDVD ||
2083  state == kState_WatchingBD);
2084 }
2085 
2087 {
2088  return (state == kState_WatchingLiveTV);
2089 }
2090 
2092 {
2093  if (StateIsRecording(state))
2094  {
2095  if (state == kState_RecordingOnly)
2096  return kState_None;
2098  }
2099  return kState_Error;
2100 }
2101 
2102 #define TRANSITION(ASTATE,BSTATE) \
2103  ((ctxState == (ASTATE)) && (desiredNextState == (BSTATE)))
2104 
2105 #define SET_NEXT() do { nextState = desiredNextState; changed = true; } while(false)
2106 #define SET_LAST() do { nextState = ctxState; changed = true; } while(false)
2107 
2108 static QString tv_i18n(const QString &msg)
2109 {
2110  QByteArray msg_arr = msg.toLatin1();
2111  QString msg_i18n = TV::tr(msg_arr.constData());
2112  QByteArray msg_i18n_arr = msg_i18n.toLatin1();
2113  return (msg_arr == msg_i18n_arr) ? msg_i18n : msg;
2114 }
2115 
2125 {
2126  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1) -- begin")
2127  .arg(find_player_index(ctx)));
2128 
2129  if (!ctx) // can never happen, but keep coverity happy
2130  return;
2131 
2132  if (ctx->IsErrored())
2133  {
2134  LOG(VB_GENERAL, LOG_ERR, LOC +
2135  "Called after fatal error detected.");
2136  return;
2137  }
2138 
2139  bool changed = false;
2140 
2141  ctx->LockState();
2142  TVState nextState = ctx->GetState();
2143  if (ctx->m_nextState.empty())
2144  {
2145  LOG(VB_GENERAL, LOG_WARNING, LOC +
2146  "Warning, called with no state to change to.");
2147  ctx->UnlockState();
2148  return;
2149  }
2150 
2151  TVState ctxState = ctx->GetState();
2152  TVState desiredNextState = ctx->DequeueNextState();
2153 
2154  LOG(VB_GENERAL, LOG_INFO, LOC +
2155  QString("Attempting to change from %1 to %2")
2156  .arg(StateToString(nextState))
2157  .arg(StateToString(desiredNextState)));
2158 
2159  if (desiredNextState == kState_Error)
2160  {
2161  LOG(VB_GENERAL, LOG_ERR, LOC + "Attempting to set to an error state!");
2162  SetErrored(ctx);
2163  ctx->UnlockState();
2164  return;
2165  }
2166 
2167  bool ok = false;
2169  {
2170  ctx->m_lastSignalUIInfo.clear();
2171 
2172  ctx->m_recorder->Setup();
2173 
2174  QDateTime timerOffTime = MythDate::current();
2175  m_lockTimerOn = false;
2176 
2177  SET_NEXT();
2178 
2179  uint chanid = m_initialChanID;
2180  if (!chanid)
2181  chanid = gCoreContext->GetNumSetting("DefaultChanid", 0);
2182 
2183  if (chanid && !IsTunable(ctx, chanid))
2184  chanid = 0;
2185 
2186  QString channum = "";
2187 
2188  if (chanid)
2189  {
2190  QStringList reclist;
2191 
2192  MSqlQuery query(MSqlQuery::InitCon());
2193  query.prepare("SELECT channum FROM channel "
2194  "WHERE chanid = :CHANID");
2195  query.bindValue(":CHANID", chanid);
2196  if (query.exec() && query.isActive() && query.size() > 0 && query.next())
2197  channum = query.value(0).toString();
2198  else
2199  channum = QString::number(chanid);
2200 
2201  bool getit = ctx->m_recorder->ShouldSwitchToAnotherCard(
2202  QString::number(chanid));
2203 
2204  if (getit)
2205  reclist = ChannelUtil::GetValidRecorderList(chanid, channum);
2206 
2207  if (!reclist.empty())
2208  {
2209  RemoteEncoder *testrec = RemoteRequestFreeRecorderFromList(reclist, 0);
2210  if (testrec && testrec->IsValidRecorder())
2211  {
2212  ctx->SetRecorder(testrec);
2213  ctx->m_recorder->Setup();
2214  }
2215  else
2216  delete testrec; // If testrec isn't a valid recorder ...
2217  }
2218  else if (getit)
2219  chanid = 0;
2220  }
2221 
2222  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- begin");
2223 
2224  if (chanid && !channum.isEmpty())
2225  ctx->m_recorder->SpawnLiveTV(ctx->m_tvchain->GetID(), false, channum);
2226  else
2227  ctx->m_recorder->SpawnLiveTV(ctx->m_tvchain->GetID(), false, "");
2228 
2229  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- end");
2230 
2231  if (!ctx->ReloadTVChain())
2232  {
2233  LOG(VB_GENERAL, LOG_ERR, LOC +
2234  "LiveTV not successfully started");
2235  RestoreScreenSaver(ctx);
2236  ctx->SetRecorder(nullptr);
2237  SetErrored(ctx);
2238  SET_LAST();
2239  }
2240  else
2241  {
2242  ctx->LockPlayingInfo(__FILE__, __LINE__);
2243  QString playbackURL = ctx->m_playingInfo->GetPlaybackURL(true);
2244  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2245 
2246  bool opennow = (ctx->m_tvchain->GetInputType(-1) != "DUMMY");
2247 
2248  LOG(VB_GENERAL, LOG_INFO, LOC +
2249  QString("playbackURL(%1) inputtype(%2)")
2250  .arg(playbackURL).arg(ctx->m_tvchain->GetInputType(-1)));
2251 
2252  ctx->SetRingBuffer(
2254  playbackURL, false, true,
2255  opennow ? RingBuffer::kLiveTVOpenTimeout : -1));
2256 
2257  if (ctx->m_buffer)
2258  ctx->m_buffer->SetLiveMode(ctx->m_tvchain);
2259  }
2260 
2261 
2262  if (ctx->m_playingInfo && StartRecorder(ctx,-1))
2263  {
2264  ok = StartPlayer(mctx, ctx, desiredNextState);
2265  }
2266  if (!ok)
2267  {
2268  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
2269  RestoreScreenSaver(ctx);
2270  ctx->SetRecorder(nullptr);
2271  SetErrored(ctx);
2272  SET_LAST();
2273  }
2274  else if (!ctx->IsPIP())
2275  {
2276  if (!m_lastLockSeenTime.isValid() ||
2277  (m_lastLockSeenTime < timerOffTime))
2278  {
2279  m_lockTimer.start();
2280  m_lockTimerOn = true;
2281  }
2282  }
2283 
2284  if (mctx != ctx)
2285  SetActive(ctx, find_player_index(ctx), false);
2286  }
2288  {
2289  SET_NEXT();
2290  RestoreScreenSaver(ctx);
2291  StopStuff(mctx, ctx, true, true, true);
2292 
2293  if ((mctx != ctx) && (GetPlayer(ctx,-1) == ctx))
2294  SetActive(mctx, 0, true);
2295  }
2301  {
2302  ctx->LockPlayingInfo(__FILE__, __LINE__);
2303  QString playbackURL = ctx->m_playingInfo->GetPlaybackURL(true);
2304  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2305 
2306  RingBuffer *buffer = RingBuffer::Create(playbackURL, false);
2307  if (buffer && !buffer->GetLastError().isEmpty())
2308  {
2309  ShowNotificationError(tr("Can't start playback"),
2310  TV::tr( "TV Player" ), buffer->GetLastError());
2311  delete buffer;
2312  buffer = nullptr;
2313  }
2314  ctx->SetRingBuffer(buffer);
2315 
2316  if (ctx->m_buffer && ctx->m_buffer->IsOpen())
2317  {
2318  if (desiredNextState == kState_WatchingRecording)
2319  {
2320  ctx->LockPlayingInfo(__FILE__, __LINE__);
2322  ctx->m_playingInfo);
2323  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2324 
2325  ctx->SetRecorder(rec);
2326 
2327  if (!ctx->m_recorder ||
2328  !ctx->m_recorder->IsValidRecorder())
2329  {
2330  LOG(VB_GENERAL, LOG_ERR, LOC +
2331  "Couldn't find recorder for in-progress recording");
2332  desiredNextState = kState_WatchingPreRecorded;
2333  ctx->SetRecorder(nullptr);
2334  }
2335  else
2336  {
2337  ctx->m_recorder->Setup();
2338  }
2339  }
2340 
2341  ok = StartPlayer(mctx, ctx, desiredNextState);
2342 
2343  if (ok)
2344  {
2345  SET_NEXT();
2346 
2347  ctx->LockPlayingInfo(__FILE__, __LINE__);
2348  if (ctx->m_playingInfo->IsRecording())
2349  {
2350  QString message = "COMMFLAG_REQUEST ";
2351  message += ctx->m_playingInfo->MakeUniqueKey();
2352  gCoreContext->SendMessage(message);
2353  }
2354  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2355  }
2356  }
2357 
2358  if (!ok)
2359  {
2360  SET_LAST();
2361  SetErrored(ctx);
2362  if (ctx->IsPlayerErrored())
2363  {
2365  TV::tr( "TV Player" ),
2366  playbackURL);
2367  // We're going to display this error as notification
2368  // no need to display it later as popup
2369  ctx->m_player->ResetErrored();
2370  }
2371  }
2372  else if (mctx != ctx)
2373  {
2374  SetActive(ctx, find_player_index(ctx), false);
2375  }
2376  }
2382  {
2383  SET_NEXT();
2384 
2385  RestoreScreenSaver(ctx);
2386  StopStuff(mctx, ctx, true, true, false);
2387 
2388  if ((mctx != ctx) && (GetPlayer(ctx,-1) == ctx))
2389  SetActive(mctx, 0, true);
2390  }
2393  {
2394  SET_NEXT();
2395  }
2396 
2397  // Print state changed message...
2398  if (!changed)
2399  {
2400  LOG(VB_GENERAL, LOG_ERR, LOC +
2401  QString("Unknown state transition: %1 to %2")
2402  .arg(StateToString(ctx->GetState()))
2403  .arg(StateToString(desiredNextState)));
2404  }
2405  else if (ctx->GetState() != nextState)
2406  {
2407  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Changing from %1 to %2")
2408  .arg(StateToString(ctx->GetState()))
2409  .arg(StateToString(nextState)));
2410  }
2411 
2412  // update internal state variable
2413  TVState lastState = ctx->GetState();
2414  ctx->m_playingState = nextState;
2415  ctx->UnlockState();
2416 
2417  if (mctx == ctx)
2418  {
2419  if (StateIsLiveTV(ctx->GetState()))
2420  {
2421  LOG(VB_GENERAL, LOG_INFO, LOC + "State is LiveTV & mctx == ctx");
2422  UpdateOSDInput(ctx);
2423  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateOSDInput done");
2424  UpdateLCD();
2425  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateLCD done");
2426  ITVRestart(ctx, true);
2427  LOG(VB_GENERAL, LOG_INFO, LOC + "ITVRestart done");
2428  }
2429  else if (StateIsPlaying(ctx->GetState()) && lastState == kState_None)
2430  {
2431  ctx->LockPlayingInfo(__FILE__, __LINE__);
2432  int count = PlayGroup::GetCount();
2433  QString msg = tr("%1 Settings")
2434  .arg(tv_i18n(ctx->m_playingInfo->GetPlaybackGroup()));
2435  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
2436  if (count > 0)
2437  SetOSDMessage(ctx, msg);
2438  ITVRestart(ctx, false);
2439  }
2440 
2441  if (ctx->m_buffer && ctx->m_buffer->IsDVD())
2442  {
2443  UpdateLCD();
2444  }
2445 
2446  if (ctx->m_recorder)
2447  ctx->m_recorder->FrontendReady();
2448 
2449  QMutexLocker locker(&m_timerIdLock);
2450  if (m_endOfRecPromptTimerId)
2451  KillTimer(m_endOfRecPromptTimerId);
2452  m_endOfRecPromptTimerId = 0;
2453  if (m_dbEndOfRecExitPrompt && !m_inPlaylist && !m_underNetworkControl)
2454  {
2455  m_endOfRecPromptTimerId =
2456  StartTimer(kEndOfRecPromptCheckFrequency, __LINE__);
2457  }
2458 
2459  if (m_endOfPlaybackTimerId)
2460  KillTimer(m_endOfPlaybackTimerId);
2461  m_endOfPlaybackTimerId = 0;
2462 
2463  if (StateIsPlaying(ctx->GetState()))
2464  {
2465  m_endOfPlaybackTimerId =
2466  StartTimer(kEndOfPlaybackFirstCheckTimer, __LINE__);
2467 
2468  }
2469 
2470  }
2471 
2478  {
2479  if (!ctx->IsPIP())
2481  // m_playerBounds is not applicable when switching modes so
2482  // skip this logic in that case.
2483  if (!m_dbUseVideoModes)
2484  GetMythMainWindow()->MoveResize(m_playerBounds);
2485 
2486  if (!m_weDisabledGUI)
2487  {
2488  m_weDisabledGUI = true;
2490  }
2491  // we no longer need the contents of myWindow
2492  if (m_myWindow)
2493  m_myWindow->DeleteAllChildren();
2494 
2495  LOG(VB_GENERAL, LOG_INFO, LOC + "Main UI disabled.");
2496  }
2497 
2498  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2499  QString("(%1) -- end")
2500  .arg(find_player_index(ctx)));
2501 }
2502 #undef TRANSITION
2503 #undef SET_NEXT
2504 #undef SET_LAST
2505 
2513 bool TV::StartRecorder(PlayerContext *ctx, int maxWait)
2514 {
2515  RemoteEncoder *rec = ctx->m_recorder;
2516  maxWait = (maxWait <= 0) ? 40000 : maxWait;
2517  MythTimer t;
2518  t.start();
2519  bool recording = false, ok = true;
2520  if (!rec) {
2521  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid Remote Encoder");
2522  SetErrored(ctx);
2523  return false;
2524  }
2525  while (!(recording = rec->IsRecording(&ok)) &&
2526  !m_exitPlayerTimerId && t.elapsed() < maxWait)
2527  {
2528  if (!ok)
2529  {
2530  LOG(VB_GENERAL, LOG_ERR, LOC + "Lost contact with backend");
2531  SetErrored(ctx);
2532  return false;
2533  }
2534  std::this_thread::sleep_for(std::chrono::microseconds(5));
2535  }
2536 
2537  if (!recording || m_exitPlayerTimerId)
2538  {
2539  if (!m_exitPlayerTimerId)
2540  LOG(VB_GENERAL, LOG_ERR, LOC +
2541  "Timed out waiting for recorder to start");
2542  return false;
2543  }
2544 
2545  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2546  QString("Took %1 ms to start recorder.")
2547  .arg(t.elapsed()));
2548 
2549  return true;
2550 }
2551 
2568  bool stopRingBuffer, bool stopPlayer, bool stopRecorder)
2569 {
2570  LOG(VB_PLAYBACK, LOG_DEBUG,
2571  LOC + QString("For player ctx %1 -- begin")
2572  .arg(find_player_index(ctx)));
2573 
2574  SetActive(mctx, 0, false);
2575 
2576  if (ctx->m_buffer)
2577  ctx->m_buffer->IgnoreWaitStates(true);
2578 
2579  ctx->LockDeletePlayer(__FILE__, __LINE__);
2580  if (stopPlayer)
2581  ctx->StopPlaying();
2582  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
2583 
2584  if (stopRingBuffer)
2585  {
2586  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopping ring buffer");
2587  if (ctx->m_buffer)
2588  {
2589  ctx->m_buffer->StopReads();
2590  ctx->m_buffer->Pause();
2591  ctx->m_buffer->WaitForPause();
2592  }
2593  }
2594 
2595  if (stopPlayer)
2596  {
2597  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopping player");
2598  if (ctx == mctx)
2599  {
2600  for (uint i = 1; mctx && (i < m_player.size()); i++)
2601  StopStuff(mctx, GetPlayer(mctx,i), true, true, true);
2602  }
2603  }
2604 
2605  if (stopRecorder)
2606  {
2607  LOG(VB_PLAYBACK, LOG_INFO, LOC + "stopping recorder");
2608  if (ctx->m_recorder)
2609  ctx->m_recorder->StopLiveTV();
2610  }
2611 
2612  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
2613 }
2614 
2616 {
2617  int ctx_index = find_player_index(ctx);
2618 
2619  QString loc = LOC + QString("player ctx %1")
2620  .arg(ctx_index);
2621 
2622  if (!mctx || !ctx || ctx_index < 0)
2623  {
2624  LOG(VB_GENERAL, LOG_ERR, loc + "-- error");
2625  return;
2626  }
2627 
2628  LOG(VB_PLAYBACK, LOG_INFO, loc);
2629 
2630  if (mctx != ctx)
2631  {
2632  if (ctx->HasPlayer())
2633  {
2634  PIPRemovePlayer(mctx, ctx);
2635  ctx->SetPlayer(nullptr);
2636  }
2637 
2638  m_player.erase(m_player.begin() + ctx_index);
2639  delete ctx;
2640  if (mctx->IsPBP())
2641  PBPRestartMainPlayer(mctx);
2642  SetActive(mctx, m_playerActive, false);
2643  return;
2644  }
2645 
2646  ctx->TeardownPlayer();
2647 }
2648 
2649 void TV::timerEvent(QTimerEvent *te)
2650 {
2651  const int timer_id = te->timerId();
2652 
2653  PlayerContext *mctx2 = GetPlayerReadLock(0, __FILE__, __LINE__);
2654  if (mctx2->IsErrored())
2655  {
2656  ReturnPlayerLock(mctx2);
2657  return;
2658  }
2659  ReturnPlayerLock(mctx2);
2660 
2661  bool ignore = false;
2662  {
2663  QMutexLocker locker(&m_timerIdLock);
2664  ignore =
2665  (!m_stateChangeTimerId.empty() &&
2666  m_stateChangeTimerId.find(timer_id) == m_stateChangeTimerId.end());
2667  }
2668  if (ignore)
2669  return; // Always handle state changes first...
2670 
2671  bool handled = true;
2672  if (timer_id == m_lcdTimerId)
2673  HandleLCDTimerEvent();
2674  else if (timer_id == m_lcdVolumeTimerId)
2675  HandleLCDVolumeTimerEvent();
2676  else if (timer_id == m_sleepTimerId)
2677  ShowOSDSleep();
2678  else if (timer_id == m_sleepDialogTimerId)
2679  SleepDialogTimeout();
2680  else if (timer_id == m_idleTimerId)
2681  ShowOSDIdle();
2682  else if (timer_id == m_idleDialogTimerId)
2683  IdleDialogTimeout();
2684  else if (timer_id == m_endOfPlaybackTimerId)
2685  HandleEndOfPlaybackTimerEvent();
2686  else if (timer_id == m_embedCheckTimerId)
2687  HandleIsNearEndWhenEmbeddingTimerEvent();
2688  else if (timer_id == m_endOfRecPromptTimerId)
2689  HandleEndOfRecordingExitPromptTimerEvent();
2690  else if (timer_id == m_videoExitDialogTimerId)
2691  HandleVideoExitDialogTimerEvent();
2692  else if (timer_id == m_pseudoChangeChanTimerId)
2693  HandlePseudoLiveTVTimerEvent();
2694  else if (timer_id == m_speedChangeTimerId)
2695  HandleSpeedChangeTimerEvent();
2696  else if (timer_id == m_pipChangeTimerId)
2697  HandlePxPTimerEvent();
2698  else if (timer_id == m_saveLastPlayPosTimerId)
2699  HandleSaveLastPlayPosEvent();
2700  else
2701  handled = false;
2702 
2703  if (handled)
2704  return;
2705 
2706  // Check if it matches a stateChangeTimerId
2707  PlayerContext *ctx = nullptr;
2708  {
2709  QMutexLocker locker(&m_timerIdLock);
2710  TimerContextMap::iterator it = m_stateChangeTimerId.find(timer_id);
2711  if (it != m_stateChangeTimerId.end())
2712  {
2713  KillTimer(timer_id);
2714  ctx = *it;
2715  m_stateChangeTimerId.erase(it);
2716  }
2717  }
2718 
2719  if (ctx)
2720  {
2721  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2722  bool still_exists = find_player_index(ctx) >= 0;
2723 
2724  while (still_exists && !ctx->m_nextState.empty())
2725  {
2726  HandleStateChange(mctx, ctx);
2727  if ((kState_None == ctx->GetState() ||
2728  kState_Error == ctx->GetState()) &&
2729  ((mctx != ctx) || m_jumpToProgram))
2730  {
2731  ReturnPlayerLock(mctx);
2732  mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
2733  TeardownPlayer(mctx, ctx);
2734  still_exists = false;
2735  }
2736  }
2737  ReturnPlayerLock(mctx);
2738  handled = true;
2739  }
2740 
2741  if (handled)
2742  return;
2743 
2744  // Check if it matches a signalMonitorTimerId
2745  ctx = nullptr;
2746  {
2747  QMutexLocker locker(&m_timerIdLock);
2748  TimerContextMap::iterator it = m_signalMonitorTimerId.find(timer_id);
2749  if (it != m_signalMonitorTimerId.end())
2750  {
2751  KillTimer(timer_id);
2752  ctx = *it;
2753  m_signalMonitorTimerId.erase(it);
2754  }
2755  }
2756 
2757  if (ctx)
2758  {
2759  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2760  bool still_exists = find_player_index(ctx) >= 0;
2761 
2762  if (still_exists && !ctx->m_lastSignalMsg.empty())
2763  { // set last signal msg, so we get some feedback...
2764  UpdateOSDSignal(ctx, ctx->m_lastSignalMsg);
2765  ctx->m_lastSignalMsg.clear();
2766  }
2767  UpdateOSDTimeoutMessage(ctx);
2768 
2769  ReturnPlayerLock(mctx);
2770  handled = true;
2771  }
2772 
2773  if (handled)
2774  return;
2775 
2776  // Check if it matches networkControlTimerId
2777  QString netCmd;
2778  {
2779  QMutexLocker locker(&m_timerIdLock);
2780  if (timer_id == m_networkControlTimerId)
2781  {
2782  if (!networkControlCommands.empty())
2783  netCmd = networkControlCommands.dequeue();
2784  if (networkControlCommands.empty())
2785  {
2786  KillTimer(m_networkControlTimerId);
2787  m_networkControlTimerId = 0;
2788  }
2789  }
2790  }
2791 
2792  if (!netCmd.isEmpty())
2793  {
2794  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2795  ProcessNetworkControlCommand(actx, netCmd);
2796  ReturnPlayerLock(actx);
2797  handled = true;
2798  }
2799 
2800  if (handled)
2801  return;
2802 
2803  // Check if it matches exitPlayerTimerId
2804  if (timer_id == m_exitPlayerTimerId)
2805  {
2806  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
2807 
2808  OSD *osd = GetOSDLock(mctx);
2809  if (osd)
2810  {
2811  osd->DialogQuit();
2812  osd->HideAll();
2813  }
2814  ReturnOSDLock(mctx, osd);
2815 
2816  if (m_jumpToProgram && m_lastProgram)
2817  {
2818  if (!m_lastProgram->IsFileReadable())
2819  {
2820  SetOSDMessage(mctx, tr("Last Program: %1 Doesn't Exist")
2821  .arg(m_lastProgram->GetTitle()));
2822  lastProgramStringList.clear();
2823  SetLastProgram(nullptr);
2824  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2825  "Last Program File does not exist");
2826  m_jumpToProgram = false;
2827  }
2828  else
2829  ForceNextStateNone(mctx);
2830  }
2831  else
2832  ForceNextStateNone(mctx);
2833 
2834  ReturnPlayerLock(mctx);
2835 
2836  QMutexLocker locker(&m_timerIdLock);
2837  KillTimer(m_exitPlayerTimerId);
2838  m_exitPlayerTimerId = 0;
2839  handled = true;
2840  }
2841 
2842  if (handled)
2843  return;
2844 
2845  if (timer_id == m_jumpMenuTimerId)
2846  {
2847  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2848  if (actx)
2849  FillOSDMenuJumpRec(actx);
2850  ReturnPlayerLock(actx);
2851 
2852  QMutexLocker locker(&m_timerIdLock);
2853  KillTimer(m_jumpMenuTimerId);
2854  m_jumpMenuTimerId = 0;
2855  handled = true;
2856  }
2857 
2858  if (handled)
2859  return;
2860 
2861  if (timer_id == m_switchToInputTimerId)
2862  {
2863  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2864  if (m_switchToInputId)
2865  {
2866  uint tmp = m_switchToInputId;
2867  m_switchToInputId = 0;
2868  SwitchInputs(actx, 0, QString(), tmp);
2869  }
2870  ReturnPlayerLock(actx);
2871 
2872  QMutexLocker locker(&m_timerIdLock);
2873  KillTimer(m_switchToInputTimerId);
2874  m_switchToInputTimerId = 0;
2875  handled = true;
2876  }
2877 
2878  if (handled)
2879  return;
2880 
2881  if (timer_id == m_ccInputTimerId)
2882  {
2883  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2884  // Clear closed caption input mode when timer expires
2885  if (m_ccInputMode)
2886  {
2887  m_ccInputMode = false;
2888  ClearInputQueues(actx, true);
2889  }
2890  ReturnPlayerLock(actx);
2891 
2892  QMutexLocker locker(&m_timerIdLock);
2893  KillTimer(m_ccInputTimerId);
2894  m_ccInputTimerId = 0;
2895  handled = true;
2896  }
2897 
2898  if (handled)
2899  return;
2900 
2901  if (timer_id == m_asInputTimerId)
2902  {
2903  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2904  // Clear closed caption input mode when timer expires
2905  if (m_asInputMode)
2906  {
2907  m_asInputMode = false;
2908  ClearInputQueues(actx, true);
2909  }
2910  ReturnPlayerLock(actx);
2911 
2912  QMutexLocker locker(&m_timerIdLock);
2913  KillTimer(m_asInputTimerId);
2914  m_asInputTimerId = 0;
2915  handled = true;
2916  }
2917 
2918  if (handled)
2919  return;
2920 
2921  if (timer_id == m_queueInputTimerId)
2922  {
2923  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2924  // Commit input when the OSD fades away
2925  if (HasQueuedChannel())
2926  {
2927  OSD *osd = GetOSDLock(actx);
2928  if (osd && !osd->IsWindowVisible("osd_input"))
2929  {
2930  ReturnOSDLock(actx, osd);
2931  CommitQueuedInput(actx);
2932  }
2933  else
2934  ReturnOSDLock(actx, osd);
2935  }
2936  ReturnPlayerLock(actx);
2937 
2938  QMutexLocker locker(&m_timerIdLock);
2939  if (!m_queuedChanID && m_queuedChanNum.isEmpty() && m_queueInputTimerId)
2940  {
2941  KillTimer(m_queueInputTimerId);
2942  m_queueInputTimerId = 0;
2943  }
2944  handled = true;
2945  }
2946 
2947  if (handled)
2948  return;
2949 
2950  if (timer_id == m_browseTimerId)
2951  {
2952  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2953  m_browseHelper->BrowseEnd(actx, false);
2954  ReturnPlayerLock(actx);
2955  handled = true;
2956  }
2957 
2958  if (handled)
2959  return;
2960 
2961  if (timer_id == m_updateOSDDebugTimerId)
2962  {
2963  bool update = false;
2964  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2965  OSD *osd = GetOSDLock(actx);
2966  if (osd && osd->IsWindowVisible("osd_debug") &&
2967  (StateIsLiveTV(actx->GetState()) ||
2968  StateIsPlaying(actx->GetState())))
2969  {
2970  update = true;
2971  }
2972  else
2973  {
2974  QMutexLocker locker(&m_timerIdLock);
2975  KillTimer(m_updateOSDDebugTimerId);
2976  m_updateOSDDebugTimerId = 0;
2977  if (actx->m_buffer)
2978  actx->m_buffer->EnableBitrateMonitor(false);
2979  if (actx->m_player)
2980  actx->m_player->EnableFrameRateMonitor(false);
2981  }
2982  ReturnOSDLock(actx, osd);
2983  if (update)
2984  UpdateOSDDebug(actx);
2985  ReturnPlayerLock(actx);
2986  handled = true;
2987  }
2988  if (timer_id == m_updateOSDPosTimerId)
2989  {
2990  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
2991  OSD *osd = GetOSDLock(actx);
2992  if (osd && osd->IsWindowVisible("osd_status") &&
2993  (StateIsLiveTV(actx->GetState()) ||
2994  StateIsPlaying(actx->GetState())))
2995  {
2996  osdInfo info;
2997  if (actx->CalcPlayerSliderPosition(info))
2998  {
2999  osd->SetText("osd_status", info.text, kOSDTimeout_Ignore);
3000  osd->SetValues("osd_status", info.values, kOSDTimeout_Ignore);
3001  }
3002  }
3003  else
3004  SetUpdateOSDPosition(false);
3005  ReturnOSDLock(actx, osd);
3006  ReturnPlayerLock(actx);
3007  handled = true;
3008  }
3009 
3010  if (handled)
3011  return;
3012 
3013  if (timer_id == m_errorRecoveryTimerId)
3014  {
3015  bool error = false;
3016  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3017 
3018  if (mctx->IsRecorderErrored() ||
3019  mctx->IsPlayerErrored() ||
3020  mctx->IsErrored())
3021  {
3022  SetExitPlayer(true, false);
3023  ForceNextStateNone(mctx);
3024  error = true;
3025  }
3026 
3027  for (size_t i = 0; i < m_player.size(); i++)
3028  {
3029  PlayerContext *ctx2 = GetPlayer(mctx, i);
3030  if (error || ctx2->IsErrored())
3031  ForceNextStateNone(ctx2);
3032  }
3033  ReturnPlayerLock(mctx);
3034 
3035  QMutexLocker locker(&m_timerIdLock);
3036  if (m_errorRecoveryTimerId)
3037  KillTimer(m_errorRecoveryTimerId);
3038  m_errorRecoveryTimerId =
3039  StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
3040  }
3041 }
3042 
3044 {
3045  QString cmd;
3046 
3047  {
3048  QMutexLocker locker(&m_timerIdLock);
3049  if (m_changePxP.empty())
3050  {
3051  if (m_pipChangeTimerId)
3052  KillTimer(m_pipChangeTimerId);
3053  m_pipChangeTimerId = 0;
3054  return true;
3055  }
3056  cmd = m_changePxP.dequeue();
3057  }
3058 
3059  PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
3060  PlayerContext *actx = GetPlayer(mctx, -1);
3061 
3062  if (cmd == "TOGGLEPIPMODE")
3063  PxPToggleView(actx, false);
3064  else if (cmd == "TOGGLEPBPMODE")
3065  PxPToggleView(actx, true);
3066  else if (cmd == "CREATEPIPVIEW")
3067  PxPCreateView(actx, false);
3068  else if (cmd == "CREATEPBPVIEW")
3069  PxPCreateView(actx, true);
3070  else if (cmd == "SWAPPIP")
3071  {
3072  if (mctx != actx)
3073  PxPSwap(mctx, actx);
3074  else if (mctx && m_player.size() == 2)
3075  PxPSwap(mctx, GetPlayer(mctx,1));
3076  }
3077  else if (cmd == "TOGGLEPIPSTATE")
3078  PxPToggleType(mctx, !mctx->IsPBP());
3079 
3080  ReturnPlayerLock(mctx);
3081 
3082  QMutexLocker locker(&m_timerIdLock);
3083 
3084  if (m_pipChangeTimerId)
3085  KillTimer(m_pipChangeTimerId);
3086 
3087  if (m_changePxP.empty())
3088  m_pipChangeTimerId = 0;
3089  else
3090  m_pipChangeTimerId = StartTimer(20, __LINE__);
3091 
3092  return true;
3093 }
3094 
3096 {
3097  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3098  LCD *lcd = LCD::Get();
3099  if (lcd)
3100  {
3101  float progress = 0.0F;
3102  QString lcd_time_string;
3103  bool showProgress = true;
3104 
3105  if (StateIsLiveTV(GetState(actx)))
3106  ShowLCDChannelInfo(actx);
3107 
3108  if (actx->m_buffer && actx->m_buffer->IsDVD())
3109  {
3110  ShowLCDDVDInfo(actx);
3111  showProgress = !actx->m_buffer->IsInDiscMenuOrStillFrame();
3112  }
3113 
3114  if (showProgress)
3115  {
3116  osdInfo info;
3117  if (actx->CalcPlayerSliderPosition(info)) {
3118  progress = info.values["position"] * 0.001F;
3119 
3120  lcd_time_string = info.text["playedtime"] + " / " + info.text["totaltime"];
3121  // if the string is longer than the LCD width, remove all spaces
3122  if (lcd_time_string.length() > (int)lcd->getLCDWidth())
3123  lcd_time_string.remove(' ');
3124  }
3125  }
3126  lcd->setChannelProgress(lcd_time_string, progress);
3127  }
3128  ReturnPlayerLock(actx);
3129 
3130  QMutexLocker locker(&m_timerIdLock);
3131  KillTimer(m_lcdTimerId);
3132  m_lcdTimerId = StartTimer(kLCDTimeout, __LINE__);
3133 
3134  return true;
3135 }
3136 
3138 {
3139  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3140  LCD *lcd = LCD::Get();
3141  if (lcd)
3142  {
3143  ShowLCDChannelInfo(actx);
3144  lcd->switchToChannel(m_lcdCallsign, m_lcdTitle, m_lcdSubtitle);
3145  }
3146  ReturnPlayerLock(actx);
3147 
3148  QMutexLocker locker(&m_timerIdLock);
3149  KillTimer(m_lcdVolumeTimerId);
3150  m_lcdVolumeTimerId = 0;
3151 }
3152 
3153 int TV::StartTimer(int interval, int line)
3154 {
3155  int x = QObject::startTimer(interval);
3156  if (!x)
3157  {
3158  LOG(VB_GENERAL, LOG_ERR, LOC +
3159  QString("Failed to start timer on line %1 of %2")
3160  .arg(line).arg(__FILE__));
3161  }
3162  return x;
3163 }
3164 
3165 void TV::KillTimer(int id)
3166 {
3167  QObject::killTimer(id);
3168 }
3169 
3171 {
3172  ctx->ForceNextStateNone();
3173  ScheduleStateChange(ctx);
3174 }
3175 
3177 {
3178  QMutexLocker locker(&m_timerIdLock);
3179  m_stateChangeTimerId[StartTimer(1, __LINE__)] = ctx;
3180 }
3181 
3183 {
3184  if (!ctx)
3185  return;
3186  QMutexLocker locker(&m_timerIdLock);
3187  ctx->m_errored = true;
3188  KillTimer(m_errorRecoveryTimerId);
3189  m_errorRecoveryTimerId = StartTimer(1, __LINE__);
3190 }
3191 
3193  const ProgramInfo &p)
3194 {
3195  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switching to program: %1")
3196  .arg(p.toString(ProgramInfo::kTitleSubtitle)));
3197  SetLastProgram(&p);
3198  PrepareToExitPlayer(ctx,__LINE__);
3199  m_jumpToProgram = true;
3200  SetExitPlayer(true, true);
3201 }
3202 
3204 {
3205  bool bm_allowed = IsBookmarkAllowed(ctx);
3206  ctx->LockDeletePlayer(__FILE__, line);
3207  if (ctx->m_player)
3208  {
3209  if (bm_allowed)
3210  {
3211  // If we're exiting in the middle of the recording, we
3212  // automatically save a bookmark when "Action on playback
3213  // exit" is set to "Save position and exit".
3214  bool allow_set_before_end =
3215  (bookmark == kBookmarkAlways ||
3216  (bookmark == kBookmarkAuto &&
3217  m_dbPlaybackExitPrompt == 2));
3218  // If we're exiting at the end of the recording, we
3219  // automatically clear the bookmark when "Action on
3220  // playback exit" is set to "Save position and exit" and
3221  // "Clear bookmark on playback" is set to true.
3222  bool allow_clear_at_end =
3223  (bookmark == kBookmarkAlways ||
3224  (bookmark == kBookmarkAuto &&
3225  m_dbPlaybackExitPrompt == 2 &&
3226  m_dbClearSavedPosition));
3227  // Whether to set/clear a bookmark depends on whether we're
3228  // exiting at the end of a recording.
3229  bool at_end = (ctx->m_player->IsNearEnd() || getEndOfRecording());
3230  // Don't consider ourselves at the end if the recording is
3231  // in-progress.
3232  at_end &= !StateIsRecording(GetState(ctx));
3233  bool clear_lastplaypos = false;
3234  if (at_end && allow_clear_at_end)
3235  {
3236  SetBookmark(ctx, true);
3237  // Tidy up the lastplaypos mark only when we clear the
3238  // bookmark due to exiting at the end.
3239  clear_lastplaypos = true;
3240  }
3241  else if (!at_end && allow_set_before_end)
3242  {
3243  SetBookmark(ctx, false);
3244  }
3245  if (clear_lastplaypos && ctx->m_playingInfo)
3247  }
3248  if (m_dbAutoSetWatched)
3249  ctx->m_player->SetWatched();
3250  }
3251  ctx->UnlockDeletePlayer(__FILE__, line);
3252 }
3253 
3254 void TV::SetExitPlayer(bool set_it, bool wants_to)
3255 {
3256  QMutexLocker locker(&m_timerIdLock);
3257  if (set_it)
3258  {
3259  m_wantsToQuit = wants_to;
3260  if (!m_exitPlayerTimerId)
3261  m_exitPlayerTimerId = StartTimer(1, __LINE__);
3262  }
3263  else
3264  {
3265  if (m_exitPlayerTimerId)
3266  KillTimer(m_exitPlayerTimerId);
3267  m_exitPlayerTimerId = 0;
3268  m_wantsToQuit = wants_to;
3269  }
3270 }
3271 
3272 void TV::SetUpdateOSDPosition(bool set_it)
3273 {
3274  QMutexLocker locker(&m_timerIdLock);
3275  if (set_it)
3276  {
3277  if (!m_updateOSDPosTimerId)
3278  m_updateOSDPosTimerId = StartTimer(500, __LINE__);
3279  }
3280  else
3281  {
3282  if (m_updateOSDPosTimerId)
3283  KillTimer(m_updateOSDPosTimerId);
3284  m_updateOSDPosTimerId = 0;
3285  }
3286 }
3287 
3289 {
3290  {
3291  QMutexLocker locker(&m_timerIdLock);
3292  if (m_endOfPlaybackTimerId)
3293  KillTimer(m_endOfPlaybackTimerId);
3294  m_endOfPlaybackTimerId = 0;
3295  }
3296 
3297  bool is_playing = false;
3298  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3299  for (uint i = 0; mctx && (i < m_player.size()); i++)
3300  {
3301  PlayerContext *ctx = GetPlayer(mctx, i);
3302  if (!StateIsPlaying(ctx->GetState()))
3303  continue;
3304 
3305  if (ctx->IsPlayerPlaying())
3306  {
3307  is_playing = true;
3308  continue;
3309  }
3310 
3311  // If the end of playback is destined to pop up the end of
3312  // recording delete prompt, then don't exit the player here.
3313  if (ctx->GetState() == kState_WatchingPreRecorded &&
3314  m_dbEndOfRecExitPrompt && !m_inPlaylist && !m_underNetworkControl)
3315  continue;
3316 
3317  ForceNextStateNone(ctx);
3318  if (mctx == ctx)
3319  {
3320  m_endOfRecording = true;
3321  PrepareToExitPlayer(mctx, __LINE__);
3322  SetExitPlayer(true, true);
3323  }
3324  }
3325  ReturnPlayerLock(mctx);
3326 
3327  if (is_playing)
3328  {
3329  QMutexLocker locker(&m_timerIdLock);
3330  m_endOfPlaybackTimerId =
3331  StartTimer(kEndOfPlaybackCheckFrequency, __LINE__);
3332  }
3333 }
3334 
3336 {
3337  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3338  if (!StateIsLiveTV(GetState(actx)))
3339  {
3340  actx->LockDeletePlayer(__FILE__, __LINE__);
3341  bool toggle = actx->m_player && actx->m_player->IsEmbedding() &&
3342  actx->m_player->IsNearEnd() && !actx->m_player->IsPaused();
3343  actx->UnlockDeletePlayer(__FILE__, __LINE__);
3344  if (toggle)
3345  DoTogglePause(actx, true);
3346  }
3347  ReturnPlayerLock(actx);
3348 }
3349 
3351 {
3352  if (m_endOfRecording || m_inPlaylist || m_editMode || m_underNetworkControl ||
3353  m_exitPlayerTimerId)
3354  {
3355  return;
3356  }
3357 
3358  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3359  OSD *osd = GetOSDLock(mctx);
3360  if (osd && osd->DialogVisible())
3361  {
3362  ReturnOSDLock(mctx, osd);
3363  ReturnPlayerLock(mctx);
3364  return;
3365  }
3366  ReturnOSDLock(mctx, osd);
3367 
3368  bool do_prompt;
3369  mctx->LockDeletePlayer(__FILE__, __LINE__);
3370  do_prompt = (mctx->GetState() == kState_WatchingPreRecorded &&
3371  mctx->m_player &&
3372  !mctx->m_player->IsEmbedding() &&
3373  !mctx->m_player->IsPlaying());
3374  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
3375 
3376  if (do_prompt)
3377  ShowOSDPromptDeleteRecording(mctx, tr("End Of Recording"));
3378 
3379  ReturnPlayerLock(mctx);
3380 }
3381 
3383 {
3384  {
3385  QMutexLocker locker(&m_timerIdLock);
3386  if (m_videoExitDialogTimerId)
3387  KillTimer(m_videoExitDialogTimerId);
3388  m_videoExitDialogTimerId = 0;
3389  }
3390 
3391  // disable dialog and exit playback after timeout
3392  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3393  OSD *osd = GetOSDLock(mctx);
3394  if (!osd || !osd->DialogVisible(OSD_DLG_VIDEOEXIT))
3395  {
3396  ReturnOSDLock(mctx, osd);
3397  ReturnPlayerLock(mctx);
3398  return;
3399  }
3400  if (osd)
3401  osd->DialogQuit();
3402  ReturnOSDLock(mctx, osd);
3403  DoTogglePause(mctx, true);
3404  ClearOSD(mctx);
3405  PrepareToExitPlayer(mctx, __LINE__);
3406  ReturnPlayerLock(mctx);
3407 
3408  m_requestDelete = false;
3409  SetExitPlayer(true, true);
3410 }
3411 
3413 {
3414  {
3415  QMutexLocker locker(&m_timerIdLock);
3416  KillTimer(m_pseudoChangeChanTimerId);
3417  m_pseudoChangeChanTimerId = 0;
3418  }
3419 
3420  bool restartTimer = false;
3421  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3422  for (uint i = 0; mctx && (i < m_player.size()); i++)
3423  {
3424  PlayerContext *ctx = GetPlayer(mctx, i);
3426  continue;
3427 
3428  if (ctx->InStateChange())
3429  {
3430  restartTimer = true;
3431  continue;
3432  }
3433 
3434  LOG(VB_CHANNEL, LOG_INFO,
3435  QString("REC_PROGRAM -- channel change %1").arg(i));
3436 
3437  uint chanid = ctx->m_pseudoLiveTVRec->GetChanID();
3438  QString channum = ctx->m_pseudoLiveTVRec->GetChanNum();
3439  StringDeque tmp = ctx->m_prevChan;
3440 
3441  ctx->m_prevChan.clear();
3442  ChangeChannel(ctx, chanid, channum);
3443  ctx->m_prevChan = tmp;
3445  }
3446  ReturnPlayerLock(mctx);
3447 
3448  if (restartTimer)
3449  {
3450  QMutexLocker locker(&m_timerIdLock);
3451  if (!m_pseudoChangeChanTimerId)
3452  m_pseudoChangeChanTimerId = StartTimer(25, __LINE__);
3453  }
3454 }
3455 
3456 void TV::SetSpeedChangeTimer(uint when, int line)
3457 {
3458  QMutexLocker locker(&m_timerIdLock);
3459  if (m_speedChangeTimerId)
3460  KillTimer(m_speedChangeTimerId);
3461  m_speedChangeTimerId = StartTimer(when, line);
3462 }
3463 
3465 {
3466  {
3467  QMutexLocker locker(&m_timerIdLock);
3468  if (m_speedChangeTimerId)
3469  KillTimer(m_speedChangeTimerId);
3470  m_speedChangeTimerId = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
3471  }
3472 
3473  bool update_msg = false;
3474  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3475  for (uint i = 0; actx && (i < m_player.size()); i++)
3476  {
3477  PlayerContext *ctx = GetPlayer(actx, i);
3478  update_msg |= ctx->HandlePlayerSpeedChangeFFRew() && (ctx == actx);
3479  }
3480  ReturnPlayerLock(actx);
3481 
3482  actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3483  for (uint i = 0; actx && (i < m_player.size()); i++)
3484  {
3485  PlayerContext *ctx = GetPlayer(actx, i);
3486  update_msg |= ctx->HandlePlayerSpeedChangeEOF() && (ctx == actx);
3487  }
3488 
3489  if (actx && update_msg)
3490  {
3491  UpdateOSDSeekMessage(actx, actx->GetPlayMessage(), kOSDTimeout_Med);
3492  }
3493  ReturnPlayerLock(actx);
3494 }
3495 
3497 bool TV::eventFilter(QObject *o, QEvent *e)
3498 {
3499  // We want to intercept all resize events sent to the main window
3500  if ((e->type() == QEvent::Resize))
3501  return (GetMythMainWindow() != o) ? false : event(e);
3502 
3503  // Intercept keypress events unless they need to be handled by a main UI
3504  // screen (e.g. GuideGrid, ProgramFinder)
3505 
3506  if ( (QEvent::KeyPress == e->type() || QEvent::KeyRelease == e->type())
3507  && m_ignoreKeyPresses )
3508  return false;
3509 
3510  QScopedPointer<QEvent> sNewEvent(nullptr);
3511  if (GetMythMainWindow()->keyLongPressFilter(&e, sNewEvent))
3512  return true;
3513 
3514  if (QEvent::KeyPress == e->type())
3515  return event(e);
3516 
3517  if (MythGestureEvent::kEventType == e->type())
3518  return m_ignoreKeyPresses ? false : event(e);
3519 
3520  if (e->type() == MythEvent::MythEventMessage ||
3521  e->type() == MythEvent::MythUserMessage ||
3523  e->type() == MythMediaEvent::kEventType)
3524  {
3525  customEvent(e);
3526  return true;
3527  }
3528 
3529  switch (e->type())
3530  {
3531  case QEvent::Paint:
3532  case QEvent::UpdateRequest:
3533  case QEvent::Enter:
3534  {
3535  event(e);
3536  return false;
3537  }
3538  default:
3539  return false;
3540  }
3541 }
3542 
3544 bool TV::event(QEvent *e)
3545 {
3546  if (QEvent::Resize == e->type())
3547  {
3548  PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
3549  mctx->LockDeletePlayer(__FILE__, __LINE__);
3550  const auto *qre = dynamic_cast<const QResizeEvent*>(e);
3551  if (qre && mctx->m_player)
3552  mctx->m_player->WindowResized(qre->size());
3553  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
3554  ReturnPlayerLock(mctx);
3555  return false;
3556  }
3557 
3558  if (QEvent::KeyPress == e->type() ||
3559  MythGestureEvent::kEventType == e->type())
3560  {
3561 #if DEBUG_ACTIONS
3562  if (QEvent::KeyPress == e->type())
3563  {
3564  LOG(VB_GENERAL, LOG_INFO, LOC + QString("keypress: %1 '%2'")
3565  .arg(((QKeyEvent*)e)->key())
3566  .arg(((QKeyEvent*)e)->text()));
3567  }
3568  else
3569  {
3570  LOG(VB_GENERAL, LOG_INFO, LOC + QString("mythgesture: g:%1 pos:%2,%3 b:%4")
3571  .arg(((MythGestureEvent*)e)->gesture())
3572  .arg(((MythGestureEvent*)e)->GetPosition().x())
3573  .arg(((MythGestureEvent*)e)->GetPosition().y())
3574  .arg(((MythGestureEvent*)e)->GetButton())
3575  );
3576  }
3577 #endif // DEBUG_ACTIONS
3578  bool handled = false;
3579  PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
3580  if (actx->HasPlayer())
3581  handled = ProcessKeypressOrGesture(actx, e);
3582  ReturnPlayerLock(actx);
3583  if (handled)
3584  return true;
3585  }
3586 
3587  switch (e->type())
3588  {
3589  case QEvent::Paint:
3590  case QEvent::UpdateRequest:
3591  case QEvent::Enter:
3592  return true;
3593  default:
3594  break;
3595  }
3596 
3597  return QObject::event(e);
3598 }
3599 
3600 bool TV::HandleTrackAction(PlayerContext *ctx, const QString &action)
3601 {
3602  ctx->LockDeletePlayer(__FILE__, __LINE__);
3603  if (!ctx->m_player)
3604  {
3605  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
3606  return false;
3607  }
3608 
3609  bool handled = true;
3610 
3613  else if (ACTION_ENABLEEXTTEXT == action)
3615  else if (ACTION_DISABLEEXTTEXT == action)
3617  else if (ACTION_ENABLEFORCEDSUBS == action)
3618  ctx->m_player->SetAllowForcedSubtitles(true);
3619  else if (ACTION_DISABLEFORCEDSUBS == action)
3620  ctx->m_player->SetAllowForcedSubtitles(false);
3621  else if (action == ACTION_ENABLESUBS)
3622  ctx->m_player->SetCaptionsEnabled(true, true);
3623  else if (action == ACTION_DISABLESUBS)
3624  ctx->m_player->SetCaptionsEnabled(false, true);
3625  else if (action == ACTION_TOGGLESUBS && !m_browseHelper->IsBrowsing())
3626  {
3627  if (m_ccInputMode)
3628  {
3629  bool valid = false;
3630  int page = GetQueuedInputAsInt(&valid, 16);
3631  if (m_vbimode == VBIMode::PAL_TT && valid)
3632  ctx->m_player->SetTeletextPage(page);
3633  else if (m_vbimode == VBIMode::NTSC_CC)
3635  max(min(page - 1, 1), 0));
3636 
3637  ClearInputQueues(ctx, true);
3638 
3639  QMutexLocker locker(&m_timerIdLock);
3640  m_ccInputMode = false;
3641  if (m_ccInputTimerId)
3642  {
3643  KillTimer(m_ccInputTimerId);
3644  m_ccInputTimerId = 0;
3645  }
3646  }
3648  {
3649  ClearInputQueues(ctx, false);
3650  AddKeyToInputQueue(ctx, 0);
3651 
3652  QMutexLocker locker(&m_timerIdLock);
3653  m_ccInputMode = true;
3654  m_asInputMode = false;
3655  m_ccInputTimerId = StartTimer(kInputModeTimeout, __LINE__);
3656  if (m_asInputTimerId)
3657  {
3658  KillTimer(m_asInputTimerId);
3659  m_asInputTimerId = 0;
3660  }
3661  }
3662  else
3663  {
3664  ctx->m_player->ToggleCaptions();
3665  }
3666  }
3667  else if (action.startsWith("TOGGLE"))
3668  {
3669  int type = to_track_type(action.mid(6));
3671  ctx->m_player->EnableTeletext();
3672  else if (type >= kTrackTypeSubtitle)
3673  ctx->m_player->ToggleCaptions(type);
3674  else
3675  handled = false;
3676  }
3677  else if (action.startsWith("SELECT"))
3678  {
3679  int type = to_track_type(action.mid(6));
3680  int num = action.section("_", -1).toInt();
3681  if (type >= kTrackTypeAudio)
3682  ctx->m_player->SetTrack(type, num);
3683  else
3684  handled = false;
3685  }
3686  else if (action.startsWith("NEXT") || action.startsWith("PREV"))
3687  {
3688  int dir = (action.startsWith("NEXT")) ? +1 : -1;
3689  int type = to_track_type(action.mid(4));
3690  if (type >= kTrackTypeAudio)
3691  ctx->m_player->ChangeTrack(type, dir);
3692  else if (action.endsWith("CC"))
3693  ctx->m_player->ChangeCaptionTrack(dir);
3694  else
3695  handled = false;
3696  }
3697  else
3698  handled = false;
3699 
3700  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
3701 
3702  return handled;
3703 }
3704 
3705 static bool has_action(const QString& action, const QStringList &actions)
3706 {
3707  QStringList::const_iterator it;
3708  for (it = actions.begin(); it != actions.end(); ++it)
3709  {
3710  if (action == *it)
3711  return true;
3712  }
3713  return false;
3714 }
3715 
3716 // Make a special check for global system-related events.
3717 //
3718 // This check needs to be done early in the keypress event processing,
3719 // because FF/REW processing causes unknown events to stop FF/REW, and
3720 // manual zoom mode processing consumes all but a few event types.
3721 // Ideally, we would just call MythScreenType::keyPressEvent()
3722 // unconditionally, but we only want certain keypresses handled by
3723 // that method.
3724 //
3725 // As a result, some of the MythScreenType::keyPressEvent() string
3726 // compare logic is copied here.
3727 static bool SysEventHandleAction(QKeyEvent *e, const QStringList &actions)
3728 {
3729  QStringList::const_iterator it;
3730  for (it = actions.begin(); it != actions.end(); ++it)
3731  {
3732  if ((*it).startsWith("SYSEVENT") ||
3733  *it == ACTION_SCREENSHOT ||
3734  *it == ACTION_TVPOWERON ||
3735  *it == ACTION_TVPOWEROFF)
3736  {
3738  keyPressEvent(e);
3739  }
3740  }
3741  return false;
3742 }
3743 
3744 QList<QKeyEvent> TV::ConvertScreenPressKeyMap(const QString &keyList)
3745 {
3746  QList<QKeyEvent> keyPressList;
3747  int i = 0;
3748  QStringList stringKeyList = keyList.split(',');
3749  QStringList::const_iterator it;
3750  for (it = stringKeyList.begin(); it != stringKeyList.end(); ++it)
3751  {
3752  QKeySequence keySequence(*it);
3753  for(i = 0; i < keySequence.count(); i++)
3754  {
3755  unsigned int keynum = keySequence[i];
3756  QKeyEvent keyEvent(QEvent::None,
3757  (int)(keynum & ~Qt::KeyboardModifierMask),
3758  (Qt::KeyboardModifiers)(keynum & Qt::KeyboardModifierMask));
3759  keyPressList.append(keyEvent);
3760  }
3761  }
3762  if (stringKeyList.count() < s_screenPressRegionCount)
3763  {
3764  // add default remainders
3765  for(; i < s_screenPressRegionCount; i++)
3766  {
3767  QKeyEvent keyEvent(QEvent::None, Qt::Key_Escape, Qt::NoModifier);
3768  keyPressList.append(keyEvent);
3769  }
3770  }
3771  return keyPressList;
3772 }
3773 
3774 bool TV::TranslateGesture(const QString &context, MythGestureEvent *e,
3775  QStringList &actions, bool isLiveTV)
3776 {
3777  if (context == "TV Playback")
3778  {
3779  // TODO make this configuable via a similar mechanism to
3780  // TranslateKeyPress
3781  // possibly with configurable hot zones of various sizes in a theme
3782  // TODO enhance gestures to support other non Click types too
3783  if ((e->gesture() == MythGestureEvent::Click) &&
3785  {
3786  // divide screen into 12 regions
3787  QSize size = GetMythMainWindow()->size();
3788  QPoint pos = e->GetPosition();
3789  int region = 0;
3790  const int widthDivider = 4;
3791  int w4 = size.width() / widthDivider;
3792  region = pos.x() / w4;
3793  int h3 = size.height() / 3;
3794  region += (pos.y() / h3) * widthDivider;
3795 
3796  if (isLiveTV)
3797  {
3799  context, &(m_screenPressKeyMapLiveTV[region]), actions, true);
3800  }
3802  context, &(m_screenPressKeyMapPlayback[region]), actions, true);
3803  }
3804  return false;
3805  }
3806  return false;
3807 }
3808 
3809 bool TV::TranslateKeyPressOrGesture(const QString &context,
3810  QEvent *e, QStringList &actions,
3811  bool isLiveTV, bool allowJumps)
3812 {
3813  if (QEvent::KeyPress == e->type())
3814  {
3816  context, dynamic_cast<QKeyEvent*>(e), actions, allowJumps);
3817  }
3818  if (MythGestureEvent::kEventType == e->type())
3819  {
3820  return TranslateGesture(context, dynamic_cast<MythGestureEvent*>(e), actions, isLiveTV);
3821  }
3822 
3823  return false;
3824 }
3825 
3827 {
3828  bool ignoreKeys = actx->IsPlayerChangingBuffers();
3829 #if DEBUG_ACTIONS
3830  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ignoreKeys: %1")
3831  .arg(ignoreKeys));
3832 #endif // DEBUG_ACTIONS
3833 
3834  if (m_idleTimerId)
3835  {
3836  KillTimer(m_idleTimerId);
3837  m_idleTimerId = StartTimer(m_dbIdleTimeout, __LINE__);
3838  }
3839 
3840 #ifdef Q_OS_LINUX
3841  // Fixups for _some_ linux native codes that QT doesn't know
3842  auto* eKeyEvent = dynamic_cast<QKeyEvent*>(e);
3843  if (eKeyEvent) {
3844  if (eKeyEvent->key() <= 0)
3845  {
3846  int keycode = 0;
3847  switch(eKeyEvent->nativeScanCode())
3848  {
3849  case 209: // XF86AudioPause
3850  keycode = Qt::Key_MediaPause;
3851  break;
3852  default:
3853  break;
3854  }
3855 
3856  if (keycode > 0)
3857  {
3858  auto *key = new QKeyEvent(QEvent::KeyPress, keycode,
3859  eKeyEvent->modifiers());
3860  QCoreApplication::postEvent(this, key);
3861  }
3862  }
3863  }
3864 #endif
3865 
3866  QStringList actions;
3867  bool handled = false;
3868  bool alreadyTranslatedPlayback = false;
3869 
3870  TVState state = GetState(actx);
3871  bool isLiveTV = StateIsLiveTV(state);
3872 
3873  if (ignoreKeys)
3874  {
3875  handled = TranslateKeyPressOrGesture("TV Playback", e, actions, isLiveTV);
3876  alreadyTranslatedPlayback = true;
3877 
3878  if (handled || actions.isEmpty())
3879  return handled;
3880 
3881  bool esc = has_action("ESCAPE", actions) ||
3882  has_action("BACK", actions);
3883  bool pause = has_action(ACTION_PAUSE, actions);
3884  bool play = has_action(ACTION_PLAY, actions);
3885 
3886  if ((!esc || m_browseHelper->IsBrowsing()) && !pause && !play)
3887  return false;
3888  }
3889 
3890  OSD *osd = GetOSDLock(actx);
3891  if (osd && osd->DialogVisible())
3892  {
3893  if (QEvent::KeyPress == e->type())
3894  {
3895  auto *qke = dynamic_cast<QKeyEvent*>(e);
3896  handled = (qke != nullptr) && osd->DialogHandleKeypress(qke);
3897  }
3898  if (MythGestureEvent::kEventType == e->type())
3899  {
3900  auto *mge = dynamic_cast<MythGestureEvent*>(e);
3901  handled = (mge != nullptr) && osd->DialogHandleGesture(mge);
3902  }
3903  }
3904  ReturnOSDLock(actx, osd);
3905 
3906  if (m_editMode && !handled)
3907  {
3908  handled |= TranslateKeyPressOrGesture(
3909  "TV Editing", e, actions, isLiveTV);
3910 
3911  if (!handled && actx->m_player)
3912  {
3913  if (has_action("MENU", actions))
3914  {
3915  ShowOSDCutpoint(actx, "EDIT_CUT_POINTS");
3916  handled = true;
3917  }
3918  if (has_action(ACTION_MENUCOMPACT, actions))
3919  {
3920  ShowOSDCutpoint(actx, "EDIT_CUT_POINTS_COMPACT");
3921  handled = true;
3922  }
3923  if (has_action("ESCAPE", actions))
3924  {
3925  if (!actx->m_player->IsCutListSaved())
3926  ShowOSDCutpoint(actx, "EXIT_EDIT_MODE");
3927  else
3928  {
3929  actx->LockDeletePlayer(__FILE__, __LINE__);
3930  actx->m_player->DisableEdit(0);
3931  actx->UnlockDeletePlayer(__FILE__, __LINE__);
3932  }
3933  handled = true;
3934  }
3935  else
3936  {
3937  actx->LockDeletePlayer(__FILE__, __LINE__);
3938  int64_t current_frame = actx->m_player->GetFramesPlayed();
3939  actx->UnlockDeletePlayer(__FILE__, __LINE__);
3940  if ((has_action(ACTION_SELECT, actions)) &&
3941  (actx->m_player->IsInDelete(current_frame)) &&
3942  (!(actx->m_player->HasTemporaryMark())))
3943  {
3944  ShowOSDCutpoint(actx, "EDIT_CUT_POINTS");
3945  handled = true;
3946  }
3947  else
3948  handled |=
3949  actx->m_player->HandleProgramEditorActions(actions);
3950  }
3951  }
3952  if (handled)
3953  m_editMode = (actx->m_player && actx->m_player->GetEditMode());
3954  }
3955 
3956  if (handled)
3957  return true;
3958 
3959  // If text is already queued up, be more lax on what is ok.
3960  // This allows hex teletext entry and minor channel entry.
3961  if (QEvent::KeyPress == e->type())
3962  {
3963  auto *qke = dynamic_cast<QKeyEvent*>(e);
3964  if (qke == nullptr)
3965  return false;
3966  const QString txt = qke->text();
3967  if (HasQueuedInput() && (1 == txt.length()))
3968  {
3969  bool ok = false;
3970  (void)txt.toInt(&ok, 16);
3971  if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
3972  {
3973  AddKeyToInputQueue(actx, txt.at(0).toLatin1());
3974  return true;
3975  }
3976  }
3977  }
3978 
3979  // Teletext menu
3980  actx->LockDeletePlayer(__FILE__, __LINE__);
3981  if (actx->m_player && (actx->m_player->GetCaptionMode() == kDisplayTeletextMenu))
3982  {
3983  QStringList tt_actions;
3984  handled = TranslateKeyPressOrGesture(
3985  "Teletext Menu", e, tt_actions, isLiveTV);
3986 
3987  if (!handled && !tt_actions.isEmpty())
3988  {
3989  for (int i = 0; i < tt_actions.size(); i++)
3990  {
3991  if (actx->m_player->HandleTeletextAction(tt_actions[i]))
3992  {
3993  actx->UnlockDeletePlayer(__FILE__, __LINE__);
3994  return true;
3995  }
3996  }
3997  }
3998  }
3999 
4000  // Interactive television
4001  if (actx->m_player && actx->m_player->GetInteractiveTV())
4002  {
4003  if (!alreadyTranslatedPlayback)
4004  {
4005  handled = TranslateKeyPressOrGesture(
4006  "TV Playback", e, actions, isLiveTV);
4007  alreadyTranslatedPlayback = true;
4008  }
4009  if (!handled && !actions.isEmpty())
4010  {
4011  for (int i = 0; i < actions.size(); i++)
4012  {
4013  if (actx->m_player->ITVHandleAction(actions[i]))
4014  {
4015  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4016  return true;
4017  }
4018  }
4019  }
4020  }
4021  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4022 
4023  if (!alreadyTranslatedPlayback)
4024  {
4025  handled = TranslateKeyPressOrGesture(
4026  "TV Playback", e, actions, isLiveTV);
4027  }
4028  if (handled || actions.isEmpty())
4029  return handled;
4030 
4031  handled = false;
4032 
4033  bool isDVD = actx->m_buffer && actx->m_buffer->IsDVD();
4034  bool isMenuOrStill = actx->m_buffer && actx->m_buffer->IsInDiscMenuOrStillFrame();
4035 
4036  if (QEvent::KeyPress == e->type())
4037  {
4038  handled = handled || SysEventHandleAction(dynamic_cast<QKeyEvent*>(e), actions);
4039  }
4040  handled = handled || BrowseHandleAction(actx, actions);
4041  handled = handled || ManualZoomHandleAction(actx, actions);
4042  handled = handled || PictureAttributeHandleAction(actx, actions);
4043  handled = handled || TimeStretchHandleAction(actx, actions);
4044  handled = handled || AudioSyncHandleAction(actx, actions);
4045  handled = handled || SubtitleZoomHandleAction(actx, actions);
4046  handled = handled || SubtitleDelayHandleAction(actx, actions);
4047  handled = handled || DiscMenuHandleAction(actx, actions);
4048  handled = handled || ActiveHandleAction(
4049  actx, actions, isDVD, isMenuOrStill);
4050  handled = handled || ToggleHandleAction(actx, actions, isDVD);
4051  handled = handled || PxPHandleAction(actx, actions);
4052  handled = handled || FFRewHandleAction(actx, actions);
4053  handled = handled || ActivePostQHandleAction(actx, actions);
4054 
4055 #if DEBUG_ACTIONS
4056  for (int i = 0; i < actions.size(); ++i)
4057  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("handled(%1) actions[%2](%3)")
4058  .arg(handled).arg(i).arg(actions[i]));
4059 #endif // DEBUG_ACTIONS
4060 
4061  if (handled)
4062  return true;
4063 
4064  if (!handled)
4065  {
4066  for (int i = 0; i < actions.size() && !handled; i++)
4067  {
4068  QString action = actions[i];
4069  bool ok = false;
4070  int val = action.toInt(&ok);
4071 
4072  if (ok)
4073  {
4074  AddKeyToInputQueue(actx, '0' + val);
4075  handled = true;
4076  }
4077  }
4078  }
4079 
4080  return true;
4081 }
4082 
4083 bool TV::BrowseHandleAction(PlayerContext *ctx, const QStringList &actions)
4084 {
4085  if (!m_browseHelper->IsBrowsing())
4086  return false;
4087 
4088  bool handled = true;
4089 
4090  if (has_action(ACTION_UP, actions) || has_action(ACTION_CHANNELUP, actions))
4091  m_browseHelper->BrowseDispInfo(ctx, BROWSE_UP);
4092  else if (has_action(ACTION_DOWN, actions) || has_action(ACTION_CHANNELDOWN, actions))
4093  m_browseHelper->BrowseDispInfo(ctx, BROWSE_DOWN);
4094  else if (has_action(ACTION_LEFT, actions))
4095  m_browseHelper->BrowseDispInfo(ctx, BROWSE_LEFT);
4096  else if (has_action(ACTION_RIGHT, actions))
4097  m_browseHelper->BrowseDispInfo(ctx, BROWSE_RIGHT);
4098  else if (has_action("NEXTFAV", actions))
4099  m_browseHelper->BrowseDispInfo(ctx, BROWSE_FAVORITE);
4100  else if (has_action(ACTION_SELECT, actions))
4101  {
4102  m_browseHelper->BrowseEnd(ctx, true);
4103  }
4104  else if (has_action(ACTION_CLEAROSD, actions) ||
4105  has_action("ESCAPE", actions) ||
4106  has_action("BACK", actions) ||
4107  has_action("TOGGLEBROWSE", actions))
4108  {
4109  m_browseHelper->BrowseEnd(ctx, false);
4110  }
4111  else if (has_action(ACTION_TOGGLERECORD, actions))
4112  QuickRecord(ctx);
4113  else
4114  {
4115  handled = false;
4116  QStringList::const_iterator it = actions.begin();
4117  for (; it != actions.end(); ++it)
4118  {
4119  if ((*it).length() == 1 && (*it)[0].isDigit())
4120  {
4121  AddKeyToInputQueue(ctx, (*it)[0].toLatin1());
4122  handled = true;
4123  }
4124  }
4125  }
4126 
4127  // only pass-through actions listed below
4128  return handled ||
4129  !(has_action(ACTION_VOLUMEDOWN, actions) ||
4130  has_action(ACTION_VOLUMEUP, actions) ||
4131  has_action("STRETCHINC", actions) ||
4132  has_action("STRETCHDEC", actions) ||
4133  has_action(ACTION_MUTEAUDIO, actions) ||
4134  has_action("CYCLEAUDIOCHAN", actions) ||
4135  has_action("BOTTOMLINEMOVE", actions) ||
4136  has_action("BOTTOMLINESAVE", actions) ||
4137  has_action("TOGGLEASPECT", actions) ||
4138  has_action("TOGGLEPIPMODE", actions) ||
4139  has_action("TOGGLEPIPSTATE", actions) ||
4140  has_action("NEXTPIPWINDOW", actions) ||
4141  has_action("CREATEPIPVIEW", actions) ||
4142  has_action("CREATEPBPVIEW", actions) ||
4143  has_action("SWAPPIP", actions));
4144 }
4145 
4146 bool TV::ManualZoomHandleAction(PlayerContext *actx, const QStringList &actions)
4147 {
4148  if (!m_zoomMode)
4149  return false;
4150 
4151  actx->LockDeletePlayer(__FILE__, __LINE__);
4152  if (!actx->m_player)
4153  {
4154  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4155  return false;
4156  }
4157 
4158  bool end_manual_zoom = false;
4159  bool handled = true;
4160  bool updateOSD = true;
4161  ZoomDirection zoom = kZoom_END;
4162  if (has_action(ACTION_ZOOMUP, actions) ||
4163  has_action(ACTION_UP, actions) ||
4164  has_action(ACTION_CHANNELUP, actions))
4165  {
4166  zoom = kZoomUp;
4167  }
4168  else if (has_action(ACTION_ZOOMDOWN, actions) ||
4169  has_action(ACTION_DOWN, actions) ||
4170  has_action(ACTION_CHANNELDOWN, actions))
4171  {
4172  zoom = kZoomDown;
4173  }
4174  else if (has_action(ACTION_ZOOMLEFT, actions) ||
4175  has_action(ACTION_LEFT, actions))
4176  zoom = kZoomLeft;
4177  else if (has_action(ACTION_ZOOMRIGHT, actions) ||
4178  has_action(ACTION_RIGHT, actions))
4179  zoom = kZoomRight;
4180  else if (has_action(ACTION_ZOOMASPECTUP, actions) ||
4181  has_action(ACTION_VOLUMEUP, actions))
4182  zoom = kZoomAspectUp;
4183  else if (has_action(ACTION_ZOOMASPECTDOWN, actions) ||
4184  has_action(ACTION_VOLUMEDOWN, actions))
4185  zoom = kZoomAspectDown;
4186  else if (has_action(ACTION_ZOOMIN, actions) ||
4187  has_action(ACTION_JUMPFFWD, actions))
4188  zoom = kZoomIn;
4189  else if (has_action(ACTION_ZOOMOUT, actions) ||
4190  has_action(ACTION_JUMPRWND, actions))
4191  zoom = kZoomOut;
4192  else if (has_action(ACTION_ZOOMVERTICALIN, actions))
4193  zoom = kZoomVerticalIn;
4194  else if (has_action(ACTION_ZOOMVERTICALOUT, actions))
4195  zoom = kZoomVerticalOut;
4196  else if (has_action(ACTION_ZOOMHORIZONTALIN, actions))
4197  zoom = kZoomHorizontalIn;
4198  else if (has_action(ACTION_ZOOMHORIZONTALOUT, actions))
4199  zoom = kZoomHorizontalOut;
4200  else if (has_action(ACTION_ZOOMQUIT, actions) ||
4201  has_action("ESCAPE", actions) ||
4202  has_action("BACK", actions))
4203  {
4204  zoom = kZoomHome;
4205  end_manual_zoom = true;
4206  }
4207  else if (has_action(ACTION_ZOOMCOMMIT, actions) ||
4208  has_action(ACTION_SELECT, actions))
4209  {
4210  end_manual_zoom = true;
4211  SetManualZoom(actx, false, tr("Zoom Committed"));
4212  }
4213  else
4214  {
4215  updateOSD = false;
4216  // only pass-through actions listed below
4217  handled = !(has_action("STRETCHINC", actions) ||
4218  has_action("STRETCHDEC", actions) ||
4219  has_action(ACTION_MUTEAUDIO, actions) ||
4220  has_action("CYCLEAUDIOCHAN", actions) ||
4221  has_action(ACTION_PAUSE, actions) ||
4222  has_action(ACTION_CLEAROSD, actions));
4223  }
4224  QString msg = tr("Zoom Committed");
4225  if (zoom != kZoom_END)
4226  {
4227  actx->m_player->Zoom(zoom);
4228  if (end_manual_zoom)
4229  msg = tr("Zoom Ignored");
4230  else
4231  msg = actx->m_player->GetVideoOutput()->GetZoomString();
4232  }
4233  else if (end_manual_zoom)
4234  msg = tr("%1 Committed")
4235  .arg(actx->m_player->GetVideoOutput()->GetZoomString());
4236  actx->UnlockDeletePlayer(__FILE__, __LINE__);
4237 
4238  if (updateOSD)
4239  SetManualZoom(actx, !end_manual_zoom, msg);
4240 
4241  return handled;
4242 }
4243 
4245  const QStringList &actions)
4246 {
4247  if (!m_adjustingPicture)
4248  return false;
4249 
4250  bool handled = true;
4251  if (has_action(ACTION_LEFT, actions))
4252  {
4253  DoChangePictureAttribute(ctx, m_adjustingPicture,
4254  m_adjustingPictureAttribute, false);
4255  }
4256  else if (has_action(ACTION_RIGHT, actions))
4257  {
4258  DoChangePictureAttribute(ctx, m_adjustingPicture,
4259  m_adjustingPictureAttribute, true);
4260  }
4261  else
4262  handled = false;
4263 
4264  return handled;
4265 }
4266 
4268  const QStringList &actions)
4269 {
4270  if (!m_stretchAdjustment)
4271  return false;
4272 
4273  bool handled = true;
4274 
4275  if (has_action(ACTION_LEFT, actions))
4276  ChangeTimeStretch(ctx, -1);
4277  else if (has_action(ACTION_RIGHT, actions))
4278  ChangeTimeStretch(ctx, 1);
4279  else if (has_action(ACTION_DOWN, actions))
4280  ChangeTimeStretch(ctx, -5);
4281  else if (has_action(ACTION_UP, actions))
4282  ChangeTimeStretch(ctx, 5);
4283  else if (has_action("ADJUSTSTRETCH", actions))
4284  ToggleTimeStretch(ctx);
4285  else if (has_action(ACTION_SELECT, actions))
4286  ClearOSD(ctx);
4287  else
4288  handled = false;
4289 
4290  return handled;
4291 }
4292 
4294  const QStringList &actions)
4295 {
4296  if (!m_audiosyncAdjustment)
4297  return false;
4298 
4299  bool handled = true;
4300 
4301  if (has_action(ACTION_LEFT, actions))
4302  ChangeAudioSync(ctx, -1);
4303  else if (has_action(ACTION_RIGHT, actions))
4304  ChangeAudioSync(ctx, 1);
4305  else if (has_action(ACTION_UP, actions))
4306  ChangeAudioSync(ctx, 10);
4307  else if (has_action(ACTION_DOWN, actions))
4308  ChangeAudioSync(ctx, -10);
4309  else if (has_action(ACTION_TOGGELAUDIOSYNC, actions) ||
4310  has_action(ACTION_SELECT, actions))
4311  ClearOSD(ctx);
4312  else
4313  handled = false;
4314 
4315  return handled;
4316 }
4317 
4319  const QStringList &actions)
4320 {
4321  if (!m_subtitleZoomAdjustment)
4322  return false;
4323 
4324  bool handled = true;
4325 
4326  if (has_action(ACTION_LEFT, actions))
4327  ChangeSubtitleZoom(ctx, -1);
4328  else if (has_action(ACTION_RIGHT, actions))
4329  ChangeSubtitleZoom(ctx, 1);
4330  else if (has_action(ACTION_UP, actions))
4331  ChangeSubtitleZoom(ctx, 10);
4332  else if (has_action(ACTION_DOWN, actions))
4333  ChangeSubtitleZoom(ctx, -10);
4334  else if (has_action(ACTION_TOGGLESUBTITLEZOOM, actions) ||
4335  has_action(ACTION_SELECT, actions))
4336  ClearOSD(ctx);
4337  else
4338  handled = false;
4339 
4340  return handled;
4341 }
4342 
4344  const QStringList &actions)
4345 {
4346  if (!m_subtitleDelayAdjustment)
4347  return false;
4348 
4349  bool handled = true;
4350 
4351  if (has_action(ACTION_LEFT, actions))
4352  ChangeSubtitleDelay(ctx, -5);
4353  else if (has_action(ACTION_RIGHT, actions))
4354  ChangeSubtitleDelay(ctx, 5);
4355  else if (has_action(ACTION_UP, actions))
4356  ChangeSubtitleDelay(ctx, 25);
4357  else if (has_action(ACTION_DOWN, actions))
4358  ChangeSubtitleDelay(ctx, -25);
4359  else if (has_action(ACTION_TOGGLESUBTITLEDELAY, actions) ||
4360  has_action(ACTION_SELECT, actions))
4361  ClearOSD(ctx);
4362  else
4363  handled = false;
4364 
4365  return handled;
4366 }
4367 
4368 bool TV::DiscMenuHandleAction(PlayerContext *ctx, const QStringList &actions)
4369 {
4370  int64_t pts = 0;
4372  if (output)
4373  {
4374  VideoFrame *frame = output->GetLastShownFrame();
4375  if (frame)
4376  {
4377  // convert timecode (msec) to pts (90kHz)
4378  pts = (int64_t)(frame->timecode * 90);
4379  }
4380  }
4381  return ctx->m_buffer->HandleAction(actions, pts);
4382 }
4383 
4384 bool TV::Handle3D(PlayerContext *ctx, const QString &action)
4385 {
4386  ctx->LockDeletePlayer(__FILE__, __LINE__);
4387  if (ctx->m_player && ctx->m_player->GetVideoOutput() &&
4389  {
4391  if (ACTION_3DSIDEBYSIDE == action)
4393  else if (ACTION_3DSIDEBYSIDEDISCARD == action)
4395  else if (ACTION_3DTOPANDBOTTOM == action)
4400  SetOSDMessage(ctx, StereoscopictoString(mode));
4401  }
4402  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4403  return true;
4404 }
4405 
4407  const QStringList &actions,
4408  bool isDVD, bool isDVDStill)
4409 {
4410  bool handled = true;
4411 
4412  if (has_action("SKIPCOMMERCIAL", actions) && !isDVD)
4413  DoSkipCommercials(ctx, 1);
4414  else if (has_action("SKIPCOMMBACK", actions) && !isDVD)
4415  DoSkipCommercials(ctx, -1);
4416  else if (has_action("QUEUETRANSCODE", actions) && !isDVD)
4417  DoQueueTranscode(ctx, "Default");
4418  else if (has_action("QUEUETRANSCODE_AUTO", actions) && !isDVD)
4419  DoQueueTranscode(ctx, "Autodetect");
4420  else if (has_action("QUEUETRANSCODE_HIGH", actions) && !isDVD)
4421  DoQueueTranscode(ctx, "High Quality");
4422  else if (has_action("QUEUETRANSCODE_MEDIUM", actions) && !isDVD)
4423  DoQueueTranscode(ctx, "Medium Quality");
4424  else if (has_action("QUEUETRANSCODE_LOW", actions) && !isDVD)
4425  DoQueueTranscode(ctx, "Low Quality");
4426  else if (has_action(ACTION_PLAY, actions))
4427  DoPlay(ctx);
4428  else if (has_action(ACTION_PAUSE, actions))
4429  DoTogglePause(ctx, true);
4430  else if (has_action("SPEEDINC", actions) && !isDVDStill)
4431  ChangeSpeed(ctx, 1);
4432  else if (has_action("SPEEDDEC", actions) && !isDVDStill)
4433  ChangeSpeed(ctx, -1);
4434  else if (has_action("ADJUSTSTRETCH", actions))
4435  ChangeTimeStretch(ctx, 0); // just display
4436  else if (has_action("CYCLECOMMSKIPMODE",actions) && !isDVD)
4437  SetAutoCommercialSkip(ctx, kCommSkipIncr);
4438  else if (has_action("NEXTSCAN", actions))
4439  {
4440  QString msg;
4441  ctx->LockDeletePlayer(__FILE__, __LINE__);
4442  if (ctx->m_player)
4443  {
4444  ctx->m_player->NextScanType();
4445  msg = toString(ctx->m_player->GetScanType());
4446  }
4447  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4448 
4449  if (!msg.isEmpty())
4450  SetOSDMessage(ctx, msg);
4451  }
4452  else if (has_action(ACTION_SEEKARB, actions) && !isDVD)
4453  {
4454  if (m_asInputMode)
4455  {
4456  ClearInputQueues(ctx, true);
4457  SetOSDText(ctx, "osd_input", "osd_number_entry", tr("Seek:"),
4458  kOSDTimeout_Med);
4459 
4460  QMutexLocker locker(&m_timerIdLock);
4461  m_asInputMode = false;
4462  if (m_asInputTimerId)
4463  {
4464  KillTimer(m_asInputTimerId);
4465  m_asInputTimerId = 0;
4466  }
4467  }
4468  else
4469  {
4470  ClearInputQueues(ctx, false);
4471  AddKeyToInputQueue(ctx, 0);
4472 
4473  QMutexLocker locker(&m_timerIdLock);
4474  m_asInputMode = true;
4475  m_ccInputMode = false;
4476  m_asInputTimerId = StartTimer(kInputModeTimeout, __LINE__);
4477  if (m_ccInputTimerId)
4478  {
4479  KillTimer(m_ccInputTimerId);
4480  m_ccInputTimerId = 0;
4481  }
4482  }
4483  }
4484  else if (has_action(ACTION_JUMPRWND, actions))
4485  DoJumpRWND(ctx);
4486  else if (has_action(ACTION_JUMPFFWD, actions))
4487  DoJumpFFWD(ctx);
4488  else if (has_action(ACTION_JUMPBKMRK, actions))
4489  {
4490  ctx->LockDeletePlayer(__FILE__, __LINE__);
4491  uint64_t bookmark = ctx->m_player->GetBookmark();
4492  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4493 
4494  if (bookmark)
4495  {
4496  DoPlayerSeekToFrame(ctx, bookmark);
4497  ctx->LockDeletePlayer(__FILE__, __LINE__);
4498  UpdateOSDSeekMessage(ctx, tr("Jump to Bookmark"), kOSDTimeout_Med);
4499  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4500  }
4501  }
4502  else if (has_action(ACTION_JUMPSTART,actions))
4503  {
4504  DoSeek(ctx, 0, tr("Jump to Beginning"),
4505  /*timeIsOffset*/false,
4506  /*honorCutlist*/true);
4507  }
4508  else if (has_action(ACTION_CLEAROSD, actions))
4509  {
4510  ClearOSD(ctx);
4511  }
4512  else if (has_action(ACTION_VIEWSCHEDULED, actions))
4513  EditSchedule(ctx, kViewSchedule);
4514  else if (HandleJumpToProgramAction(ctx, actions))
4515  { // NOLINT(bugprone-branch-clone)
4516  }
4517  else if (has_action(ACTION_SIGNALMON, actions))
4518  {
4519  if ((GetState(ctx) == kState_WatchingLiveTV) && ctx->m_recorder)
4520  {
4521  QString input = ctx->m_recorder->GetInput();
4523 
4524  if (timeout == 0xffffffff)
4525  {
4526  SetOSDMessage(ctx, "No Signal Monitor");
4527  return false;
4528  }
4529 
4530  int rate = m_sigMonMode ? 0 : 100;
4531  bool notify = !m_sigMonMode;
4532 
4533  PauseLiveTV(ctx);
4534  ctx->m_recorder->SetSignalMonitoringRate(rate, notify);
4535  UnpauseLiveTV(ctx);
4536 
4537  m_lockTimerOn = false;
4538  m_sigMonMode = !m_sigMonMode;
4539  }
4540  }
4541  else if (has_action(ACTION_SCREENSHOT, actions))
4542  {
4544  }
4545  else if (has_action(ACTION_STOP, actions))
4546  {
4547  PrepareToExitPlayer(ctx, __LINE__);
4548  SetExitPlayer(true, true);
4549  }
4550  else if (has_action(ACTION_EXITSHOWNOPROMPTS, actions))
4551  {
4552  m_requestDelete = false;
4553  PrepareToExitPlayer(ctx, __LINE__);
4554  SetExitPlayer(true, true);
4555  }
4556  else if (has_action("ESCAPE", actions) ||
4557  has_action("BACK", actions))
4558  {
4559  if (StateIsLiveTV(ctx->GetState()) &&
4560  (ctx->m_lastSignalMsgTime.elapsed() <
4562  {
4563  ClearOSD(ctx);
4564  }
4565  else
4566  {
4567  OSD *osd = GetOSDLock(ctx);
4568  if (osd && osd->IsVisible())
4569  {
4570  ClearOSD(ctx);
4571  ReturnOSDLock(ctx, osd);
4572  return handled;
4573  }
4574  ReturnOSDLock(ctx, osd);
4575  }
4576 
4577  NormalSpeed(ctx);
4578 
4579  StopFFRew(ctx);
4580 
4581  bool do_exit = false;
4582 
4583  if (StateIsLiveTV(GetState(ctx)))
4584  {
4585  if (ctx->HasPlayer() && (12 & m_dbPlaybackExitPrompt))
4586  {
4587  ShowOSDStopWatchingRecording(ctx);
4588  return handled;
4589  }
4590  do_exit = true;
4591  }
4592  else
4593  {
4594  if (ctx->HasPlayer() && (5 & m_dbPlaybackExitPrompt) &&
4595  !m_underNetworkControl && !isDVDStill)
4596  {
4597  ShowOSDStopWatchingRecording(ctx);
4598  return handled;
4599  }
4600  PrepareToExitPlayer(ctx, __LINE__);
4601  m_requestDelete = false;
4602  do_exit = true;
4603  }
4604 
4605  if (do_exit)
4606  {
4607  PlayerContext *mctx = GetPlayer(ctx, 0);
4608  if (mctx != ctx)
4609  { // A PIP is active, just tear it down..
4610  PxPTeardownView(ctx);
4611  return handled;
4612  }
4613 
4614  // If it's a DVD, and we're not trying to execute a
4615  // jumppoint, try to back up.
4616  if (isDVD &&
4617  !GetMythMainWindow()->IsExitingToMain() &&
4618  has_action("BACK", actions) &&
4619  ctx->m_buffer && ctx->m_buffer->DVD()->GoBack())
4620  {
4621  return handled;
4622  }
4623  SetExitPlayer(true, true);
4624  }
4625 
4626  SetActive(ctx, 0, false);
4627  }
4628  else if (has_action(ACTION_ENABLEUPMIX, actions))
4629  EnableUpmix(ctx, true);
4630  else if (has_action(ACTION_DISABLEUPMIX, actions))
4631  EnableUpmix(ctx, false);
4632  else if (has_action(ACTION_VOLUMEDOWN, actions))
4633  ChangeVolume(ctx, false);
4634  else if (has_action(ACTION_VOLUMEUP, actions))
4635  ChangeVolume(ctx, true);
4636  else if (has_action("CYCLEAUDIOCHAN", actions))
4637  ToggleMute(ctx, true);
4638  else if (has_action(ACTION_MUTEAUDIO, actions))
4639  ToggleMute(ctx);
4640  else if (has_action("STRETCHINC", actions))
4641  ChangeTimeStretch(ctx, 1);
4642  else if (has_action("STRETCHDEC", actions))
4643  ChangeTimeStretch(ctx, -1);
4644  else if (has_action("MENU", actions))
4645  ShowOSDMenu();
4646  else if (has_action(ACTION_MENUCOMPACT, actions))
4647  ShowOSDMenu(true);
4648  else if (has_action("INFO", actions) ||
4649  has_action("INFOWITHCUTLIST", actions))
4650  {
4651  if (HasQueuedInput())
4652  {
4653  DoArbSeek(ctx, ARBSEEK_SET,
4654  has_action("INFOWITHCUTLIST", actions));
4655  }
4656  else
4657  ToggleOSD(ctx, true);
4658  }
4659  else if (has_action(ACTION_TOGGLEOSDDEBUG, actions))
4660  ToggleOSDDebug(ctx);
4661  else if (!isDVDStill && SeekHandleAction(ctx, actions, isDVD))
4662  {
4663  }
4664  else
4665  {
4666  handled = false;
4667  QStringList::const_iterator it = actions.begin();
4668  for (; it != actions.end() && !handled; ++it)
4669  handled = HandleTrackAction(ctx, *it);
4670  }
4671 
4672  return handled;
4673 }
4674 
4675 bool TV::FFRewHandleAction(PlayerContext *ctx, const QStringList &actions)
4676 {
4677  bool handled = false;
4678 
4679  if (ctx->m_ffRewState)
4680  {
4681  for (int i = 0; i < actions.size() && !handled; i++)
4682  {
4683  QString action = actions[i];
4684  bool ok = false;
4685  int val = action.toInt(&ok);
4686 
4687  if (ok && val < (int)m_ffRewSpeeds.size())
4688  {
4689  SetFFRew(ctx, val);
4690  handled = true;
4691  }
4692  }
4693 
4694  if (!handled)
4695  {
4696  DoPlayerSeek(ctx, StopFFRew(ctx));
4697  UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Short);
4698  handled = true;
4699  }
4700  }
4701 
4702  if (ctx->m_ffRewSpeed)
4703  {
4704  NormalSpeed(ctx);
4705  UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Short);
4706  handled = true;
4707  }
4708 
4709  return handled;
4710 }
4711 
4713  const QStringList &actions, bool isDVD)
4714 {
4715  bool handled = true;
4716  bool islivetv = StateIsLiveTV(GetState(ctx));
4717 
4718  if (has_action(ACTION_BOTTOMLINEMOVE, actions))
4719  ToggleMoveBottomLine(ctx);
4720  else if (has_action(ACTION_BOTTOMLINESAVE, actions))
4721  SaveBottomLine(ctx);
4722  else if (has_action("TOGGLEASPECT", actions))
4723  ToggleAspectOverride(ctx);
4724  else if (has_action("TOGGLEFILL", actions))
4725  ToggleAdjustFill(ctx);
4726  else if (has_action(ACTION_TOGGELAUDIOSYNC, actions))
4727  ChangeAudioSync(ctx, 0); // just display
4728  else if (has_action(ACTION_TOGGLESUBTITLEZOOM, actions))
4729  ChangeSubtitleZoom(ctx, 0); // just display
4730  else if (has_action(ACTION_TOGGLESUBTITLEDELAY, actions))
4731  ChangeSubtitleDelay(ctx, 0); // just display
4732  else if (has_action(ACTION_TOGGLEVISUALISATION, actions))
4733  EnableVisualisation(ctx, false, true /*toggle*/);
4734  else if (has_action(ACTION_ENABLEVISUALISATION, actions))
4735  EnableVisualisation(ctx, true);
4736  else if (has_action(ACTION_DISABLEVISUALISATION, actions))
4737  EnableVisualisation(ctx, false);
4738  else if (has_action("TOGGLEPICCONTROLS", actions))
4739  DoTogglePictureAttribute(ctx, kAdjustingPicture_Playback);
4740  else if (has_action(ACTION_TOGGLENIGHTMODE, actions))
4741  DoToggleNightMode(ctx);
4742  else if (has_action("TOGGLESTRETCH", actions))
4743  ToggleTimeStretch(ctx);
4744  else if (has_action(ACTION_TOGGLEUPMIX, actions))
4745  EnableUpmix(ctx, false, true);
4746  else if (has_action(ACTION_TOGGLESLEEP, actions))
4747  ToggleSleepTimer(ctx);
4748  else if (has_action(ACTION_TOGGLERECORD, actions) && islivetv)
4749  QuickRecord(ctx);
4750  else if (has_action(ACTION_TOGGLEFAV, actions) && islivetv)
4751  ToggleChannelFavorite(ctx);
4752  else if (has_action(ACTION_TOGGLECHANCONTROLS, actions) && islivetv)
4753  DoTogglePictureAttribute(ctx, kAdjustingPicture_Channel);
4754  else if (has_action(ACTION_TOGGLERECCONTROLS, actions) && islivetv)
4755  DoTogglePictureAttribute(ctx, kAdjustingPicture_Recording);
4756  else if (has_action("TOGGLEBROWSE", actions))
4757  {
4758  if (islivetv)
4759  m_browseHelper->BrowseStart(ctx);
4760  else if (!isDVD)
4761  ShowOSDMenu();
4762  else
4763  handled = false;
4764  }
4765  else if (has_action("EDIT", actions))
4766  {
4767  if (islivetv)
4768  StartChannelEditMode(ctx);
4769  else if (!isDVD)
4770  StartProgramEditMode(ctx);
4771  }
4772  else if (has_action(ACTION_OSDNAVIGATION, actions))
4773  StartOsdNavigation(ctx);
4774  else
4775  handled = false;
4776 
4777  return handled;
4778 }
4779 
4780 void TV::EnableVisualisation(const PlayerContext *ctx, bool enable,
4781  bool toggle, const QString &action)
4782 {
4783  QString visualiser = QString("");
4784  if (action.startsWith("VISUALISER"))
4785  visualiser = action.mid(11);
4786 
4787  ctx->LockDeletePlayer(__FILE__, __LINE__);
4788  if (ctx->m_player && ctx->m_player->CanVisualise())
4789  {
4790  bool want = enable || !visualiser.isEmpty();
4791  if (toggle && visualiser.isEmpty())
4792  want = !ctx->m_player->IsVisualising();
4793  bool on = ctx->m_player->EnableVisualisation(want, visualiser);
4794  SetOSDMessage(ctx, on ? ctx->m_player->GetVisualiserName() :
4795  tr("Visualisation Off"));
4796  }
4797  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4798 }
4799 
4800 bool TV::PxPHandleAction(PlayerContext *ctx, const QStringList &actions)
4801 {
4802  if (!IsPIPSupported(ctx) && !IsPBPSupported(ctx))
4803  return false;
4804 
4805  bool handled = true;
4806  {
4807  QMutexLocker locker(&m_timerIdLock);
4808 
4809  if (has_action("TOGGLEPIPMODE", actions))
4810  m_changePxP.enqueue("TOGGLEPIPMODE");
4811  else if (has_action("TOGGLEPBPMODE", actions))
4812  m_changePxP.enqueue("TOGGLEPBPMODE");
4813  else if (has_action("CREATEPIPVIEW", actions))
4814  m_changePxP.enqueue("CREATEPIPVIEW");
4815  else if (has_action("CREATEPBPVIEW", actions))
4816  m_changePxP.enqueue("CREATEPBPVIEW");
4817  else if (has_action("SWAPPIP", actions))
4818  m_changePxP.enqueue("SWAPPIP");
4819  else if (has_action("TOGGLEPIPSTATE", actions))
4820  m_changePxP.enqueue("TOGGLEPIPSTATE");
4821  else
4822  handled = false;
4823 
4824  if (!m_changePxP.empty() && !m_pipChangeTimerId)
4825  m_pipChangeTimerId = StartTimer(1, __LINE__);
4826  }
4827 
4828  if (has_action("NEXTPIPWINDOW", actions))
4829  {
4830  SetActive(ctx, -1, true);
4831  handled = true;
4832  }
4833 
4834  return handled;
4835 }
4836 
4838 {
4839  ctx->LockDeletePlayer(__FILE__, __LINE__);
4840  if (ctx->m_player)
4841  {
4842  if (clear)
4843  {
4844  ctx->m_player->SetBookmark(true);
4845  SetOSDMessage(ctx, tr("Bookmark Cleared"));
4846  }
4847  else // if (IsBookmarkAllowed(ctx))
4848  {
4849  ctx->m_player->SetBookmark();
4850  osdInfo info;
4851  ctx->CalcPlayerSliderPosition(info);
4852  info.text["title"] = tr("Position");
4853  UpdateOSDStatus(ctx, info, kOSDFunctionalType_Default,
4854  kOSDTimeout_Med);
4855  SetOSDMessage(ctx, tr("Bookmark Saved"));
4856  }
4857  }
4858  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4859 }
4860 
4861 bool TV::ActivePostQHandleAction(PlayerContext *ctx, const QStringList &actions)
4862 {
4863  bool handled = true;
4864  TVState state = GetState(ctx);
4865  bool islivetv = StateIsLiveTV(state);
4866  bool isdvd = state == kState_WatchingDVD;
4867  bool isdisc = isdvd || state == kState_WatchingBD;
4868 
4869  if (has_action(ACTION_SETBOOKMARK, actions))
4870  {
4871  if (!CommitQueuedInput(ctx))
4872  {
4873  ctx->LockDeletePlayer(__FILE__, __LINE__);
4874  SetBookmark(ctx, false);
4875  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4876  }
4877  }
4878  if (has_action(ACTION_TOGGLEBOOKMARK, actions))
4879  {
4880  if (!CommitQueuedInput(ctx))
4881  {
4882  ctx->LockDeletePlayer(__FILE__, __LINE__);
4883  SetBookmark(ctx, ctx->m_player->GetBookmark());
4884  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4885  }
4886  }
4887  else if (has_action("NEXTFAV", actions) && islivetv)
4888  ChangeChannel(ctx, CHANNEL_DIRECTION_FAVORITE);
4889  else if (has_action("NEXTSOURCE", actions) && islivetv)
4890  SwitchSource(ctx, kNextSource);
4891  else if (has_action("PREVSOURCE", actions) && islivetv)
4892  SwitchSource(ctx, kPreviousSource);
4893  else if (has_action("NEXTINPUT", actions) && islivetv)
4894  SwitchInputs(ctx);
4895  else if (has_action(ACTION_GUIDE, actions))
4896  EditSchedule(ctx, kScheduleProgramGuide);
4897  else if (has_action("PREVCHAN", actions) && islivetv)
4898  PopPreviousChannel(ctx, false);
4899  else if (has_action(ACTION_CHANNELUP, actions))
4900  {
4901  if (islivetv)
4902  {
4903  if (m_dbBrowseAlways)
4904  m_browseHelper->BrowseDispInfo(ctx, BROWSE_UP);
4905  else
4906  ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
4907  }
4908  else
4909  DoJumpRWND(ctx);
4910  }
4911  else if (has_action(ACTION_CHANNELDOWN, actions))
4912  {
4913  if (islivetv)
4914  {
4915  if (m_dbBrowseAlways)
4916  m_browseHelper->BrowseDispInfo(ctx, BROWSE_DOWN);
4917  else
4918  ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
4919  }
4920  else
4921  DoJumpFFWD(ctx);
4922  }
4923  else if (has_action("DELETE", actions) && !islivetv)
4924  {
4925  NormalSpeed(ctx);
4926  StopFFRew(ctx);
4927  SetBookmark(ctx);
4928  ShowOSDPromptDeleteRecording(ctx, tr("Are you sure you want to delete:"));
4929  }
4930  else if (has_action(ACTION_JUMPTODVDROOTMENU, actions) && isdisc)
4931  {
4932  ctx->LockDeletePlayer(__FILE__, __LINE__);
4933  if (ctx->m_player)
4934  ctx->m_player->GoToMenu("root");
4935  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4936  }
4937  else if (has_action(ACTION_JUMPTODVDCHAPTERMENU, actions) && isdisc)
4938  {
4939  ctx->LockDeletePlayer(__FILE__, __LINE__);
4940  if (ctx->m_player)
4941  ctx->m_player->GoToMenu("chapter");
4942  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4943  }
4944  else if (has_action(ACTION_JUMPTODVDTITLEMENU, actions) && isdisc)
4945  {
4946  ctx->LockDeletePlayer(__FILE__, __LINE__);
4947  if (ctx->m_player)
4948  ctx->m_player->GoToMenu("title");
4949  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4950  }
4951  else if (has_action(ACTION_JUMPTOPOPUPMENU, actions) && isdisc)
4952  {
4953  ctx->LockDeletePlayer(__FILE__, __LINE__);
4954  if (ctx->m_player)
4955  ctx->m_player->GoToMenu("popup");
4956  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
4957  }
4958  else if (has_action(ACTION_FINDER, actions))
4959  EditSchedule(ctx, kScheduleProgramFinder);
4960  else
4961  handled = false;
4962 
4963  return handled;
4964 }
4965 
4966 
4968  const QString &command)
4969 {
4970  bool ignoreKeys = ctx->IsPlayerChangingBuffers();
4971 #ifdef DEBUG_ACTIONS
4972  LOG(VB_GENERAL, LOG_DEBUG, LOC +
4973  QString("(%1) ignoreKeys: %2").arg(command).arg(ignoreKeys));
4974 #endif
4975 
4976  if (ignoreKeys)
4977  {
4978  LOG(VB_GENERAL, LOG_WARNING, LOC +
4979  "Ignoring network control command"
4980  "\n\t\t\tbecause ignoreKeys is set");
4981  return;
4982  }
4983 
4984  QStringList tokens = command.split(" ", QString::SkipEmptyParts);
4985  if (tokens.size() < 2)
4986  {
4987  LOG(VB_GENERAL, LOG_ERR, LOC + "Not enough tokens"
4988  "in network control command" + "\n\t\t\t" +
4989  QString("'%1'").arg(command));
4990  return;
4991  }
4992 
4993  OSD *osd = GetOSDLock(ctx);
4994  bool dlg = false;
4995  if (osd)
4996  dlg = osd->DialogVisible();
4997  ReturnOSDLock(ctx, osd);
4998 
4999  if (dlg)
5000  {
5001  LOG(VB_GENERAL, LOG_WARNING, LOC +
5002  "Ignoring network control command\n\t\t\t" +
5003  QString("because dialog is waiting for a response"));
5004  return;
5005  }
5006 
5007  if (tokens[1] != "QUERY")
5008  ClearOSD(ctx);
5009 
5010  if (tokens.size() == 3 && tokens[1] == "CHANID")
5011  {
5012  m_queuedChanID = tokens[2].toUInt();
5013  m_queuedChanNum.clear();
5014  CommitQueuedInput(ctx);
5015  }
5016  else if (tokens.size() == 3 && tokens[1] == "CHANNEL")
5017  {
5018  if (StateIsLiveTV(GetState(ctx)))
5019  {
5020  if (tokens[2] == "UP")
5021  ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
5022  else if (tokens[2] == "DOWN")
5023  ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
5024  else if (tokens[2].contains(QRegExp("^[-\\.\\d_#]+$")))
5025  ChangeChannel(ctx, 0, tokens[2]);
5026  }
5027  }
5028  else if (tokens.size() == 3 && tokens[1] == "SPEED")
5029  {
5030  bool paused = ContextIsPaused(ctx, __FILE__, __LINE__);
5031 
5032  if (tokens[2] == "0x")
5033  {
5034  NormalSpeed(ctx);
5035  StopFFRew(ctx);
5036  if (!paused)
5037  DoTogglePause(ctx, true);
5038  }
5039  else if (tokens[2] == "normal")
5040  {
5041  NormalSpeed(ctx);
5042  StopFFRew(ctx);
5043  if (paused)
5044  DoTogglePause(ctx, true);
5045  return;
5046  }
5047  else
5048  {
5049  float tmpSpeed = 1.0F;
5050  bool ok = false;
5051 
5052  if (tokens[2].contains(QRegExp("^\\-*\\d+x$")))
5053  {
5054  QString speed = tokens[2].left(tokens[2].length()-1);
5055  tmpSpeed = speed.toFloat(&ok);
5056  }
5057  else if (tokens[2].contains(QRegExp("^\\-*\\d*\\.\\d+x$")))
5058  {
5059  QString speed = tokens[2].left(tokens[2].length() - 1);
5060  tmpSpeed = speed.toFloat(&ok);
5061  }
5062  else
5063  {
5064  QRegExp re = QRegExp("^(\\-*\\d+)\\/(\\d+)x$");
5065  if (tokens[2].contains(re))
5066  {
5067  QStringList matches = re.capturedTexts();
5068 
5069  int numerator, denominator;
5070  numerator = matches[1].toInt(&ok);
5071  denominator = matches[2].toInt(&ok);
5072 
5073  if (ok && denominator != 0)
5074  tmpSpeed = static_cast<float>(numerator) /
5075  static_cast<float>(denominator);
5076  else
5077  ok = false;
5078  }
5079  }
5080 
5081  if (ok)
5082  {
5083  float searchSpeed = fabs(tmpSpeed);
5084  size_t index;
5085 
5086  if (paused)
5087  DoTogglePause(ctx, true);
5088 
5089  if (tmpSpeed == 0.0F)
5090  {
5091  NormalSpeed(ctx);
5092  StopFFRew(ctx);
5093 
5094  if (!paused)
5095  DoTogglePause(ctx, true);
5096  }
5097  else if (tmpSpeed == 1.0F)
5098  {
5099  StopFFRew(ctx);
5100  ctx->m_tsNormal = 1.0F;
5101  ChangeTimeStretch(ctx, 0, false);
5102  return;
5103  }
5104 
5105  NormalSpeed(ctx);
5106 
5107  for (index = 0; index < m_ffRewSpeeds.size(); index++)
5108  if (float(m_ffRewSpeeds[index]) == searchSpeed)
5109  break;
5110 
5111  if ((index < m_ffRewSpeeds.size()) &&
5112  (float(m_ffRewSpeeds[index]) == searchSpeed))
5113  {
5114  if (tmpSpeed < 0)
5115  ctx->m_ffRewState = -1;
5116  else if (tmpSpeed > 1)
5117  ctx->m_ffRewState = 1;
5118  else
5119  StopFFRew(ctx);
5120 
5121  if (ctx->m_ffRewState)
5122  SetFFRew(ctx, index);
5123  }
5124  else if (0.48F <= tmpSpeed && tmpSpeed <= 2.0F) {
5125  StopFFRew(ctx);
5126 
5127  ctx->m_tsNormal = tmpSpeed; // alter speed before display
5128  ChangeTimeStretch(ctx, 0, false);
5129  }
5130  else
5131  {
5132  LOG(VB_GENERAL, LOG_WARNING,
5133  QString("Couldn't find %1 speed. Setting Speed to 1x")
5134  .arg(searchSpeed));
5135 
5136  ctx->m_ffRewState = 0;
5137  SetFFRew(ctx, kInitFFRWSpeed);
5138  }
5139  }
5140  else
5141  {
5142  LOG(VB_GENERAL, LOG_ERR,
5143  QString("Found an unknown speed of %1").arg(tokens[2]));
5144  }
5145  }
5146  }
5147  else if (tokens.size() == 2 && tokens[1] == "STOP")
5148  {
5149  SetBookmark(ctx);
5150  ctx->LockDeletePlayer(__FILE__, __LINE__);
5151  if (ctx->m_player && m_dbAutoSetWatched)
5152  ctx->m_player->SetWatched();
5153  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5154  SetExitPlayer(true, true);
5155  }
5156  else if (tokens.size() >= 3 && tokens[1] == "SEEK" && ctx->HasPlayer())
5157  {
5158  if (ctx->m_buffer && ctx->m_buffer->IsInDiscMenuOrStillFrame())
5159  return;
5160 
5161  if (tokens[2] == "BEGINNING")
5162  DoSeek(ctx, 0, tr("Jump to Beginning"),
5163  /*timeIsOffset*/false,
5164  /*honorCutlist*/true);
5165  else if (tokens[2] == "FORWARD")
5166  DoSeek(ctx, ctx->m_fftime, tr("Skip Ahead"),
5167  /*timeIsOffset*/true,
5168  /*honorCutlist*/true);
5169  else if (tokens[2] == "BACKWARD")
5170  DoSeek(ctx, -ctx->m_rewtime, tr("Skip Back"),
5171  /*timeIsOffset*/true,
5172  /*honorCutlist*/true);
5173  else if ((tokens[2] == "POSITION" ||
5174  tokens[2] == "POSITIONWITHCUTLIST") &&
5175  (tokens.size() == 4) &&
5176  (tokens[3].contains(QRegExp("^\\d+$"))))
5177  {
5178  DoSeekAbsolute(ctx, tokens[3].toInt(),
5179  tokens[2] == "POSITIONWITHCUTLIST");
5180  }
5181  }
5182  else if (tokens.size() >= 3 && tokens[1] == "SUBTITLES")
5183  {
5184  bool ok = false;
5185  uint track = tokens[2].toUInt(&ok);
5186 
5187  if (!ok)
5188  return;
5189 
5190  if (track == 0)
5191  {
5192  ctx->m_player->SetCaptionsEnabled(false, true);
5193  }
5194  else
5195  {
5196  uint start = 1;
5197  QStringList subs = ctx->m_player->GetTracks(kTrackTypeSubtitle);
5198  uint finish = start + subs.size();
5199  if (track >= start && track < finish)
5200  {
5201  ctx->m_player->SetTrack(kTrackTypeSubtitle, track - start);
5203  return;
5204  }
5205 
5206  start = finish + 1;
5207  subs = ctx->m_player->GetTracks(kTrackTypeCC708);
5208  finish = start + subs.size();
5209  if (track >= start && track < finish)
5210  {
5211  ctx->m_player->SetTrack(kTrackTypeCC708, track - start);
5213  return;
5214  }
5215 
5216  start = finish + 1;
5217  subs = ctx->m_player->GetTracks(kTrackTypeCC608);
5218  finish = start + subs.size();
5219  if (track >= start && track < finish)
5220  {
5221  ctx->m_player->SetTrack(kTrackTypeCC608, track - start);
5223  return;
5224  }
5225 
5226  start = finish + 1;
5228  finish = start + subs.size();
5229  if (track >= start && track < finish)
5230  {
5231  ctx->m_player->SetTrack(kTrackTypeTeletextCaptions, track-start);
5233  return;
5234  }
5235 
5236  start = finish + 1;
5238  finish = start + subs.size();
5239  if (track >= start && track < finish)
5240  {
5241  ctx->m_player->SetTrack(kTrackTypeTeletextMenu, track - start);
5243  return;
5244  }
5245 
5246  start = finish + 1;
5247  subs = ctx->m_player->GetTracks(kTrackTypeRawText);
5248  finish = start + subs.size();
5249  if (track >= start && track < finish)
5250  {
5251  ctx->m_player->SetTrack(kTrackTypeRawText, track - start);
5253  return;
5254  }
5255  }
5256  }
5257  else if (tokens.size() >= 3 && tokens[1] == "VOLUME")
5258  {
5259  QRegExp re = QRegExp("(\\d+)%");
5260  if (tokens[2].contains(re))
5261  {
5262  QStringList matches = re.capturedTexts();
5263 
5264  LOG(VB_GENERAL, LOG_INFO, QString("Set Volume to %1%")
5265  .arg(matches[1]));
5266 
5267  bool ok = false;
5268 
5269  int vol = matches[1].toInt(&ok);
5270 
5271  if (!ok)
5272  return;
5273 
5274  if (0 <= vol && vol <= 100)
5275  {
5276  ctx->LockDeletePlayer(__FILE__, __LINE__);
5277  if (!ctx->m_player)
5278  {
5279  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5280  return;
5281  }
5282 
5283  vol -= ctx->m_player->GetVolume();
5284  vol = ctx->m_player->AdjustVolume(vol);
5285  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5286 
5287  if (!m_browseHelper->IsBrowsing() && !m_editMode)
5288  {
5289  UpdateOSDStatus(
5290  ctx, tr("Adjust Volume"), tr("Volume"),
5291  QString::number(vol),
5292  kOSDFunctionalType_PictureAdjust, "%", vol * 10,
5293  kOSDTimeout_Med);
5294  SetUpdateOSDPosition(false);
5295  }
5296  }
5297  }
5298  }
5299  else if (tokens.size() >= 3 && tokens[1] == "QUERY")
5300  {
5301  if (tokens[2] == "POSITION")
5302  {
5303  if (!ctx->m_player)
5304  return;
5305  QString speedStr;
5306  if (ContextIsPaused(ctx, __FILE__, __LINE__))
5307  {
5308  speedStr = "pause";
5309  }
5310  else if (ctx->m_ffRewState)
5311  {
5312  speedStr = QString("%1x").arg(ctx->m_ffRewSpeed);
5313  }
5314  else
5315  {
5316  QRegExp re = QRegExp("Play (.*)x");
5317  if (ctx->GetPlayMessage().contains(re))
5318  {
5319  QStringList matches = re.capturedTexts();
5320  speedStr = QString("%1x").arg(matches[1]);
5321  }
5322  else
5323  {
5324  speedStr = "1x";
5325  }
5326  }
5327 
5328  osdInfo info;
5329  ctx->CalcPlayerSliderPosition(info, true);
5330 
5331  QDateTime respDate = MythDate::current(true);
5332  QString infoStr = "";
5333 
5334  ctx->LockDeletePlayer(__FILE__, __LINE__);
5335  long long fplay = 0;
5336  float rate = 30.0F;
5337  if (ctx->m_player)
5338  {
5339  fplay = ctx->m_player->GetFramesPlayed();
5340  rate = ctx->m_player->GetFrameRate(); // for display only
5341  }
5342  ctx->UnlockDeletePlayer(__FILE__, __LINE__);
5343 
5344  ctx->LockPlayingInfo(__FILE__, __LINE__);
5345  if (ctx->GetState() == kState_WatchingLiveTV)
5346  {
5347  infoStr = "LiveTV";
5348  if (ctx->m_playingInfo)
5349  respDate = ctx->m_playingInfo->GetScheduledStartTime();
5350  }
5351  else
5352  {
5353  if (ctx->m_buffer && ctx->m_buffer->IsDVD())
5354  infoStr = "DVD";
5355  else if (ctx->m_playingInfo->IsRecording())
5356  infoStr = "Recorded";
5357  else
5358  infoStr = "Video";
5359 
5360  if (ctx->m_playingInfo)
5361  respDate = ctx->m_playingInfo->GetRecordingStartTime();
5362  }
5363 
5364  QString bufferFilename =
5365  ctx->m_buffer ? ctx->m_buffer->GetFilename() : QString("no buffer");
5366  if ((infoStr == "Recorded") || (infoStr == "LiveTV"))
5367  {
5368  infoStr += QString(" %1 %2 %3 %4 %5 %6 %7")
5369  .arg(info.text["description"])
5370  .arg(speedStr)
5371  .arg(ctx->m_playingInfo != nullptr ?
5372  ctx->m_playingInfo->GetChanID() : 0)
5373  .arg(respDate.toString(Qt::ISODate))
5374  .arg(fplay)
5375  .arg(bufferFilename)
5376  .arg(rate);
5377  }
5378  else
5379  {
5380  QString position = info.text["description"].section(" ",0,0);
5381  infoStr += QString(" %1 %2 %3 %4 %5")
5382  .arg(position)
5383  .arg(speedStr)
5384  .arg(bufferFilename)
5385  .arg(fplay)
5386  .arg(rate);
5387  }
5388 
5389  infoStr += QString(" Subtitles:");
5390 
5391  uint subtype = ctx->m_player->GetCaptionMode();
5392 
5393  if (subtype == kDisplayNone)
5394  infoStr += QString(" *0:[None]*");
5395  else
5396  infoStr += QString(" 0:[None]");
5397 
5398  uint n = 1;
5399 
5400  QStringList subs = ctx->m_player->GetTracks(kTrackTypeSubtitle);
5401  for (uint i = 0; i < (uint)subs.size(); i++)
5402  {
5403  if ((subtype & kDisplayAVSubtitle) &&
5404  (ctx->m_player->GetTrack(kTrackTypeSubtitle) == (int)i))
5405  {
5406  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5407  }
5408  else
5409  {
5410  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5411  }
5412  n++;
5413  }
5414 
5415  subs = ctx->m_player->GetTracks(kTrackTypeCC708);
5416  for (uint i = 0; i < (uint)subs.size(); i++)
5417  {
5418  if ((subtype & kDisplayCC708) &&
5419  (ctx->m_player->GetTrack(kTrackTypeCC708) == (int)i))
5420  {
5421  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5422  }
5423  else
5424  {
5425  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5426  }
5427  n++;
5428  }
5429 
5430  subs = ctx->m_player->GetTracks(kTrackTypeCC608);
5431  for (uint i = 0; i < (uint)subs.size(); i++)
5432  {
5433  if ((subtype & kDisplayCC608) &&
5434  (ctx->m_player->GetTrack(kTrackTypeCC608) == (int)i))
5435  {
5436  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5437  }
5438  else
5439  {
5440  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5441  }
5442  n++;
5443  }
5444 
5446  for (uint i = 0; i < (uint)subs.size(); i++)
5447  {
5448  if ((subtype & kDisplayTeletextCaptions) &&
5449  (ctx->m_player->GetTrack(kTrackTypeTeletextCaptions)==(int)i))
5450  {
5451  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5452  }
5453  else
5454  {
5455  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5456  }
5457  n++;
5458  }
5459 
5461  for (uint i = 0; i < (uint)subs.size(); i++)
5462  {
5463  if ((subtype & kDisplayTeletextMenu) &&
5464  ctx->m_player->GetTrack(kTrackTypeTeletextMenu) == (int)i)
5465  {
5466  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5467  }
5468  else
5469  {
5470  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5471  }
5472  n++;
5473  }
5474 
5475  subs = ctx->m_player->GetTracks(kTrackTypeRawText);
5476  for (uint i = 0; i < (uint)subs.size(); i++)
5477  {
5478  if ((subtype & kDisplayRawTextSubtitle) &&
5479  ctx->m_player->GetTrack(kTrackTypeRawText) == (int)i)
5480  {
5481  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
5482  }
5483  else
5484  {
5485  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
5486  }
5487  n++;
5488  }
5489 
5490  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
5491 
5492  QString message = QString("NETWORK_CONTROL ANSWER %1")
5493  .arg(infoStr);
5494  MythEvent me(message);
5495  gCoreContext->dispatch(me);
5496  }
5497  else if (tokens[2] == "VOLUME")
5498  {
5499  if (!ctx->m_player)
5500  return;
5501  QString infoStr = QString("%1%").arg(ctx->m_player->GetVolume());
5502 
5503  QString message = QString("NETWORK_CONTROL ANSWER %1")
5504  .arg(infoStr);
5505  MythEvent me(message);
5506  gCoreContext->dispatch(me);
5507  }
5508  }
5509 }
5510 
5516 bool TV::CreatePBP(PlayerContext *ctx, const ProgramInfo *info)
5517 {
5518  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
5519 
5520  if (m_player.size() > 1)
5521  {
5522  LOG(VB_GENERAL, LOG_ERR, LOC + "Only allowed when player.size() == 1");
5523  return false;
5524  }
5525 
5526  PlayerContext *mctx = GetPlayer(ctx, 0);
5527  if (!IsPBPSupported(mctx))
5528  {
5529  LOG(VB_GENERAL, LOG_ERR, LOC + "PBP not supported by video method.");
5530  return false;
5531  }
5532 
5533  if (!mctx->m_player)
5534  return false;
5535  mctx->LockDeletePlayer(__FILE__, __LINE__);
5536  long long mctx_frame = mctx->m_player->GetFramesPlayed();
5537  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
5538 
5539  // This is safe because we are already holding lock for a ctx
5540  m_player.push_back(new PlayerContext(kPBPPlayerInUseID));
5541  PlayerContext *pbpctx = m_player.back();
5542  // see comment in CreatePIP on disabling hardware acceleration for secondary players
5543  //if (m_noHardwareDecoders)
5544  pbpctx->SetNoHardwareDecoders();
5545  pbpctx->SetPIPState(kPBPRight);
5546 
5547  if (info)
5548  {
5549  pbpctx->SetPlayingInfo(info);
5550  pbpctx->SetInitialTVState(false);
5551  ScheduleStateChange(pbpctx);
5552  }
5553  else if (RequestNextRecorder(pbpctx, false))
5554  {
5555  pbpctx->SetInitialTVState(true);
5556  ScheduleStateChange(pbpctx);
5557  }
5558  else
5559  {
5560  delete m_player.back();
5561  m_player.pop_back();
5562  return false;
5563  }
5564 
5565  mctx->PIPTeardown();
5566  mctx->SetPIPState(kPBPLeft);
5567  if (mctx->m_buffer)
5568  mctx->m_buffer->Seek(0, SEEK_SET);
5569 
5570  if (StateIsLiveTV(mctx->GetState()))
5571  if (mctx->m_buffer)
5572  mctx->m_buffer->Unpause();
5573 
5574  bool ok = mctx->CreatePlayer(
5575  this, GetMythMainWindow(), mctx->GetState(), false);
5576 
5577  if (ok)
5578  {
5579  ScheduleStateChange(mctx);
5580  mctx->LockDeletePlayer(__FILE__, __LINE__);
5581  if (mctx->m_player)
5582  mctx->m_player->JumpToFrame(mctx_frame);
5583  mctx->UnlockDeletePlayer(__FILE__, __LINE__);
5584  SetSpeedChangeTimer(25, __LINE__);
5585  }
5586  else
5587  {
5588  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to restart new main context");
5589  // Make putative PBP context the main context
5590  swap(m_player[0],m_player[1]);
5591  m_player[0]->SetPIPState(kPIPOff);
5592  // End the old main context..
5593  ForceNextStateNone(mctx);
5594  }
5595 
5596  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
5597  QString("-- end : %1").arg(ok));
5598  return ok;
5599 }
5600 
5606 bool TV::CreatePIP(PlayerContext *ctx, const ProgramInfo *info)
5607 {
5608  PlayerContext *mctx = GetPlayer(ctx, 0);
5609  if (!mctx)
5610  return false;
5611 
5612  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
5613 
5614  if (mctx->IsPBP())
5615  {
5616  LOG(VB_GENERAL, LOG_ERR, LOC +
5617  "CreatePIP called, but we're in PBP mode already, ignoring.");
5618  return false;
5619  }
5620 
5621  if (!IsPIPSupported(mctx))
5622  {
5623  LOG(VB_GENERAL, LOG_ERR, LOC + "PiP not supported by video method.");
5624  return false;
5625  }
5626 
5627  auto *pipctx = new PlayerContext(kPIPPlayerInUseID);
5628  // Hardware acceleration of PiP is currently disabled as the null video
5629  // renderer cannot deal with hardware codecs which are returned by the display
5630  // profile. The workaround would be to encourage the decoder, when using null
5631  // video, to change the decoder to a decode only version - which will work
5632  // with null video
5633  //if (m_noHardwareDecoders)
5634  pipctx->SetNoHardwareDecoders();
5635  pipctx->SetNullVideo(true);
5636  pipctx->SetPIPState(kPIPonTV);
5637  if (info)
5638  {
5639  pipctx->SetPlayingInfo(info);
5640  pipctx->SetInitialTVState(false);
5641  ScheduleStateChange(pipctx);
5642  }
5643  else if (RequestNextRecorder(pipctx, false))
5644  {
5645  pipctx->SetInitialTVState(true);
5646  ScheduleStateChange(pipctx);
5647  }
5648  else
5649  {
5650  delete pipctx;
5651  return false;
5652  }
5653 
5654  // this is safe because we are already holding lock for ctx
5655  m_player.push_back(pipctx);
5656 
5657  return true;
5658 }
5659 
5661 {
5662  for (size_t i = 0; i < m_player.size(); i++)
5663  if (GetPlayer(ctx, i) == ctx)
5664  return i;
5665  return -1;
5666 }
5667 
5669  TVState desiredState)
5670 {
5671  bool wantPiP = ctx->IsPIP();
5672 
5673  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1, %2, %3) -- begin")
5674  .arg(find_player_index(ctx)).arg(StateToString(desiredState))
5675  .arg((wantPiP) ? "PiP" : "main"));
5676 
5677  LOG(VB_PLAYBACK, LOG_INFO, LOC +
5678  QString("Elapsed time since TV constructor was called: %1 ms")
5679  .arg(m_ctorTime.elapsed()));
5680 
5681  if (wantPiP)
5682  {
5683  if (mctx->HasPlayer() && ctx->StartPIPPlayer(this, desiredState) &&
5684  ctx->HasPlayer() && PIPAddPlayer(mctx, ctx))
5685  {
5686  ScheduleStateChange(ctx);
5687  LOG(VB_GENERAL, LOG_DEBUG, "PiP -- end : ok");
5688  return true;
5689  }
5690 
5691  ForceNextStateNone(ctx);
5692  LOG(VB_GENERAL, LOG_ERR, "PiP -- end : !ok");
5693  return false;
5694  }
5695 
5696  bool ok = false;
5697  if (ctx->IsNullVideoDesired())
5698  {
5699  ok = ctx->CreatePlayer(this, nullptr, desiredState, false);
5700  ScheduleStateChange(ctx);
5701  if (ok)
5702  ok = PIPAddPlayer(mctx, ctx);
5703  }
5704  else
5705  {
5706  ok = ctx->CreatePlayer(this, GetMythMainWindow(), desiredState, false);
5707  ScheduleStateChange(ctx);
5708  }
5709 
5710  if (ok)
5711  {
5712  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created player."));
5713  SetSpeedChangeTimer(25, __LINE__);
5714  }
5715  else
5716  LOG(VB_GENERAL, LOG_CRIT, LOC + QString("Failed to create player."));
5717 
5718  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
5719  QString("(%1, %2, %3) -- end %4")
5720  .arg(find_player_index(ctx)).arg(StateToString(desiredState))
5721  .arg((wantPiP) ? "PiP" : "main").arg((ok) ? "ok" : "error"));
5722 
5723  return ok;
5724 }
5725 
5728 {
5729  if (!mctx || !pipctx)
5730  return false;
5731 
5732  if (!mctx->IsPlayerPlaying())
5733  return false;
5734 
5735  bool ok = false, addCondition = false;
5736  bool is_using_null = false;
5737  pipctx->LockDeletePlayer(__FILE__, __LINE__);
5738  if (pipctx->m_player)
5739  {
5740  is_using_null = pipctx->m_player->UsingNullVideo();
5741  pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
5742 
5743  if (is_using_null)
5744  {
5745  addCondition = true;
5746  multi_lock(&mctx->m_deletePlayerLock, &pipctx->m_deletePlayerLock, nullptr);
5747  if (mctx->m_player && pipctx->m_player)
5748  {
5749  PIPLocation loc = mctx->m_player->GetNextPIPLocation();
5750  if (loc != kPIP_END)
5751  ok = mctx->m_player->AddPIPPlayer(pipctx->m_player, loc);
5752  }
5753  mctx->m_deletePlayerLock.unlock();
5754  pipctx->m_deletePlayerLock.unlock();
5755  }
5756  else if (pipctx->IsPIP())
5757  {
5758  ok = ResizePIPWindow(pipctx);
5759  }
5760  }
5761  else
5762  pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
5763 
5764  LOG(VB_GENERAL, LOG_ERR,
5765  QString("AddPIPPlayer null: %1 IsPIP: %2 addCond: %3 ok: %4")
5766  .arg(is_using_null)
5767  .arg(pipctx->IsPIP()).arg(addCondition).arg(ok));
5768 
5769  return ok;
5770 }
5771 
5774 {
5775  if (!mctx || !pipctx)
5776  return false;
5777 
5778  bool ok = false;
5779  multi_lock(&mctx->m_deletePlayerLock, &pipctx->m_deletePlayerLock, nullptr);
5780  if (mctx->m_player && pipctx->m_player)
5781  ok = mctx->m_player->RemovePIPPlayer(pipctx->m_player);
5782  mctx->m_deletePlayerLock.unlock();
5783  pipctx->m_deletePlayerLock.unlock();
5784 
5785  LOG(VB_GENERAL, LOG_INFO, QString("PIPRemovePlayer ok: %1").arg(ok));
5786 
5787  return ok;
5788 }
5789 
5791 void TV::PxPToggleView(PlayerContext *actx, bool wantPBP)
5792 {
5793  if (wantPBP && !IsPBPSupported(actx))
5794  {
5795  LOG(VB_GENERAL, LOG_WARNING, LOC +
5796  "-- end: PBP not supported by video method.");
5797  return;
5798  }
5799 
5800  if (m_player.size() <= 1)
5801  PxPCreateView(actx, wantPBP);
5802  else
5803  PxPTeardownView(actx);
5804 }
5805 
5807 void TV::PxPCreateView(PlayerContext *actx, bool wantPBP)
5808 {
5809  if (!actx)
5810  return;
5811 
5812  QString err_msg;
5813  if ((m_player.size() > kMaxPBPCount) && (wantPBP || actx->IsPBP()))
5814  {
5815  err_msg = tr("Sorry, PBP only supports %n video stream(s)", "",
5816  kMaxPBPCount);
5817  }
5818 
5819  if ((m_player.size() > kMaxPIPCount) &&
5820  (!wantPBP || GetPlayer(actx,1)->IsPIP()))
5821  {
5822  err_msg = tr("Sorry, PIP only supports %n video stream(s)", "",
5823  kMaxPIPCount);
5824  }
5825 
5826  if ((m_player.size() > 1) && (wantPBP ^ actx->IsPBP()))
5827  err_msg = tr("Sorry, cannot mix PBP and PIP views");
5828 
5829  if (!err_msg.isEmpty())
5830  {
5831  LOG(VB_GENERAL, LOG_ERR, LOC + err_msg);
5832  SetOSDMessage(actx, err_msg);
5833  return;
5834  }
5835 
5836  bool ok = false;
5837  if (wantPBP)
5838  ok = CreatePBP(actx, nullptr);
5839  else
5840  ok = CreatePIP(actx, nullptr);
5841  actx = GetPlayer(actx, -1); // CreatePBP/PIP mess with ctx's
5842 
5843  QString msg = (ok) ?
5844  ((wantPBP) ? tr("Creating PBP") : tr("Creating PIP")) :
5845  ((wantPBP) ? tr("Cannot create PBP") : tr("Cannot create PIP"));
5846 
5847  SetOSDMessage(actx, msg);
5848 }
5849 
5852 {
5853  LOG(VB_GENERAL, LOG_INFO, "PxPTeardownView()");
5854 
5855  QString msg;
5856  PlayerContext *mctx = GetPlayer(actx, 0);
5857  PlayerContext *dctx = nullptr;
5858  dctx = (mctx != actx) ? actx : dctx;
5859  dctx = (2 == m_player.size()) ? GetPlayer(actx, 1) : dctx;
5860 
5861  SetActive(actx, 0, false);
5862 
5863  PlayerContext *ctx1 = GetPlayer(actx, 1);
5864  msg = (ctx1->IsPIP()) ? tr("Stopping PIP") : tr("Stopping PBP");
5865  if (dctx)
5866  {
5867  ForceNextStateNone(dctx);
5868  }
5869  else
5870  {
5871  if (m_player.size() > 2)
5872  {
5873  msg = (ctx1->IsPIP()) ?
5874  tr("Stopping all PIPs") : tr("Stopping all PBPs");
5875  }
5876 
5877  for (uint i = m_player.size() - 1; i > 0; i--)
5878  ForceNextStateNone(GetPlayer(actx,i));
5879  }
5880 
5881  SetOSDMessage(mctx, msg);
5882 }
5883 
5887 void TV::PxPToggleType(PlayerContext *mctx, bool wantPBP)
5888 {
5889  const QString before = (mctx->IsPBP()) ? "PBP" : "PIP";
5890  const QString after = (wantPBP) ? "PBP" : "PIP";
5891 
5892  // TODO renderer may change depending on display profile
5893  // check for support in new renderer
5894  if (wantPBP && !IsPBPSupported(mctx))
5895  {
5896  LOG(VB_GENERAL, LOG_WARNING, LOC +
5897  "-- end: PBP not supported by video method.");
5898  return;
5899  }
5900 
5901 
5902  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
5903  QString("co