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