MythTV  master
tv_play.cpp
Go to the documentation of this file.
1 // Qt
2 #include <QApplication>
3 #include <QDomDocument>
4 #include <QDomElement>
5 #include <QDomNode>
6 #include <QEvent>
7 #include <QFile>
8 #include <QKeyEvent>
9 #include <QRegExp>
10 #include <QRegularExpression>
11 #include <QRunnable>
12 #include <QTimerEvent>
13 #include <utility>
14 
15 #include "mythconfig.h"
16 
17 // libmythbase
18 #include "mthreadpool.h"
19 #include "signalhandling.h"
20 #include "mythdb.h"
21 #include "mythcorecontext.h"
22 #include "mythlogging.h"
23 #include "lcddevice.h"
24 #include "compat.h"
25 #include "mythdirs.h"
26 #include "mythmedia.h"
27 
28 // libmyth
29 #include "programinfo.h"
30 #include "remoteutil.h"
31 
32 // libmythui
33 #include "mythuistatetracker.h"
34 #include "mythuihelper.h"
35 #include "mythdialogbox.h"
36 #include "mythmainwindow.h"
37 #include "mythmiscutil.h"
38 #include "mythscreenstack.h"
39 #include "mythscreentype.h"
40 #include "mythuiactions.h"
41 
42 // libmythtv
43 #include "DVD/mythdvdplayer.h"
44 #include "Bluray/mythbdplayer.h"
45 #include "DVD/mythdvdbuffer.h"
46 #include "Bluray/mythbdbuffer.h"
47 #include "remoteencoder.h"
48 #include "tvremoteutil.h"
49 #include "mythplayerui.h"
50 #include "jobqueue.h"
51 #include "livetvchain.h"
52 #include "playgroup.h"
53 #include "sourceutil.h"
54 #include "cardutil.h"
55 #include "channelutil.h"
56 #include "tv_play_win.h"
57 #include "recordinginfo.h"
58 #include "signalmonitorvalue.h"
59 #include "recordingrule.h"
60 #include "mythsystemevent.h"
61 #include "videometadatautil.h"
62 #include "playercontext.h"
63 #include "programtypes.h"
64 #include "io/mythmediabuffer.h"
65 #include "mythtvactionutils.h"
66 #include "mythcodeccontext.h"
67 #include "tv_play.h"
68 
69 // Std
70 #include <algorithm>
71 #include <cassert>
72 #include <chrono>
73 #include <cmath>
74 #include <cstdarg>
75 #include <cstdint>
76 #include <cstdlib>
77 #include <thread>
78 
79 #if ! HAVE_ROUND
80 #define round(x) ((int) ((x) + 0.5))
81 #endif
82 
83 #define DEBUG_CHANNEL_PREFIX 0
84 #define DEBUG_ACTIONS 0
86 #define LOC QString("TV::%1(): ").arg(__func__)
87 
88 #define HideOSDWindow(WINDOW) { \
89  OSD *osd = GetOSDL(); \
90  if (osd) \
91  osd->HideWindow(WINDOW); \
92  ReturnOSDLock(); }
93 
94 const int TV::kInitFFRWSpeed = 0;
95 const uint TV::kInputKeysMax = 6;
96 const uint TV::kNextSource = 1;
97 const uint TV::kPreviousSource = 2;
98 
99 const uint TV::kInputModeTimeout = 5000;
100 const uint TV::kLCDTimeout = 1000;
101 const uint TV::kBrowseTimeout = 30000;
102 const uint TV::kKeyRepeatTimeout = 300;
103 const uint TV::kPrevChanTimeout = 750;
104 const uint TV::kSleepTimerDialogTimeout = 45000;
105 const uint TV::kIdleTimerDialogTimeout = 45000;
106 const uint TV::kVideoExitDialogTimeout = 120000;
107 
110 const uint TV::kEmbedCheckFrequency = 250;
113 #ifdef USING_VALGRIND
115 #else
117 #endif
118 const uint TV::kSaveLastPlayPosTimeout = 30000;
119 
124 QStringList TV::lastProgramStringList = QStringList();
125 
130 
135 
140 
145 
150 
155 {
156  int count = 0;
157 
159  query.prepare("SELECT COUNT(cardid) FROM capturecard;");
160  if (query.exec() && query.isActive() && query.size() && query.next())
161  count = query.value(0).toInt();
162 
163  LOG(VB_RECORD, LOG_INFO,
164  "ConfiguredTunerCards() = " + QString::number(count));
165 
166  return count;
167 }
168 
176 TV* TV::AcquireRelease(int& RefCount, bool Acquire, bool Create /*=false*/)
177 {
178  static QMutex s_lock;
179  static TV* s_tv = nullptr;
180  QMutexLocker locker(&s_lock);
181 
182  if (Acquire)
183  {
184  if (!s_tv && Create)
185  s_tv = new TV(GetMythMainWindow());
186  else if (s_tv)
187  s_tv->IncrRef();
188  }
189  else
190  {
191  if (!s_tv)
192  LOG(VB_GENERAL, LOG_ERR, LOC + "Ref count error");
193  else
194  if (s_tv->DecrRef() == 0)
195  s_tv = nullptr;
196  }
197 
198  if (s_tv)
199  RefCount = s_tv->m_referenceCount;
200  else
201  RefCount = 0;
202  return s_tv;
203 }
204 
210 {
211  bool result = false;
212  int dummy = 0;
213  TV* tv = AcquireRelease(dummy, true);
214  if (tv)
215  {
216  result = true;
217  AcquireRelease(dummy, false);
218  }
219  return result;
220 }
221 
230 {
231  return &m_playerContext;
232 }
233 
234 bool TV::CreatePlayer(TVState State, bool Muted)
235 {
237  {
238  LOG(VB_GENERAL, LOG_ERR, LOC + "Already have a player");
239  return false;
240  }
241 
242  uint playerflags = kDecodeAllowGPU;
243  playerflags |= Muted ? kAudioMuted : kNoFlags;
244  auto flags = static_cast<PlayerFlags>(playerflags);
245 
246  MythPlayerUI *player = nullptr;
247  if (kState_WatchingBD == State)
248  player = new MythBDPlayer(m_mainWindow, this, &m_playerContext, flags);
249  else if (kState_WatchingDVD == State)
250  player = new MythDVDPlayer(m_mainWindow, this, &m_playerContext, flags);
251  else
252  player = new MythPlayerUI(m_mainWindow, this, &m_playerContext, flags);
253 
254  player->SetLength(static_cast<int>(m_playerContext.m_playingLen));
255 
256  bool isWatchingRecording = (State == kState_WatchingRecording);
257  player->SetWatchingRecording(isWatchingRecording);
258 
259  m_playerContext.SetPlayer(player);
260  emit InitialisePlayerState();
261  m_player = player;
262  return StartPlaying(-1);
263 }
264 
270 bool TV::StartPlaying(int MaxWait)
271 {
272  if (!m_player)
273  return false;
274 
275  if (!m_player->StartPlaying())
276  {
277  LOG(VB_GENERAL, LOG_ERR, LOC + "StartPlaying() Failed to start player");
278  // no need to call StopPlaying here as the player context will be deleted
279  // later following the error
280  return false;
281  }
282  MaxWait = (MaxWait <= 0) ? 20000 : MaxWait;
283 #ifdef USING_VALGRIND
284  maxWait = (1<<30);
285 #endif // USING_VALGRIND
286  MythTimer t;
287  t.start();
288 
289  while (!m_player->IsPlaying(50, true) && (t.elapsed() < MaxWait))
291 
292  if (m_player->IsPlaying())
293  {
294  LOG(VB_PLAYBACK, LOG_INFO, LOC +
295  QString("StartPlaying(): took %1 ms to start player.")
296  .arg(t.elapsed()));
297  return true;
298  }
299  LOG(VB_GENERAL, LOG_ERR, LOC + "StartPlaying() Failed to start player");
301  return false;
302 }
303 
305 {
307  PrepareToExitPlayer(__LINE__);
308  SetExitPlayer(true, true);
311 }
312 
323 bool TV::StartTV(ProgramInfo* TVRec, uint Flags, const ChannelInfoList& Selection)
324 {
325  int refs = 0;
326  TV* tv = AcquireRelease(refs, true, true);
327  // handle existing TV object atomically
328  if (refs > 1)
329  {
330  AcquireRelease(refs, false);
331  LOG(VB_GENERAL, LOG_WARNING, LOC + "Already have a TV object.");
333  return false;
334  }
335 
336  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
337  bool inPlaylist = (Flags & kStartTVInPlayList) != 0U;
338  bool initByNetworkCommand = (Flags & kStartTVByNetworkCommand) != 0U;
339  bool quitAll = false;
340  bool showDialogs = true;
341  bool playCompleted = false;
342  ProgramInfo *curProgram = nullptr;
343  bool startSysEventSent = false;
344  bool startLivetvEventSent = false;
345 
346  if (TVRec)
347  {
348  curProgram = new ProgramInfo(*TVRec);
349  curProgram->SetIgnoreBookmark((Flags & kStartTVIgnoreBookmark) != 0U);
350  curProgram->SetIgnoreProgStart((Flags & kStartTVIgnoreProgStart) != 0U);
351  curProgram->SetAllowLastPlayPos((Flags & kStartTVAllowLastPlayPos) != 0U);
352  }
353 
354  // Initialize TV
355  if (!tv->Init())
356  {
357  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed initializing TV");
358  AcquireRelease(refs, false);
359  delete curProgram;
361  return false;
362  }
363 
364  if (!lastProgramStringList.empty())
365  {
367  if (pginfo.HasPathname() || pginfo.GetChanID())
368  tv->SetLastProgram(&pginfo);
369  }
370 
371  // Notify others that we are about to play
373 
374  QString playerError;
375  while (!quitAll)
376  {
377  if (curProgram)
378  {
379  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->Playback() -- begin");
380  if (!tv->Playback(*curProgram))
381  {
382  quitAll = true;
383  }
384  else if (!startSysEventSent)
385  {
386  startSysEventSent = true;
387  SendMythSystemPlayEvent("PLAY_STARTED", curProgram);
388  }
389 
390  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->Playback() -- end");
391  }
392  else if (RemoteGetFreeRecorderCount())
393  {
394  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->LiveTV() -- begin");
395  if (!tv->LiveTV(showDialogs, Selection))
396  {
397  tv->SetExitPlayer(true, true);
398  quitAll = true;
399  }
400  else if (!startSysEventSent)
401  {
402  startSysEventSent = true;
403  startLivetvEventSent = true;
404  gCoreContext->SendSystemEvent("LIVETV_STARTED");
405  }
406 
407  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "tv->LiveTV() -- end");
408  }
409  else
410  {
411  if (!ConfiguredTunerCards())
412  LOG(VB_GENERAL, LOG_ERR, LOC + "No tuners configured");
413  else
414  LOG(VB_GENERAL, LOG_ERR, LOC + "No tuners free for live tv");
415  quitAll = true;
416  continue;
417  }
418 
419  tv->SetInPlayList(inPlaylist);
420  tv->setUnderNetworkControl(initByNetworkCommand);
421 
423 
424  // Process Events
425  LOG(VB_GENERAL, LOG_INFO, LOC + "Entering main playback loop.");
426  tv->PlaybackLoop();
427  LOG(VB_GENERAL, LOG_INFO, LOC + "Exiting main playback loop.");
428 
429  if (tv->GetJumpToProgram())
430  {
431  ProgramInfo *nextProgram = tv->GetLastProgram();
432 
433  tv->SetLastProgram(curProgram);
434  delete curProgram;
435  curProgram = nextProgram;
436 
437  SendMythSystemPlayEvent("PLAY_CHANGED", curProgram);
438  continue;
439  }
440 
441  tv->GetPlayerReadLock();
442  PlayerContext* context = tv->GetPlayerContext();
443  quitAll = tv->m_wantsToQuit || (context->m_errored);
444  context->LockDeletePlayer(__FILE__, __LINE__);
445  if (context->m_player && context->m_player->IsErrored())
446  playerError = context->m_player->GetError();
447  context->UnlockDeletePlayer(__FILE__, __LINE__);
448  tv->ReturnPlayerLock();
449  quitAll |= !playerError.isEmpty();
450  }
451 
452  QCoreApplication::processEvents();
453 
454  // check if the show has reached the end.
455  if (TVRec && tv->GetEndOfRecording())
456  playCompleted = true;
457 
458  bool allowrerecord = tv->GetAllowRerecord();
459  bool deleterecording = tv->m_requestDelete;
460  AcquireRelease(refs, false);
463 
464  if (curProgram)
465  {
466  if (startSysEventSent)
467  SendMythSystemPlayEvent("PLAY_STOPPED", curProgram);
468 
469  if (deleterecording)
470  {
471  QStringList list;
472  list.push_back(QString::number(curProgram->GetRecordingID()));
473  list.push_back("0"); // do not force delete
474  list.push_back(allowrerecord ? "1" : "0");
475  MythEvent me("LOCAL_PBB_DELETE_RECORDINGS", list);
476  gCoreContext->dispatch(me);
477  }
478  else if (curProgram->IsRecording())
479  {
480  lastProgramStringList.clear();
481  curProgram->ToStringList(lastProgramStringList);
482  }
483 
484  delete curProgram;
485  }
486  else if (startSysEventSent)
487  gCoreContext->SendSystemEvent("PLAY_STOPPED");
488 
489  if (!playerError.isEmpty())
490  {
491  MythScreenStack *ss = GetMythMainWindow()->GetStack("popup stack");
492  auto *dlg = new MythConfirmationDialog(ss, playerError, false);
493  if (!dlg->Create())
494  delete dlg;
495  else
496  ss->AddScreen(dlg);
497  }
498 
499  if (startLivetvEventSent)
500  gCoreContext->SendSystemEvent("LIVETV_ENDED");
501 
502  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
503 
504  return playCompleted;
505 }
506 
511 void TV::SetFuncPtr(const char* Name, void* Pointer)
512 {
513  QString name(Name);
514  if (name == "playbackbox")
515  RunPlaybackBoxPtr = reinterpret_cast<EMBEDRETURNVOID>(Pointer);
516  else if (name == "viewscheduled")
517  RunViewScheduledPtr = reinterpret_cast<EMBEDRETURNVOID>(Pointer);
518  else if (name == "programguide")
519  RunProgramGuidePtr = reinterpret_cast<EMBEDRETURNVOIDEPG>(Pointer);
520  else if (name == "programfinder")
521  RunProgramFinderPtr = reinterpret_cast<EMBEDRETURNVOIDFINDER>(Pointer);
522  else if (name == "scheduleeditor")
523  RunScheduleEditorPtr = reinterpret_cast<EMBEDRETURNVOIDSCHEDIT>(Pointer);
524 }
525 
527 {
528  REG_KEY("TV Frontend", ACTION_PLAYBACK, QT_TRANSLATE_NOOP("MythControls",
529  "Play Program"), "P");
530  REG_KEY("TV Frontend", ACTION_STOP, QT_TRANSLATE_NOOP("MythControls",
531  "Stop Program"), "");
532  REG_KEY("TV Frontend", ACTION_TOGGLERECORD, QT_TRANSLATE_NOOP("MythControls",
533  "Toggle recording status of current program"), "R");
534  REG_KEY("TV Frontend", ACTION_DAYLEFT, QT_TRANSLATE_NOOP("MythControls",
535  "Page the program guide back one day"), "Home");
536  REG_KEY("TV Frontend", ACTION_DAYRIGHT, QT_TRANSLATE_NOOP("MythControls",
537  "Page the program guide forward one day"), "End");
538  REG_KEY("TV Frontend", ACTION_PAGELEFT, QT_TRANSLATE_NOOP("MythControls",
539  "Page the program guide left"), ",,<");
540  REG_KEY("TV Frontend", ACTION_PAGERIGHT, QT_TRANSLATE_NOOP("MythControls",
541  "Page the program guide right"), ">,.");
542  REG_KEY("TV Frontend", ACTION_TOGGLEFAV, QT_TRANSLATE_NOOP("MythControls",
543  "Toggle the current channel as a favorite"), "?");
544  REG_KEY("TV Frontend", ACTION_TOGGLEPGORDER, QT_TRANSLATE_NOOP("MythControls",
545  "Reverse the channel order in the program guide"), "");
546  REG_KEY("TV Frontend", ACTION_GUIDE, QT_TRANSLATE_NOOP("MythControls",
547  "Show the Program Guide"), "S");
548  REG_KEY("TV Frontend", ACTION_FINDER, QT_TRANSLATE_NOOP("MythControls",
549  "Show the Program Finder"), "#");
550  REG_KEY("TV Frontend", ACTION_CHANNELSEARCH, QT_TRANSLATE_NOOP("MythControls",
551  "Show the Channel Search"), "");
552  REG_KEY("TV Frontend", "NEXTFAV", QT_TRANSLATE_NOOP("MythControls",
553  "Cycle through channel groups and all channels in the "
554  "program guide."), "/");
555  REG_KEY("TV Frontend", "CHANUPDATE", QT_TRANSLATE_NOOP("MythControls",
556  "Switch channels without exiting guide in Live TV mode."), "X");
557  REG_KEY("TV Frontend", ACTION_VOLUMEDOWN, QT_TRANSLATE_NOOP("MythControls",
558  "Volume down"), "[,{,F10,Volume Down");
559  REG_KEY("TV Frontend", ACTION_VOLUMEUP, QT_TRANSLATE_NOOP("MythControls",
560  "Volume up"), "],},F11,Volume Up");
561  REG_KEY("TV Frontend", ACTION_MUTEAUDIO, QT_TRANSLATE_NOOP("MythControls",
562  "Mute"), "|,\\,F9,Volume Mute");
563  REG_KEY("TV Frontend", "CYCLEAUDIOCHAN", QT_TRANSLATE_NOOP("MythControls",
564  "Cycle audio channels"), "");
565  REG_KEY("TV Frontend", "RANKINC", QT_TRANSLATE_NOOP("MythControls",
566  "Increase program or channel rank"), "Right");
567  REG_KEY("TV Frontend", "RANKDEC", QT_TRANSLATE_NOOP("MythControls",
568  "Decrease program or channel rank"), "Left");
569  REG_KEY("TV Frontend", "UPCOMING", QT_TRANSLATE_NOOP("MythControls",
570  "List upcoming episodes"), "O");
571  REG_KEY("TV Frontend", ACTION_VIEWSCHEDULED, QT_TRANSLATE_NOOP("MythControls",
572  "List scheduled upcoming episodes"), "");
573  REG_KEY("TV Frontend", ACTION_PREVRECORDED, QT_TRANSLATE_NOOP("MythControls",
574  "List previously recorded episodes"), "");
575  REG_KEY("TV Frontend", "DETAILS", QT_TRANSLATE_NOOP("MythControls",
576  "Show details"), "U");
577  REG_KEY("TV Frontend", "VIEWINPUT", QT_TRANSLATE_NOOP("MythControls",
578  "Switch Recording Input view"), "C");
579  REG_KEY("TV Frontend", "CUSTOMEDIT", QT_TRANSLATE_NOOP("MythControls",
580  "Edit Custom Record Rule"), "");
581  REG_KEY("TV Frontend", "CHANGERECGROUP", QT_TRANSLATE_NOOP("MythControls",
582  "Change Recording Group"), "");
583  REG_KEY("TV Frontend", "CHANGEGROUPVIEW", QT_TRANSLATE_NOOP("MythControls",
584  "Change Group View"), "");
585  REG_KEY("TV Frontend", ACTION_LISTRECORDEDEPISODES, QT_TRANSLATE_NOOP("MythControls",
586  "List recorded episodes"), "");
587  /*
588  * TODO DB update needs to perform the necessary conversion and delete
589  * the following upgrade code and replace bkmKeys and togBkmKeys with "" in the
590  * REG_KEY for ACTION_SETBOOKMARK and ACTION_TOGGLEBOOKMARK.
591  */
592  // Bookmarks - Instead of SELECT to add or toggle,
593  // Use separate bookmark actions. This code is to convert users
594  // who may already be using SELECT. If they are not already using
595  // this frontend then nothing will be assigned to bookmark actions.
596  QString bkmKeys;
597  QString togBkmKeys;
598  // Check if this is a new frontend - if PAUSE returns
599  // "?" then frontend is new, never used before, so we will not assign
600  // any default bookmark keys
601  QString testKey = MythMainWindow::GetKey("TV Playback", ACTION_PAUSE);
602  if (testKey != "?")
603  {
604  int alternate = gCoreContext->GetNumSetting("AltClearSavedPosition",0);
605  QString selectKeys = MythMainWindow::GetKey("Global", ACTION_SELECT);
606  if (selectKeys != "?")
607  {
608  if (alternate)
609  togBkmKeys = selectKeys;
610  else
611  bkmKeys = selectKeys;
612  }
613  }
614  REG_KEY("TV Playback", ACTION_SETBOOKMARK, QT_TRANSLATE_NOOP("MythControls",
615  "Add Bookmark"), bkmKeys);
616  REG_KEY("TV Playback", ACTION_TOGGLEBOOKMARK, QT_TRANSLATE_NOOP("MythControls",
617  "Toggle Bookmark"), togBkmKeys);
618  REG_KEY("TV Playback", "BACK", QT_TRANSLATE_NOOP("MythControls",
619  "Exit or return to DVD menu"), "Esc");
620  REG_KEY("TV Playback", ACTION_MENUCOMPACT, QT_TRANSLATE_NOOP("MythControls",
621  "Playback Compact Menu"), "Alt+M");
622  REG_KEY("TV Playback", ACTION_CLEAROSD, QT_TRANSLATE_NOOP("MythControls",
623  "Clear OSD"), "Backspace");
624  REG_KEY("TV Playback", ACTION_PAUSE, QT_TRANSLATE_NOOP("MythControls",
625  "Pause"), "P,Space");
626  REG_KEY("TV Playback", ACTION_SEEKFFWD, QT_TRANSLATE_NOOP("MythControls",
627  "Fast Forward"), "Right");
628  REG_KEY("TV Playback", ACTION_SEEKRWND, QT_TRANSLATE_NOOP("MythControls",
629  "Rewind"), "Left");
630  REG_KEY("TV Playback", ACTION_SEEKARB, QT_TRANSLATE_NOOP("MythControls",
631  "Arbitrary Seek"), "*");
632  REG_KEY("TV Playback", ACTION_SEEKABSOLUTE, QT_TRANSLATE_NOOP("MythControls",
633  "Seek to a position in seconds"), "");
634  REG_KEY("TV Playback", ACTION_CHANNELUP, QT_TRANSLATE_NOOP("MythControls",
635  "Channel up"), "Up");
636  REG_KEY("TV Playback", ACTION_CHANNELDOWN, QT_TRANSLATE_NOOP("MythControls",
637  "Channel down"), "Down");
638  REG_KEY("TV Playback", "NEXTFAV", QT_TRANSLATE_NOOP("MythControls",
639  "Switch to the next favorite channel"), "/");
640  REG_KEY("TV Playback", "PREVCHAN", QT_TRANSLATE_NOOP("MythControls",
641  "Switch to the previous channel"), "H");
642  REG_KEY("TV Playback", ACTION_JUMPFFWD, QT_TRANSLATE_NOOP("MythControls",
643  "Jump ahead"), "PgDown");
644  REG_KEY("TV Playback", ACTION_JUMPRWND, QT_TRANSLATE_NOOP("MythControls",
645  "Jump back"), "PgUp");
646  REG_KEY("TV Playback", "INFOWITHCUTLIST", QT_TRANSLATE_NOOP("MythControls",
647  "Info utilizing cutlist"), "");
648  REG_KEY("TV Playback", ACTION_JUMPBKMRK, QT_TRANSLATE_NOOP("MythControls",
649  "Jump to bookmark"), "K");
650  REG_KEY("TV Playback", "FFWDSTICKY", QT_TRANSLATE_NOOP("MythControls",
651  "Fast Forward (Sticky) or Forward one second while paused"), ">,.");
652  REG_KEY("TV Playback", "RWNDSTICKY", QT_TRANSLATE_NOOP("MythControls",
653  "Rewind (Sticky) or Rewind one second while paused"), ",,<");
654  REG_KEY("TV Playback", "NEXTSOURCE", QT_TRANSLATE_NOOP("MythControls",
655  "Next Video Source"), "Y");
656  REG_KEY("TV Playback", "PREVSOURCE", QT_TRANSLATE_NOOP("MythControls",
657  "Previous Video Source"), "");
658  REG_KEY("TV Playback", "NEXTINPUT", QT_TRANSLATE_NOOP("MythControls",
659  "Next Input"), "C");
660  REG_KEY("TV Playback", "NEXTCARD", QT_TRANSLATE_NOOP("MythControls",
661  "Next Card"), "");
662  REG_KEY("TV Playback", "SKIPCOMMERCIAL", QT_TRANSLATE_NOOP("MythControls",
663  "Skip Commercial"), "Z,End");
664  REG_KEY("TV Playback", "SKIPCOMMBACK", QT_TRANSLATE_NOOP("MythControls",
665  "Skip Commercial (Reverse)"), "Q,Home");
666  REG_KEY("TV Playback", ACTION_JUMPSTART, QT_TRANSLATE_NOOP("MythControls",
667  "Jump to the start of the recording."), "Ctrl+B");
668  REG_KEY("TV Playback", "TOGGLEBROWSE", QT_TRANSLATE_NOOP("MythControls",
669  "Toggle channel browse mode"), "O");
670  REG_KEY("TV Playback", ACTION_TOGGLERECORD, QT_TRANSLATE_NOOP("MythControls",
671  "Toggle recording status of current program"), "R");
672  REG_KEY("TV Playback", ACTION_TOGGLEFAV, QT_TRANSLATE_NOOP("MythControls",
673  "Toggle the current channel as a favorite"), "?");
674  REG_KEY("TV Playback", ACTION_VOLUMEDOWN, QT_TRANSLATE_NOOP("MythControls",
675  "Volume down"), "[,{,F10,Volume Down");
676  REG_KEY("TV Playback", ACTION_VOLUMEUP, QT_TRANSLATE_NOOP("MythControls",
677  "Volume up"), "],},F11,Volume Up");
678  REG_KEY("TV Playback", ACTION_MUTEAUDIO, QT_TRANSLATE_NOOP("MythControls",
679  "Mute"), "|,\\,F9,Volume Mute");
680  REG_KEY("TV Playback", ACTION_SETVOLUME, QT_TRANSLATE_NOOP("MythControls",
681  "Set the volume"), "");
682  REG_KEY("TV Playback", "CYCLEAUDIOCHAN", QT_TRANSLATE_NOOP("MythControls",
683  "Cycle audio channels"), "");
684  REG_KEY("TV Playback", ACTION_TOGGLEUPMIX, QT_TRANSLATE_NOOP("MythControls",
685  "Toggle audio upmixer"), "Ctrl+U");
686  REG_KEY("TV Playback", ACTION_BOTTOMLINEMOVE,
687  QT_TRANSLATE_NOOP("MythControls", "Move BottomLine off screen"), "L");
688  REG_KEY("TV Playback", ACTION_BOTTOMLINESAVE,
689  QT_TRANSLATE_NOOP("MythControls", "Save manual zoom for BottomLine"), "");
690  REG_KEY("TV Playback", "TOGGLEASPECT", QT_TRANSLATE_NOOP("MythControls",
691  "Toggle the video aspect ratio"), "Ctrl+W");
692  REG_KEY("TV Playback", "TOGGLEFILL", QT_TRANSLATE_NOOP("MythControls",
693  "Next Preconfigured Zoom mode"), "W");
694  REG_KEY("TV Playback", ACTION_TOGGLESUBS, QT_TRANSLATE_NOOP("MythControls",
695  "Toggle any captions"), "T");
696  REG_KEY("TV Playback", ACTION_ENABLESUBS, QT_TRANSLATE_NOOP("MythControls",
697  "Enable any captions"), "");
698  REG_KEY("TV Playback", ACTION_DISABLESUBS, QT_TRANSLATE_NOOP("MythControls",
699  "Disable any captions"), "");
700  REG_KEY("TV Playback", "TOGGLETTC", QT_TRANSLATE_NOOP("MythControls",
701  "Toggle Teletext Captions"),"");
702  REG_KEY("TV Playback", "TOGGLESUBTITLE", QT_TRANSLATE_NOOP("MythControls",
703  "Toggle Subtitles"), "");
704  REG_KEY("TV Playback", "TOGGLECC608", QT_TRANSLATE_NOOP("MythControls",
705  "Toggle VBI CC"), "");
706  REG_KEY("TV Playback", "TOGGLECC708", QT_TRANSLATE_NOOP("MythControls",
707  "Toggle ATSC CC"), "");
708  REG_KEY("TV Playback", "TOGGLETTM", QT_TRANSLATE_NOOP("MythControls",
709  "Toggle Teletext Menu"), "");
710  REG_KEY("TV Playback", ACTION_TOGGLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
711  "Toggle External Subtitles"), "");
712  REG_KEY("TV Playback", ACTION_ENABLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
713  "Enable External Subtitles"), "");
714  REG_KEY("TV Playback", ACTION_DISABLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
715  "Disable External Subtitles"), "");
716  REG_KEY("TV Playback", "TOGGLERAWTEXT", QT_TRANSLATE_NOOP("MythControls",
717  "Toggle Text Subtitles"), "");
718 
719  REG_KEY("TV Playback", "SELECTAUDIO_0", QT_TRANSLATE_NOOP("MythControls",
720  "Play audio track 1"), "");
721  REG_KEY("TV Playback", "SELECTAUDIO_1", QT_TRANSLATE_NOOP("MythControls",
722  "Play audio track 2"), "");
723  REG_KEY("TV Playback", "SELECTSUBTITLE_0",QT_TRANSLATE_NOOP("MythControls",
724  "Display subtitle 1"), "");
725  REG_KEY("TV Playback", "SELECTSUBTITLE_1",QT_TRANSLATE_NOOP("MythControls",
726  "Display subtitle 2"), "");
727  REG_KEY("TV Playback", "SELECTRAWTEXT_0",QT_TRANSLATE_NOOP("MythControls",
728  "Display Text Subtitle 1"), "");
729  REG_KEY("TV Playback", "SELECTCC608_0", QT_TRANSLATE_NOOP("MythControls",
730  "Display VBI CC1"), "");
731  REG_KEY("TV Playback", "SELECTCC608_1", QT_TRANSLATE_NOOP("MythControls",
732  "Display VBI CC2"), "");
733  REG_KEY("TV Playback", "SELECTCC608_2", QT_TRANSLATE_NOOP("MythControls",
734  "Display VBI CC3"), "");
735  REG_KEY("TV Playback", "SELECTCC608_3", QT_TRANSLATE_NOOP("MythControls",
736  "Display VBI CC4"), "");
737  REG_KEY("TV Playback", "SELECTCC708_0", QT_TRANSLATE_NOOP("MythControls",
738  "Display ATSC CC1"), "");
739  REG_KEY("TV Playback", "SELECTCC708_1", QT_TRANSLATE_NOOP("MythControls",
740  "Display ATSC CC2"), "");
741  REG_KEY("TV Playback", "SELECTCC708_2", QT_TRANSLATE_NOOP("MythControls",
742  "Display ATSC CC3"), "");
743  REG_KEY("TV Playback", "SELECTCC708_3", QT_TRANSLATE_NOOP("MythControls",
744  "Display ATSC CC4"), "");
745  REG_KEY("TV Playback", ACTION_ENABLEFORCEDSUBS, QT_TRANSLATE_NOOP("MythControls",
746  "Enable Forced Subtitles"), "");
747  REG_KEY("TV Playback", ACTION_DISABLEFORCEDSUBS, QT_TRANSLATE_NOOP("MythControls",
748  "Disable Forced Subtitles"), "");
749 
750  REG_KEY("TV Playback", "NEXTAUDIO", QT_TRANSLATE_NOOP("MythControls",
751  "Next audio track"), "+");
752  REG_KEY("TV Playback", "PREVAUDIO", QT_TRANSLATE_NOOP("MythControls",
753  "Previous audio track"), "-");
754  REG_KEY("TV Playback", "NEXTSUBTITLE", QT_TRANSLATE_NOOP("MythControls",
755  "Next subtitle track"), "");
756  REG_KEY("TV Playback", "PREVSUBTITLE", QT_TRANSLATE_NOOP("MythControls",
757  "Previous subtitle track"), "");
758  REG_KEY("TV Playback", "NEXTRAWTEXT", QT_TRANSLATE_NOOP("MythControls",
759  "Next Text track"), "");
760  REG_KEY("TV Playback", "PREVRAWTEXT", QT_TRANSLATE_NOOP("MythControls",
761  "Previous Text track"), "");
762  REG_KEY("TV Playback", "NEXTCC608", QT_TRANSLATE_NOOP("MythControls",
763  "Next VBI CC track"), "");
764  REG_KEY("TV Playback", "PREVCC608", QT_TRANSLATE_NOOP("MythControls",
765  "Previous VBI CC track"), "");
766  REG_KEY("TV Playback", "NEXTCC708", QT_TRANSLATE_NOOP("MythControls",
767  "Next ATSC CC track"), "");
768  REG_KEY("TV Playback", "PREVCC708", QT_TRANSLATE_NOOP("MythControls",
769  "Previous ATSC CC track"), "");
770  REG_KEY("TV Playback", "NEXTCC", QT_TRANSLATE_NOOP("MythControls",
771  "Next of any captions"), "");
772 
773  REG_KEY("TV Playback", "NEXTSCAN", QT_TRANSLATE_NOOP("MythControls",
774  "Next video scan overidemode"), "");
775  REG_KEY("TV Playback", "QUEUETRANSCODE", QT_TRANSLATE_NOOP("MythControls",
776  "Queue the current recording for transcoding"), "X");
777  REG_KEY("TV Playback", "SPEEDINC", QT_TRANSLATE_NOOP("MythControls",
778  "Increase the playback speed"), "U");
779  REG_KEY("TV Playback", "SPEEDDEC", QT_TRANSLATE_NOOP("MythControls",
780  "Decrease the playback speed"), "J");
781  REG_KEY("TV Playback", "ADJUSTSTRETCH", QT_TRANSLATE_NOOP("MythControls",
782  "Turn on time stretch control"), "A");
783  REG_KEY("TV Playback", "STRETCHINC", QT_TRANSLATE_NOOP("MythControls",
784  "Increase time stretch speed"), "");
785  REG_KEY("TV Playback", "STRETCHDEC", QT_TRANSLATE_NOOP("MythControls",
786  "Decrease time stretch speed"), "");
787  REG_KEY("TV Playback", "TOGGLESTRETCH", QT_TRANSLATE_NOOP("MythControls",
788  "Toggle time stretch speed"), "");
789  REG_KEY("TV Playback", ACTION_TOGGELAUDIOSYNC,
790  QT_TRANSLATE_NOOP("MythControls",
791  "Turn on audio sync adjustment controls"), "");
792  REG_KEY("TV Playback", ACTION_SETAUDIOSYNC,
793  QT_TRANSLATE_NOOP("MythControls",
794  "Set the audio sync adjustment"), "");
795  REG_KEY("TV Playback", "TOGGLEPICCONTROLS",
796  QT_TRANSLATE_NOOP("MythControls", "Playback picture adjustments"),
797  "F");
798  REG_KEY("TV Playback", ACTION_SETBRIGHTNESS,
799  QT_TRANSLATE_NOOP("MythControls", "Set the picture brightness"), "");
800  REG_KEY("TV Playback", ACTION_SETCONTRAST,
801  QT_TRANSLATE_NOOP("MythControls", "Set the picture contrast"), "");
802  REG_KEY("TV Playback", ACTION_SETCOLOUR,
803  QT_TRANSLATE_NOOP("MythControls", "Set the picture color"), "");
804  REG_KEY("TV Playback", ACTION_SETHUE,
805  QT_TRANSLATE_NOOP("MythControls", "Set the picture hue"), "");
806  REG_KEY("TV Playback", ACTION_TOGGLECHANCONTROLS,
807  QT_TRANSLATE_NOOP("MythControls", "Recording picture adjustments "
808  "for this channel"), "Ctrl+G");
809  REG_KEY("TV Playback", ACTION_TOGGLERECCONTROLS,
810  QT_TRANSLATE_NOOP("MythControls", "Recording picture adjustments "
811  "for this recorder"), "G");
812  REG_KEY("TV Playback", "CYCLECOMMSKIPMODE",
813  QT_TRANSLATE_NOOP("MythControls", "Cycle Commercial Skip mode"),
814  "");
815  REG_KEY("TV Playback", ACTION_GUIDE, QT_TRANSLATE_NOOP("MythControls",
816  "Show the Program Guide"), "S");
817  REG_KEY("TV Playback", ACTION_FINDER, QT_TRANSLATE_NOOP("MythControls",
818  "Show the Program Finder"), "#");
819  REG_KEY("TV Playback", ACTION_TOGGLESLEEP, QT_TRANSLATE_NOOP("MythControls",
820  "Toggle the Sleep Timer"), "F8");
821  REG_KEY("TV Playback", ACTION_PLAY, QT_TRANSLATE_NOOP("MythControls", "Play"),
822  "Ctrl+P");
823  REG_KEY("TV Playback", ACTION_JUMPPREV, QT_TRANSLATE_NOOP("MythControls",
824  "Jump to previously played recording"), "");
825  REG_KEY("TV Playback", ACTION_JUMPREC, QT_TRANSLATE_NOOP("MythControls",
826  "Display menu of recorded programs to jump to"), "");
827  REG_KEY("TV Playback", ACTION_VIEWSCHEDULED, QT_TRANSLATE_NOOP("MythControls",
828  "Display scheduled recording list"), "");
829  REG_KEY("TV Playback", ACTION_PREVRECORDED, QT_TRANSLATE_NOOP("MythControls",
830  "Display previously recorded episodes"), "");
831  REG_KEY("TV Playback", ACTION_SIGNALMON, QT_TRANSLATE_NOOP("MythControls",
832  "Monitor Signal Quality"), "Alt+F7");
833  REG_KEY("TV Playback", ACTION_JUMPTODVDROOTMENU,
834  QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Root Menu"), "");
835  REG_KEY("TV Playback", ACTION_JUMPTOPOPUPMENU,
836  QT_TRANSLATE_NOOP("MythControls", "Jump to the Popup Menu"), "");
837  REG_KEY("TV Playback", ACTION_JUMPTODVDCHAPTERMENU,
838  QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Chapter Menu"), "");
839  REG_KEY("TV Playback", ACTION_JUMPTODVDTITLEMENU,
840  QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Title Menu"), "");
841  REG_KEY("TV Playback", ACTION_EXITSHOWNOPROMPTS,
842  QT_TRANSLATE_NOOP("MythControls", "Exit Show without any prompts"),
843  "");
844  REG_KEY("TV Playback", ACTION_JUMPCHAPTER, QT_TRANSLATE_NOOP("MythControls",
845  "Jump to a chapter"), "");
846  REG_KEY("TV Playback", ACTION_SWITCHTITLE, QT_TRANSLATE_NOOP("MythControls",
847  "Switch title"), "");
848  REG_KEY("TV Playback", ACTION_SWITCHANGLE, QT_TRANSLATE_NOOP("MythControls",
849  "Switch angle"), "");
850  REG_KEY("TV Playback", ACTION_OSDNAVIGATION, QT_TRANSLATE_NOOP("MythControls",
851  "OSD Navigation"), "");
852  REG_KEY("TV Playback", ACTION_ZOOMUP, QT_TRANSLATE_NOOP("MythControls",
853  "Zoom mode - shift up"), "");
854  REG_KEY("TV Playback", ACTION_ZOOMDOWN, QT_TRANSLATE_NOOP("MythControls",
855  "Zoom mode - shift down"), "");
856  REG_KEY("TV Playback", ACTION_ZOOMLEFT, QT_TRANSLATE_NOOP("MythControls",
857  "Zoom mode - shift left"), "");
858  REG_KEY("TV Playback", ACTION_ZOOMRIGHT, QT_TRANSLATE_NOOP("MythControls",
859  "Zoom mode - shift right"), "");
860  REG_KEY("TV Playback", ACTION_ZOOMASPECTUP,
861  QT_TRANSLATE_NOOP("MythControls",
862  "Zoom mode - increase aspect ratio"), "");
863  REG_KEY("TV Playback", ACTION_ZOOMASPECTDOWN,
864  QT_TRANSLATE_NOOP("MythControls",
865  "Zoom mode - decrease aspect ratio"), "");
866  REG_KEY("TV Playback", ACTION_ZOOMIN, QT_TRANSLATE_NOOP("MythControls",
867  "Zoom mode - zoom in"), "");
868  REG_KEY("TV Playback", ACTION_ZOOMOUT, QT_TRANSLATE_NOOP("MythControls",
869  "Zoom mode - zoom out"), "");
870  REG_KEY("TV Playback", ACTION_ZOOMVERTICALIN,
871  QT_TRANSLATE_NOOP("MythControls",
872  "Zoom mode - vertical zoom in"), "8");
873  REG_KEY("TV Playback", ACTION_ZOOMVERTICALOUT,
874  QT_TRANSLATE_NOOP("MythControls",
875  "Zoom mode - vertical zoom out"), "2");
876  REG_KEY("TV Playback", ACTION_ZOOMHORIZONTALIN,
877  QT_TRANSLATE_NOOP("MythControls",
878  "Zoom mode - horizontal zoom in"), "6");
879  REG_KEY("TV Playback", ACTION_ZOOMHORIZONTALOUT,
880  QT_TRANSLATE_NOOP("MythControls",
881  "Zoom mode - horizontal zoom out"), "4");
882  REG_KEY("TV Playback", ACTION_ZOOMQUIT, QT_TRANSLATE_NOOP("MythControls",
883  "Zoom mode - quit and abandon changes"), "");
884  REG_KEY("TV Playback", ACTION_ZOOMCOMMIT, QT_TRANSLATE_NOOP("MythControls",
885  "Zoom mode - commit changes"), "");
886 
887  /* Interactive Television keys */
888  REG_KEY("TV Playback", ACTION_MENURED, QT_TRANSLATE_NOOP("MythControls",
889  "Menu Red"), "F2");
890  REG_KEY("TV Playback", ACTION_MENUGREEN, QT_TRANSLATE_NOOP("MythControls",
891  "Menu Green"), "F3");
892  REG_KEY("TV Playback", ACTION_MENUYELLOW, QT_TRANSLATE_NOOP("MythControls",
893  "Menu Yellow"), "F4");
894  REG_KEY("TV Playback", ACTION_MENUBLUE, QT_TRANSLATE_NOOP("MythControls",
895  "Menu Blue"), "F5");
896  REG_KEY("TV Playback", ACTION_TEXTEXIT, QT_TRANSLATE_NOOP("MythControls",
897  "Menu Exit"), "F6");
898  REG_KEY("TV Playback", ACTION_MENUTEXT, QT_TRANSLATE_NOOP("MythControls",
899  "Menu Text"), "F7");
900  REG_KEY("TV Playback", ACTION_MENUEPG, QT_TRANSLATE_NOOP("MythControls",
901  "Menu EPG"), "F12");
902 
903  /* Editing keys */
904  REG_KEY("TV Editing", ACTION_CLEARMAP, QT_TRANSLATE_NOOP("MythControls",
905  "Clear editing cut points"), "C,Q,Home");
906  REG_KEY("TV Editing", ACTION_INVERTMAP, QT_TRANSLATE_NOOP("MythControls",
907  "Invert Begin/End cut points"),"I");
908  REG_KEY("TV Editing", ACTION_SAVEMAP, QT_TRANSLATE_NOOP("MythControls",
909  "Save cuts"),"");
910  REG_KEY("TV Editing", ACTION_LOADCOMMSKIP,QT_TRANSLATE_NOOP("MythControls",
911  "Load cuts from detected commercials"), "Z,End");
912  REG_KEY("TV Editing", ACTION_NEXTCUT, QT_TRANSLATE_NOOP("MythControls",
913  "Jump to the next cut point"), "PgDown");
914  REG_KEY("TV Editing", ACTION_PREVCUT, QT_TRANSLATE_NOOP("MythControls",
915  "Jump to the previous cut point"), "PgUp");
916  REG_KEY("TV Editing", ACTION_BIGJUMPREW, QT_TRANSLATE_NOOP("MythControls",
917  "Jump back 10x the normal amount"), ",,<");
918  REG_KEY("TV Editing", ACTION_BIGJUMPFWD, QT_TRANSLATE_NOOP("MythControls",
919  "Jump forward 10x the normal amount"), ">,.");
920  REG_KEY("TV Editing", ACTION_MENUCOMPACT, QT_TRANSLATE_NOOP("MythControls",
921  "Cut point editor compact menu"), "Alt+M");
922 
923  /* Teletext keys */
924  REG_KEY("Teletext Menu", ACTION_NEXTPAGE, QT_TRANSLATE_NOOP("MythControls",
925  "Next Page"), "Down");
926  REG_KEY("Teletext Menu", ACTION_PREVPAGE, QT_TRANSLATE_NOOP("MythControls",
927  "Previous Page"), "Up");
928  REG_KEY("Teletext Menu", ACTION_NEXTSUBPAGE, QT_TRANSLATE_NOOP("MythControls",
929  "Next Subpage"), "Right");
930  REG_KEY("Teletext Menu", ACTION_PREVSUBPAGE, QT_TRANSLATE_NOOP("MythControls",
931  "Previous Subpage"), "Left");
932  REG_KEY("Teletext Menu", ACTION_TOGGLETT, QT_TRANSLATE_NOOP("MythControls",
933  "Toggle Teletext"), "T");
934  REG_KEY("Teletext Menu", ACTION_MENURED, QT_TRANSLATE_NOOP("MythControls",
935  "Menu Red"), "F2");
936  REG_KEY("Teletext Menu", ACTION_MENUGREEN, QT_TRANSLATE_NOOP("MythControls",
937  "Menu Green"), "F3");
938  REG_KEY("Teletext Menu", ACTION_MENUYELLOW, QT_TRANSLATE_NOOP("MythControls",
939  "Menu Yellow"), "F4");
940  REG_KEY("Teletext Menu", ACTION_MENUBLUE, QT_TRANSLATE_NOOP("MythControls",
941  "Menu Blue"), "F5");
942  REG_KEY("Teletext Menu", ACTION_MENUWHITE, QT_TRANSLATE_NOOP("MythControls",
943  "Menu White"), "F6");
944  REG_KEY("Teletext Menu", ACTION_TOGGLEBACKGROUND,
945  QT_TRANSLATE_NOOP("MythControls", "Toggle Background"), "F7");
946  REG_KEY("Teletext Menu", ACTION_REVEAL, QT_TRANSLATE_NOOP("MythControls",
947  "Reveal hidden Text"), "F8");
948 
949  /* Visualisations */
950  REG_KEY("TV Playback", ACTION_TOGGLEVISUALISATION,
951  QT_TRANSLATE_NOOP("MythControls", "Toggle audio visualisation"), "");
952 
953  /* OSD playback information screen */
954  REG_KEY("TV Playback", ACTION_TOGGLEOSDDEBUG,
955  QT_TRANSLATE_NOOP("MythControls", "Toggle OSD playback information"), "");
956 
957  /* 3D/Frame compatible/Stereoscopic TV */
958  REG_KEY("TV Playback", ACTION_3DNONE,
959  QT_TRANSLATE_NOOP("MythControls", "Auto 3D"), "");
960  REG_KEY("TV Playback", ACTION_3DIGNORE,
961  QT_TRANSLATE_NOOP("MythControls", "Ignore 3D"), "");
962  REG_KEY("TV Playback", ACTION_3DSIDEBYSIDEDISCARD,
963  QT_TRANSLATE_NOOP("MythControls", "Discard 3D Side by Side"), "");
964  REG_KEY("TV Playback", ACTION_3DTOPANDBOTTOMDISCARD,
965  QT_TRANSLATE_NOOP("MythControls", "Discard 3D Top and Bottom"), "");
966 
967 /*
968  keys already used:
969 
970  Global: I M 0123456789
971  Playback: ABCDEFGH JK NOPQRSTUVWXYZ
972  Frontend: CD OP R U XY 01 3 7 9
973  Editing: C E I Q Z
974  Teletext: T
975 
976  Playback: <>,.?/|[]{}\+-*#^
977  Frontend: <>,.?/
978  Editing: <>,.
979 
980  Global: PgDown, PgUp, Right, Left, Home, End, Up, Down,
981  Playback: PgDown, PgUp, Right, Left, Home, End, Up, Down, Backspace,
982  Frontend: Right, Left, Home, End
983  Editing: PgDown, PgUp, Home, End
984  Teletext: Right, Left, Up, Down,
985 
986  Global: Return, Enter, Space, Esc
987 
988  Global: F1,
989  Playback: F7,F8,F9,F10,F11
990  Teletext F2,F3,F4,F5,F6,F7,F8
991  ITV F2,F3,F4,F5,F6,F7,F12
992 
993  Playback: Ctrl-B,Ctrl-G,Ctrl-Y,Ctrl-U,L
994 */
995 }
996 
998 {
999  m_mainWindow->ClearKeyContext("TV Frontend");
1000  m_mainWindow->ClearKeyContext("TV Playback");
1001  m_mainWindow->ClearKeyContext("TV Editing");
1002  m_mainWindow->ClearKeyContext("Teletext Menu");
1003  InitKeys();
1004 }
1005 
1006 
1008 {
1009  public:
1010  SleepTimerInfo(QString String, unsigned long Seconds)
1011  : dispString(std::move(String)),
1012  seconds(Seconds) {}
1013  QString dispString;
1014  unsigned long seconds;
1015 };
1016 
1017 const vector<TV::SleepTimerInfo> TV::s_sleepTimes =
1018 {
1019  { tr("Off", "Sleep timer"), 0 },
1020  { tr("30m", "Sleep timer"), 30*60 },
1021  { tr("1h", "Sleep timer"), 60*60 },
1022  { tr("1h30m", "Sleep timer"), 90*60 },
1023  { tr("2h", "Sleep timer"), 120*60}
1024 };
1025 
1038  : ReferenceCounter("TV"),
1039  TVBrowseHelper(this),
1040  m_mainWindow(MainWindow)
1041 
1042 {
1043  LOG(VB_GENERAL, LOG_INFO, LOC + "Creating TV object");
1044  m_ctorTime.start();
1045 
1046  QObject::setObjectName("TV");
1048  connect(this, &TV::RequestEmbedding, this, &TV::Embed);
1049  InitFromDB();
1050 
1051 #ifdef Q_OS_ANDROID
1052  connect(qApp, &QApplication::applicationStateChanged, this, &TV::onApplicationStateChange);
1053 #endif
1054 
1055  if (m_mainWindow)
1057 
1058  // Setup various state signals
1059  connect(this, &TV::ChangeAudioOffset, [&]() { m_audiosyncAdjustment = true; });
1060  connect(this, &TV::AdjustSubtitleZoom, [&]() { m_subtitleZoomAdjustment = true; });
1061  connect(this, &TV::AdjustSubtitleDelay, [&]() { m_subtitleDelayAdjustment = true; });
1062 
1063  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Finished creating TV object");
1064 }
1065 
1067 {
1068  QMap<QString,QString> kv;
1069  kv["LiveTVIdleTimeout"] = "0";
1070  kv["BrowseMaxForward"] = "240";
1071  kv["PlaybackExitPrompt"] = "0";
1072  kv["AutomaticSetWatched"] = "0";
1073  kv["EndOfRecordingExitPrompt"] = "0";
1074  kv["JumpToProgramOSD"] = "1";
1075  kv["GuiSizeForTV"] = "0";
1076  kv["UseVideoModes"] = "0";
1077  kv["ClearSavedPosition"] = "1";
1078  kv["JobsRunOnRecordHost"] = "0";
1079  kv["ContinueEmbeddedTVPlay"] = "0";
1080  kv["UseFixedWindowSize"] = "1";
1081  kv["RunFrontendInWindow"] = "0";
1082  kv["PersistentBrowseMode"] = "0";
1083  kv["BrowseAllTuners"] = "0";
1084  kv["ChannelOrdering"] = "channum";
1085 
1086  kv["CustomFilters"] = "";
1087  kv["ChannelFormat"] = "<num> <sign>";
1088 
1089  kv["TryUnflaggedSkip"] = "0";
1090 
1091  kv["ChannelGroupDefault"] = "-1";
1092  kv["BrowseChannelGroup"] = "0";
1093  kv["SmartForward"] = "0";
1094  kv["FFRewReposTime"] = "100";
1095  kv["FFRewReverse"] = "1";
1096 
1097  kv["BrowseChannelGroup"] = "0";
1098  kv["ChannelGroupDefault"] = "-1";
1099  kv["ChannelGroupRememberLast"] = "0";
1100 
1101  kv["VbiFormat"] = "";
1102  kv["DecodeVBIFormat"] = "";
1103 
1104  // these need exactly 12 items, comma cant be used as it is the delimiter
1105  kv["PlaybackScreenPressKeyMap"] = "P,Up,Z,],Left,Return,Return,Right,A,Down,Q,[";
1106  kv["LiveTVScreenPressKeyMap"] = "P,Up,Z,S,Left,Return,Return,Right,A,Down,Q,F";
1107 
1108  constexpr std::array<const int,8> ff_rew_def { 3, 5, 10, 20, 30, 60, 120, 180 };
1109  for (size_t i = 0; i < ff_rew_def.size(); i++)
1110  kv[QString("FFRewSpeed%1").arg(i)] = QString::number(ff_rew_def[i]);
1111 
1113 
1114  m_screenPressKeyMapPlayback = ConvertScreenPressKeyMap(kv["PlaybackScreenPressKeyMap"]);
1115  m_screenPressKeyMapLiveTV = ConvertScreenPressKeyMap(kv["LiveTVScreenPressKeyMap"]);
1116 
1117  QString db_channel_ordering;
1118 
1119  // convert from minutes to ms.
1120  m_dbIdleTimeout = kv["LiveTVIdleTimeout"].toUInt() * 60 * 1000;
1121  uint db_browse_max_forward = kv["BrowseMaxForward"].toUInt() * 60;
1122  m_dbPlaybackExitPrompt = kv["PlaybackExitPrompt"].toInt();
1123  m_dbAutoSetWatched = (kv["AutomaticSetWatched"].toInt() != 0);
1124  m_dbEndOfRecExitPrompt = (kv["EndOfRecordingExitPrompt"].toInt() != 0);
1125  m_dbJumpPreferOsd = (kv["JumpToProgramOSD"].toInt() != 0);
1126  m_dbUseGuiSizeForTv = (kv["GuiSizeForTV"].toInt() != 0);
1127  m_dbUseVideoModes = (kv["UseVideoModes"].toInt() != 0);
1128  m_dbClearSavedPosition = (kv["ClearSavedPosition"].toInt() != 0);
1129  m_dbRunJobsOnRemote = (kv["JobsRunOnRecordHost"].toInt() != 0);
1130  m_dbContinueEmbedded = (kv["ContinueEmbeddedTVPlay"].toInt() != 0);
1131  m_dbRunFrontendInWindow= (kv["RunFrontendInWindow"].toInt() != 0);
1132  m_dbBrowseAlways = (kv["PersistentBrowseMode"].toInt() != 0);
1133  m_dbBrowseAllTuners = (kv["BrowseAllTuners"].toInt() != 0);
1134  db_channel_ordering = kv["ChannelOrdering"];
1135  m_baseFilters += kv["CustomFilters"];
1136  m_dbChannelFormat = kv["ChannelFormat"];
1137  m_tryUnflaggedSkip = (kv["TryUnflaggedSkip"].toInt() != 0);
1138  m_smartForward = (kv["SmartForward"].toInt() != 0);
1139  m_ffRewRepos = kv["FFRewReposTime"].toFloat() * 0.01F;
1140  m_ffRewReverse = (kv["FFRewReverse"].toInt() != 0);
1141 
1142  m_dbUseChannelGroups = (kv["BrowseChannelGroup"].toInt() != 0);
1143  m_dbRememberLastChannelGroup = (kv["ChannelGroupRememberLast"].toInt() != 0);
1144  m_channelGroupId = kv["ChannelGroupDefault"].toInt();
1145 
1146  QString beVBI = kv["VbiFormat"];
1147  QString feVBI = kv["DecodeVBIFormat"];
1148 
1149  RecordingRule record;
1150  record.LoadTemplate("Default");
1151  m_dbAutoexpireDefault = static_cast<uint>(record.m_autoExpire);
1152 
1154  {
1156  if (m_channelGroupId > -1)
1157  {
1158  m_channelGroupChannelList = ChannelUtil::GetChannels(0, true, "channum, callsign",
1159  static_cast<uint>(m_channelGroupId));
1161  }
1162  }
1163 
1164  for (size_t i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
1165  m_ffRewSpeeds.push_back(kv[QString("FFRewSpeed%1").arg(i)].toInt());
1166 
1167  // process it..
1168  BrowseInit(db_browse_max_forward, m_dbBrowseAllTuners, m_dbUseChannelGroups, db_channel_ordering);
1169 
1170  m_vbimode = VBIMode::Parse(!feVBI.isEmpty() ? feVBI : beVBI);
1171 
1172  gCoreContext->addListener(this);
1174 }
1175 
1180 bool TV::Init()
1181 {
1182  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
1183 
1184  if (!m_mainWindow)
1185  {
1186  LOG(VB_GENERAL, LOG_ERR, LOC + "No MythMainWindow");
1187  return false;
1188  }
1189 
1190  bool fullscreen = !m_dbUseGuiSizeForTv;
1191  m_savedGuiBounds = QRect(m_mainWindow->geometry().topLeft(), m_mainWindow->size());
1192 
1193  // adjust for window manager wierdness.
1194  QRect screen = m_mainWindow->GetScreenRect();
1195  if ((abs(m_savedGuiBounds.x() - screen.left()) < 3) &&
1196  (abs(m_savedGuiBounds.y() - screen.top()) < 3))
1197  {
1198  m_savedGuiBounds = QRect(screen.topLeft(), m_mainWindow->size());
1199  }
1200 
1201  // if width && height are zero users expect fullscreen playback
1202  if (!fullscreen)
1203  {
1204  int gui_width = 0;
1205  int gui_height = 0;
1206  gCoreContext->GetResolutionSetting("Gui", gui_width, gui_height);
1207  fullscreen |= (0 == gui_width && 0 == gui_height);
1208  }
1209 
1211  if (fullscreen)
1213 
1214  // player window sizing
1215  MythScreenStack *mainStack = m_mainWindow->GetMainStack();
1216 
1217  m_myWindow = new TvPlayWindow(mainStack, "Playback");
1218 
1219  if (m_myWindow->Create())
1220  {
1221  mainStack->AddScreen(m_myWindow, false);
1222  LOG(VB_GENERAL, LOG_INFO, LOC + "Created TvPlayWindow.");
1223  }
1224  else
1225  {
1226  delete m_myWindow;
1227  m_myWindow = nullptr;
1228  }
1229 
1231  m_mainWindow->GetPaintWindow()->update();
1232  m_mainWindow->installEventFilter(this);
1233  QCoreApplication::processEvents();
1234 
1239  m_playerContext.m_tsNormal = 1.0F;
1240  ReturnPlayerLock();
1241 
1242  m_sleepIndex = 0;
1243 
1244  emit ChangeOSDPositionUpdates(false);
1245 
1247  ClearInputQueues(false);
1248  ReturnPlayerLock();
1249 
1250  m_switchToRec = nullptr;
1251  SetExitPlayer(false, false);
1252 
1254  m_lcdTimerId = StartTimer(1, __LINE__);
1257 
1258  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
1259  return true;
1260 }
1261 
1263 {
1264  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
1265 
1266  BrowseStop();
1267  BrowseWait();
1268 
1271 
1272  if (m_mainWindow)
1273  {
1274  m_mainWindow->removeEventFilter(this);
1275  if (m_weDisabledGUI)
1277  }
1278 
1279  if (m_myWindow)
1280  {
1281  m_myWindow->Close();
1282  m_myWindow = nullptr;
1283  }
1284 
1285  LOG(VB_PLAYBACK, LOG_INFO, LOC + "-- lock");
1286 
1287  // restore window to gui size and position
1288  if (m_mainWindow)
1289  {
1290  MythDisplay* display = m_mainWindow->GetDisplay();
1291  if (display->UsingVideoModes())
1292  {
1293  bool hide = display->NextModeIsLarger(display->GetGUIResolution());
1294  if (hide)
1295  m_mainWindow->hide();
1296  display->SwitchToGUI(true);
1297  if (hide)
1298  m_mainWindow->Show();
1299  }
1301  #ifdef Q_OS_ANDROID
1302  m_mainWindow->Show();
1303  #else
1304  m_mainWindow->show();
1305  #endif
1306  m_mainWindow->PauseIdleTimer(false);
1307  }
1308 
1309 
1310  delete m_lastProgram;
1311 
1312  if (LCD *lcd = LCD::Get())
1313  {
1314  lcd->setFunctionLEDs(FUNC_TV, false);
1315  lcd->setFunctionLEDs(FUNC_MOVIE, false);
1316  lcd->switchToTime();
1317  }
1318 
1319  m_playerLock.lockForWrite();
1321  m_player = nullptr;
1322  m_playerLock.unlock();
1323 
1324  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
1325 }
1326 
1331 {
1332  while (true)
1333  {
1334  QCoreApplication::processEvents();
1336  {
1337  m_wantsToQuit = true;
1338  return;
1339  }
1340 
1341  TVState state = GetState();
1342  if ((kState_Error == state) || (kState_None == state))
1343  return;
1344 
1345  if (kState_ChangingState == state)
1346  continue;
1347 
1349  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
1350  if (m_player && !m_player->IsErrored())
1351  {
1352  m_player->EventLoop();
1353  m_player->VideoLoop();
1354  }
1355  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
1356  ReturnPlayerLock();
1357 
1359  return;
1360  }
1361 }
1362 
1366 void TV::UpdateChannelList(int GroupID)
1367 {
1368  if (!m_dbUseChannelGroups)
1369  return;
1370 
1371  QMutexLocker locker(&m_channelGroupLock);
1372  if (GroupID == m_channelGroupId)
1373  return;
1374 
1375  ChannelInfoList list;
1376  if (GroupID >= 0)
1377  {
1378  list = ChannelUtil::GetChannels(0, true, "channum, callsign", static_cast<uint>(GroupID));
1379  ChannelUtil::SortChannels(list, "channum", true);
1380  }
1381 
1382  m_channelGroupId = GroupID;
1384 
1386  gCoreContext->SaveSetting("ChannelGroupDefault", m_channelGroupId);
1387 }
1388 
1390 {
1393  ret = m_playerContext.GetState();
1394  return ret;
1395 }
1396 
1397 // XXX what about subtitlezoom?
1399 {
1400  QVariantMap status;
1401 
1403  status.insert("state", StateToString(GetState()));
1404  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
1406  {
1407  status.insert("title", m_playerContext.m_playingInfo->GetTitle());
1408  status.insert("subtitle", m_playerContext.m_playingInfo->GetSubtitle());
1409  status.insert("starttime", m_playerContext.m_playingInfo->GetRecordingStartTime()
1410  .toUTC().toString("yyyy-MM-ddThh:mm:ssZ"));
1411  status.insert("chanid", QString::number(m_playerContext.m_playingInfo->GetChanID()));
1412  status.insert("programid", m_playerContext.m_playingInfo->GetProgramID());
1413  status.insert("pathname", m_playerContext.m_playingInfo->GetPathname());
1414  }
1415  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
1416  osdInfo info;
1418  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
1419  if (m_player)
1420  {
1421  if (!info.text["totalchapters"].isEmpty())
1422  {
1423  QList<long long> chapters;
1424  m_player->GetChapterTimes(chapters);
1425  QVariantList var;
1426  for (long long chapter : qAsConst(chapters))
1427  var << QVariant(chapter);
1428  status.insert("chaptertimes", var);
1429  }
1430 
1432  QVariantMap tracks;
1433 
1434  QStringList list = m_player->GetTracks(kTrackTypeSubtitle);
1435  int currenttrack = -1;
1436  if (!list.isEmpty() && (kDisplayAVSubtitle == capmode))
1437  currenttrack = m_player->GetTrack(kTrackTypeSubtitle);
1438  for (int i = 0; i < list.size(); i++)
1439  {
1440  if (i == currenttrack)
1441  status.insert("currentsubtitletrack", list[i]);
1442  tracks.insert("SELECTSUBTITLE_" + QString::number(i), list[i]);
1443  }
1444 
1446  currenttrack = -1;
1447  if (!list.isEmpty() && (kDisplayTeletextCaptions == capmode))
1448  currenttrack = m_player->GetTrack(kTrackTypeTeletextCaptions);
1449  for (int i = 0; i < list.size(); i++)
1450  {
1451  if (i == currenttrack)
1452  status.insert("currentsubtitletrack", list[i]);
1453  tracks.insert("SELECTTTC_" + QString::number(i), list[i]);
1454  }
1455 
1457  currenttrack = -1;
1458  if (!list.isEmpty() && (kDisplayCC708 == capmode))
1459  currenttrack = m_player->GetTrack(kTrackTypeCC708);
1460  for (int i = 0; i < list.size(); i++)
1461  {
1462  if (i == currenttrack)
1463  status.insert("currentsubtitletrack", list[i]);
1464  tracks.insert("SELECTCC708_" + QString::number(i), list[i]);
1465  }
1466 
1468  currenttrack = -1;
1469  if (!list.isEmpty() && (kDisplayCC608 == capmode))
1470  currenttrack = m_player->GetTrack(kTrackTypeCC608);
1471  for (int i = 0; i < list.size(); i++)
1472  {
1473  if (i == currenttrack)
1474  status.insert("currentsubtitletrack", list[i]);
1475  tracks.insert("SELECTCC608_" + QString::number(i), list[i]);
1476  }
1477 
1479  currenttrack = -1;
1480  if (!list.isEmpty() && (kDisplayRawTextSubtitle == capmode))
1481  currenttrack = m_player->GetTrack(kTrackTypeRawText);
1482  for (int i = 0; i < list.size(); i++)
1483  {
1484  if (i == currenttrack)
1485  status.insert("currentsubtitletrack", list[i]);
1486  tracks.insert("SELECTRAWTEXT_" + QString::number(i), list[i]);
1487  }
1488 
1490  {
1491  if (kDisplayTextSubtitle == capmode)
1492  status.insert("currentsubtitletrack", tr("External Subtitles"));
1493  tracks.insert(ACTION_ENABLEEXTTEXT, tr("External Subtitles"));
1494  }
1495 
1496  status.insert("totalsubtitletracks", tracks.size());
1497  if (!tracks.isEmpty())
1498  status.insert("subtitletracks", tracks);
1499 
1500  tracks.clear();
1502  currenttrack = m_player->GetTrack(kTrackTypeAudio);
1503  for (int i = 0; i < list.size(); i++)
1504  {
1505  if (i == currenttrack)
1506  status.insert("currentaudiotrack", list[i]);
1507  tracks.insert("SELECTAUDIO_" + QString::number(i), list[i]);
1508  }
1509 
1510  status.insert("totalaudiotracks", tracks.size());
1511  if (!tracks.isEmpty())
1512  status.insert("audiotracks", tracks);
1513 
1514  status.insert("playspeed", m_player->GetPlaySpeed());
1515  status.insert("audiosyncoffset", static_cast<long long>(m_audioState.m_audioOffset));
1516 
1518  {
1519  status.insert("volume", m_audioState.m_volume);
1520  status.insert("mute", m_audioState.m_muteState);
1521  }
1522 
1525  status.insert("brightness", m_videoColourState.GetValue(kPictureAttribute_Brightness));
1527  status.insert("contrast", m_videoColourState.GetValue(kPictureAttribute_Contrast));
1529  status.insert("colour", m_videoColourState.GetValue(kPictureAttribute_Colour));
1530  if (supp & kPictureAttributeSupported_Hue)
1531  status.insert("hue", m_videoColourState.GetValue(kPictureAttribute_Hue));
1532  }
1533  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
1534  ReturnPlayerLock();
1535 
1536  for (auto tit =info.text.cbegin(); tit != info.text.cend(); ++tit)
1537  status.insert(tit.key(), tit.value());
1538 
1539  QHashIterator<QString,int> vit(info.values);
1540  while (vit.hasNext())
1541  {
1542  vit.next();
1543  status.insert(vit.key(), vit.value());
1544  }
1545 
1547 }
1548 
1554 bool TV::LiveTV(bool ShowDialogs, const ChannelInfoList &Selection)
1555 {
1556  m_requestDelete = false;
1557  m_allowRerecord = false;
1558  m_jumpToProgram = false;
1559 
1561  if (m_playerContext.GetState() == kState_None && RequestNextRecorder(ShowDialogs, Selection))
1562  {
1565  m_switchToRec = nullptr;
1566 
1567  // Start Idle Timer
1568  if (m_dbIdleTimeout > 0)
1569  {
1570  m_idleTimerId = StartTimer(static_cast<int>(m_dbIdleTimeout), __LINE__);
1571  LOG(VB_GENERAL, LOG_INFO, QString("Using Idle Timer. %1 minutes").arg(m_dbIdleTimeout * (1.0 / 60000.0)));
1572  }
1573 
1574  ReturnPlayerLock();
1575  return true;
1576  }
1577  ReturnPlayerLock();
1578  return false;
1579 }
1580 
1581 bool TV::RequestNextRecorder(bool ShowDialogs, const ChannelInfoList &Selection)
1582 {
1583  m_playerContext.SetRecorder(nullptr);
1584 
1585  RemoteEncoder *testrec = nullptr;
1586  if (m_switchToRec)
1587  {
1588  // If this is set we, already got a new recorder in SwitchCards()
1589  testrec = m_switchToRec;
1590  m_switchToRec = nullptr;
1591  }
1592  else if (!Selection.empty())
1593  {
1594  for (const auto & ci : Selection)
1595  {
1596  uint chanid = ci.m_chanId;
1597  QString channum = ci.m_chanNum;
1598  if (!chanid || channum.isEmpty())
1599  continue;
1600  QSet<uint> cards = IsTunableOn(&m_playerContext, chanid);
1601 
1602  if (chanid && !channum.isEmpty() && !cards.isEmpty())
1603  {
1604  testrec = RemoteGetExistingRecorder(static_cast<int>(*(cards.begin())));
1605  m_initialChanID = chanid;
1606  break;
1607  }
1608  }
1609  }
1610  else
1611  {
1612  // When starting LiveTV we just get the next free recorder
1613  testrec = RemoteRequestNextFreeRecorder(-1);
1614  }
1615 
1616  if (!testrec)
1617  return false;
1618 
1619  if (!testrec->IsValidRecorder())
1620  {
1621  if (ShowDialogs)
1623 
1624  delete testrec;
1625 
1626  return false;
1627  }
1628 
1629  m_playerContext.SetRecorder(testrec);
1630 
1631  return true;
1632 }
1633 
1634 void TV::AskAllowRecording(const QStringList &Msg, int Timeuntil, bool HasRec, bool HasLater)
1635 {
1636  if (!StateIsLiveTV(GetState()))
1637  return;
1638 
1639  auto *info = new ProgramInfo(Msg);
1640  if (!info->GetChanID())
1641  {
1642  delete info;
1643  return;
1644  }
1645 
1646  QMutexLocker locker(&m_askAllowLock);
1647  QString key = info->MakeUniqueKey();
1648  if (Timeuntil > 0)
1649  {
1650  // add program to list
1651 #if 0
1652  LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording -- " +
1653  QString("adding '%1'").arg(info->m_title));
1654 #endif
1655  QDateTime expiry = MythDate::current().addSecs(Timeuntil);
1656  m_askAllowPrograms[key] = AskProgramInfo(expiry, HasRec, HasLater, info);
1657  }
1658  else
1659  {
1660  // remove program from list
1661  LOG(VB_GENERAL, LOG_INFO, LOC + "-- " +
1662  QString("removing '%1'").arg(info->GetTitle()));
1663  QMap<QString,AskProgramInfo>::iterator it = m_askAllowPrograms.find(key);
1664  if (it != m_askAllowPrograms.end())
1665  {
1666  delete (*it).m_info;
1667  m_askAllowPrograms.erase(it);
1668  }
1669  delete info;
1670  }
1671 
1672  ShowOSDAskAllow();
1673 }
1674 
1676 {
1677  QMutexLocker locker(&m_askAllowLock);
1679  return;
1680 
1681  uint cardid = m_playerContext.GetCardID();
1682 
1683  QString single_rec = tr("MythTV wants to record \"%1\" on %2 in %d seconds. Do you want to:");
1684 
1685  QString record_watch = tr("Record and watch while it records");
1686  QString let_record1 = tr("Let it record and go back to the Main Menu");
1687  QString let_recordm = tr("Let them record and go back to the Main Menu");
1688  QString record_later1 = tr("Record it later, I want to watch TV");
1689  QString record_laterm = tr("Record them later, I want to watch TV");
1690  QString do_not_record1= tr("Don't let it record, I want to watch TV");
1691  QString do_not_recordm= tr("Don't let them record, I want to watch TV");
1692 
1693  // eliminate timed out programs
1694  QDateTime timeNow = MythDate::current();
1695  QMap<QString,AskProgramInfo>::iterator it = m_askAllowPrograms.begin();
1696  QMap<QString,AskProgramInfo>::iterator next = it;
1697  while (it != m_askAllowPrograms.end())
1698  {
1699  next = it; ++next;
1700  if ((*it).m_expiry <= timeNow)
1701  {
1702 #if 0
1703  LOG(VB_GENERAL, LOG_DEBUG, LOC + "-- " +
1704  QString("removing '%1'").arg((*it).m_info->m_title));
1705 #endif
1706  delete (*it).m_info;
1707  m_askAllowPrograms.erase(it);
1708  }
1709  it = next;
1710  }
1711  int timeuntil = 0;
1712  QString message;
1713  uint conflict_count = static_cast<uint>(m_askAllowPrograms.size());
1714 
1715  it = m_askAllowPrograms.begin();
1716  if ((1 == m_askAllowPrograms.size()) && ((*it).m_info->GetInputID() == cardid))
1717  {
1718  (*it).m_isInSameInputGroup = (*it).m_isConflicting = true;
1719  }
1720  else if (!m_askAllowPrograms.empty())
1721  {
1722  // get the currently used input on our card
1723  bool busy_input_grps_loaded = false;
1724  vector<uint> busy_input_grps;
1725  InputInfo busy_input;
1726  RemoteIsBusy(cardid, busy_input);
1727 
1728  // check if current input can conflict
1729  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1730  {
1731  (*it).m_isInSameInputGroup =
1732  (cardid == (*it).m_info->GetInputID());
1733 
1734  if ((*it).m_isInSameInputGroup)
1735  continue;
1736 
1737  // is busy_input in same input group as recording
1738  if (!busy_input_grps_loaded)
1739  {
1740  busy_input_grps = CardUtil::GetInputGroups(busy_input.m_inputId);
1741  busy_input_grps_loaded = true;
1742  }
1743 
1744  vector<uint> input_grps =
1745  CardUtil::GetInputGroups((*it).m_info->GetInputID());
1746 
1747  for (uint grp : input_grps)
1748  {
1749  if (find(busy_input_grps.begin(), busy_input_grps.end(),
1750  grp) != busy_input_grps.end())
1751  {
1752  (*it).m_isInSameInputGroup = true;
1753  break;
1754  }
1755  }
1756  }
1757 
1758  // check if inputs that can conflict are ok
1759  conflict_count = 0;
1760  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1761  {
1762  if (!(*it).m_isInSameInputGroup)
1763  (*it).m_isConflicting = false; // NOLINT(bugprone-branch-clone)
1764  else if (cardid == (*it).m_info->GetInputID())
1765  (*it).m_isConflicting = true; // NOLINT(bugprone-branch-clone)
1766  else if (!CardUtil::IsTunerShared(cardid, (*it).m_info->GetInputID()))
1767  (*it).m_isConflicting = true;
1768  else if ((busy_input.m_mplexId &&
1769  (busy_input.m_mplexId == (*it).m_info->QueryMplexID())) ||
1770  (!busy_input.m_mplexId &&
1771  (busy_input.m_chanId == (*it).m_info->GetChanID())))
1772  (*it).m_isConflicting = false;
1773  else
1774  (*it).m_isConflicting = true;
1775 
1776  conflict_count += (*it).m_isConflicting ? 1 : 0;
1777  }
1778  }
1779 
1780  it = m_askAllowPrograms.begin();
1781  for (; it != m_askAllowPrograms.end() && !(*it).m_isConflicting; ++it);
1782 
1783  if (conflict_count == 0)
1784  {
1785  LOG(VB_GENERAL, LOG_INFO, LOC + "The scheduler wants to make "
1786  "a non-conflicting recording.");
1787  // TODO take down mplexid and inform user of problem
1788  // on channel changes.
1789  }
1790  else if (conflict_count == 1 && ((*it).m_info->GetInputID() == cardid))
1791  {
1792 #if 0
1793  LOG(VB_GENERAL, LOG_DEBUG, LOC + "UpdateOSDAskAllowDialog -- " +
1794  "kAskAllowOneRec");
1795 #endif
1796 
1797  it = m_askAllowPrograms.begin();
1798 
1799  QString channel = m_dbChannelFormat;
1800  channel
1801  .replace("<num>", (*it).m_info->GetChanNum())
1802  .replace("<sign>", (*it).m_info->GetChannelSchedulingID())
1803  .replace("<name>", (*it).m_info->GetChannelName());
1804 
1805  message = single_rec.arg((*it).m_info->GetTitle()).arg(channel);
1806 
1807  BrowseEnd(false);
1808  timeuntil = static_cast<int>(MythDate::current().secsTo((*it).m_expiry) * 1000);
1809  MythOSDDialogData dialog { OSD_DLG_ASKALLOW, message, timeuntil };
1810  dialog.m_buttons.push_back({ record_watch, "DIALOG_ASKALLOW_WATCH_0", false, !((*it).m_hasRec)} );
1811  dialog.m_buttons.push_back({ let_record1, "DIALOG_ASKALLOW_EXIT_0" });
1812  dialog.m_buttons.push_back({ ((*it).m_hasLater) ? record_later1 : do_not_record1,
1813  "DIALOG_ASKALLOW_CANCELRECORDING_0", false, ((*it).m_hasRec) });
1814  emit ChangeOSDDialog(dialog);
1815  }
1816  else
1817  {
1818  if (conflict_count > 1)
1819  {
1820  message = tr(
1821  "MythTV wants to record these programs in %d seconds:");
1822  message += "\n";
1823  }
1824 
1825  bool has_rec = false;
1826  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1827  {
1828  if (!(*it).m_isConflicting)
1829  continue;
1830 
1831  QString title = (*it).m_info->GetTitle();
1832  if ((title.length() < 10) && !(*it).m_info->GetSubtitle().isEmpty())
1833  title += ": " + (*it).m_info->GetSubtitle();
1834  if (title.length() > 20)
1835  title = title.left(17) + "...";
1836 
1837  QString channel = m_dbChannelFormat;
1838  channel
1839  .replace("<num>", (*it).m_info->GetChanNum())
1840  .replace("<sign>", (*it).m_info->GetChannelSchedulingID())
1841  .replace("<name>", (*it).m_info->GetChannelName());
1842 
1843  if (conflict_count > 1)
1844  {
1845  message += tr("\"%1\" on %2").arg(title).arg(channel);
1846  message += "\n";
1847  }
1848  else
1849  {
1850  message = single_rec.arg((*it).m_info->GetTitle()).arg(channel);
1851  has_rec = (*it).m_hasRec;
1852  }
1853  }
1854 
1855  if (conflict_count > 1)
1856  {
1857  message += "\n";
1858  message += tr("Do you want to:");
1859  }
1860 
1861  bool all_have_later = true;
1862  timeuntil = 9999999;
1863  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1864  {
1865  if ((*it).m_isConflicting)
1866  {
1867  all_have_later &= (*it).m_hasLater;
1868  int tmp = static_cast<int>(MythDate::current().secsTo((*it).m_expiry));
1869  tmp *= 1000;
1870  timeuntil = std::min(timeuntil, std::max(tmp, 0));
1871  }
1872  }
1873  timeuntil = (9999999 == timeuntil) ? 0 : timeuntil;
1874 
1875  if (conflict_count > 1)
1876  {
1877  BrowseEnd(false);
1878  emit ChangeOSDDialog( { OSD_DLG_ASKALLOW, message, timeuntil, {
1879  { let_recordm, "DIALOG_ASKALLOW_EXIT_0", false, true },
1880  { all_have_later ? record_laterm : do_not_recordm, "DIALOG_ASKALLOW_CANCELCONFLICTING_0" }
1881  }});
1882  }
1883  else
1884  {
1885  BrowseEnd(false);
1886  emit ChangeOSDDialog( {OSD_DLG_ASKALLOW, message, timeuntil, {
1887  { let_record1, "DIALOG_ASKALLOW_EXIT_0", false, !has_rec},
1888  { all_have_later ? record_later1 : do_not_record1, "DIALOG_ASKALLOW_CANCELRECORDING_0", false, has_rec}
1889  }});
1890  }
1891  }
1892 }
1893 
1894 void TV::HandleOSDAskAllow(const QString& Action)
1895 {
1897  return;
1898 
1899  if (!m_askAllowLock.tryLock())
1900  {
1901  LOG(VB_GENERAL, LOG_ERR, "allowrecordingbox : askAllowLock is locked");
1902  return;
1903  }
1904 
1905  if (Action == "CANCELRECORDING")
1906  {
1909  }
1910  else if (Action == "CANCELCONFLICTING")
1911  {
1912  for (const auto& pgm : qAsConst(m_askAllowPrograms))
1913  {
1914  if (pgm.m_isConflicting)
1915  RemoteCancelNextRecording(pgm.m_info->GetInputID(), true);
1916  }
1917  }
1918  else if (Action == "WATCH")
1919  {
1922  }
1923  else // if (action == "EXIT")
1924  {
1925  PrepareToExitPlayer(__LINE__);
1926  SetExitPlayer(true, true);
1927  }
1928 
1929  m_askAllowLock.unlock();
1930 }
1931 
1933 {
1934  m_wantsToQuit = false;
1935  m_jumpToProgram = false;
1936  m_allowRerecord = false;
1937  m_requestDelete = false;
1939 
1942  {
1943  ReturnPlayerLock();
1944  return 0;
1945  }
1946 
1950 
1951  ReturnPlayerLock();
1952 
1953  if (LCD *lcd = LCD::Get())
1954  {
1955  lcd->switchToChannel(ProgInfo.GetChannelSchedulingID(), ProgInfo.GetTitle(), ProgInfo.GetSubtitle());
1956  lcd->setFunctionLEDs((ProgInfo.IsRecording())?FUNC_TV:FUNC_MOVIE, true);
1957  }
1958 
1959  return 1;
1960 }
1961 
1963 {
1965 }
1966 
1968 {
1969  return (State == kState_WatchingPreRecorded ||
1974 }
1975 
1977 {
1978  return (State == kState_WatchingLiveTV);
1979 }
1980 
1981 #define TRANSITION(ASTATE,BSTATE) ((ctxState == (ASTATE)) && (desiredNextState == (BSTATE)))
1982 
1983 #define SET_NEXT() do { nextState = desiredNextState; changed = true; } while(false)
1984 #define SET_LAST() do { nextState = ctxState; changed = true; } while(false)
1985 
1986 static QString tv_i18n(const QString &msg)
1987 {
1988  QByteArray msg_arr = msg.toLatin1();
1989  QString msg_i18n = TV::tr(msg_arr.constData());
1990  QByteArray msg_i18n_arr = msg_i18n.toLatin1();
1991  return (msg_arr == msg_i18n_arr) ? msg_i18n : msg;
1992 }
1993 
2003 {
2004  if (m_playerContext.IsErrored())
2005  {
2006  LOG(VB_GENERAL, LOG_ERR, LOC + "Called after fatal error detected.");
2007  return;
2008  }
2009 
2010  bool changed = false;
2011 
2013  TVState nextState = m_playerContext.GetState();
2014  if (m_playerContext.m_nextState.empty())
2015  {
2016  LOG(VB_GENERAL, LOG_WARNING, LOC + "Warning, called with no state to change to.");
2018  return;
2019  }
2020 
2021  TVState ctxState = m_playerContext.GetState();
2022  TVState desiredNextState = m_playerContext.DequeueNextState();
2023 
2024  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Attempting to change from %1 to %2")
2025  .arg(StateToString(nextState)).arg(StateToString(desiredNextState)));
2026 
2027  if (desiredNextState == kState_Error)
2028  {
2029  LOG(VB_GENERAL, LOG_ERR, LOC + "Attempting to set to an error state!");
2030  SetErrored();
2032  return;
2033  }
2034 
2035  bool ok = false;
2037  {
2039 
2041 
2042  QDateTime timerOffTime = MythDate::current();
2043  m_lockTimerOn = false;
2044 
2045  SET_NEXT();
2046 
2047  uint chanid = m_initialChanID;
2048  if (!chanid)
2049  chanid = static_cast<uint>(gCoreContext->GetNumSetting("DefaultChanid", 0));
2050 
2051  if (chanid && !IsTunablePriv(chanid))
2052  chanid = 0;
2053 
2054  QString channum = "";
2055 
2056  if (chanid)
2057  {
2058  QStringList reclist;
2059 
2061  query.prepare("SELECT channum FROM channel "
2062  "WHERE chanid = :CHANID");
2063  query.bindValue(":CHANID", chanid);
2064  if (query.exec() && query.isActive() && query.size() > 0 && query.next())
2065  channum = query.value(0).toString();
2066  else
2067  channum = QString::number(chanid);
2068 
2070  QString::number(chanid));
2071 
2072  if (getit)
2073  reclist = ChannelUtil::GetValidRecorderList(chanid, channum);
2074 
2075  if (!reclist.empty())
2076  {
2077  RemoteEncoder *testrec = RemoteRequestFreeRecorderFromList(reclist, 0);
2078  if (testrec && testrec->IsValidRecorder())
2079  {
2080  m_playerContext.SetRecorder(testrec);
2082  }
2083  else
2084  delete testrec; // If testrec isn't a valid recorder ...
2085  }
2086  else if (getit)
2087  chanid = 0;
2088  }
2089 
2090  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- begin");
2091 
2092  if (chanid && !channum.isEmpty())
2094  else
2096 
2097  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- end");
2098 
2100  {
2101  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
2103  m_playerContext.SetRecorder(nullptr);
2104  SetErrored();
2105  SET_LAST();
2106  }
2107  else
2108  {
2109  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2110  QString playbackURL = m_playerContext.m_playingInfo->GetPlaybackURL(true);
2111  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2112 
2113  bool opennow = (m_playerContext.m_tvchain->GetInputType(-1) != "DUMMY");
2114 
2115  LOG(VB_GENERAL, LOG_INFO, LOC +
2116  QString("playbackURL(%1) inputtype(%2)")
2117  .arg(playbackURL).arg(m_playerContext.m_tvchain->GetInputType(-1)));
2118 
2121  playbackURL, false, true,
2122  opennow ? MythMediaBuffer::kLiveTVOpenTimeout : -1));
2123 
2126  }
2127 
2128 
2130  {
2131  ok = StartPlayer(desiredNextState);
2132  }
2133  if (!ok)
2134  {
2135  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
2137  m_playerContext.SetRecorder(nullptr);
2138  SetErrored();
2139  SET_LAST();
2140  }
2141  else
2142  {
2143  if (!m_lastLockSeenTime.isValid() ||
2144  (m_lastLockSeenTime < timerOffTime))
2145  {
2146  m_lockTimer.start();
2147  m_lockTimerOn = true;
2148  }
2149  }
2150  }
2152  {
2153  SET_NEXT();
2155  StopStuff(true, true, true);
2156  }
2162  {
2163  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2164  QString playbackURL = m_playerContext.m_playingInfo->GetPlaybackURL(true);
2165  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2166 
2167  MythMediaBuffer *buffer = MythMediaBuffer::Create(playbackURL, false);
2168  if (buffer && !buffer->GetLastError().isEmpty())
2169  {
2170  ShowNotificationError(tr("Can't start playback"),
2171  TV::tr( "TV Player" ), buffer->GetLastError());
2172  delete buffer;
2173  buffer = nullptr;
2174  }
2176 
2178  {
2179  if (desiredNextState == kState_WatchingRecording)
2180  {
2181  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2183  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2184 
2186 
2188  {
2189  LOG(VB_GENERAL, LOG_ERR, LOC +
2190  "Couldn't find recorder for in-progress recording");
2191  desiredNextState = kState_WatchingPreRecorded;
2192  m_playerContext.SetRecorder(nullptr);
2193  }
2194  else
2195  {
2197  }
2198  }
2199 
2200  ok = StartPlayer(desiredNextState);
2201 
2202  if (ok)
2203  {
2204  SET_NEXT();
2205 
2206  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2208  {
2209  QString message = "COMMFLAG_REQUEST ";
2211  gCoreContext->SendMessage(message);
2212  }
2213  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2214  }
2215  }
2216 
2217  if (!ok)
2218  {
2219  SET_LAST();
2220  SetErrored();
2222  {
2224  TV::tr( "TV Player" ),
2225  playbackURL);
2226  // We're going to display this error as notification
2227  // no need to display it later as popup
2229  }
2230  }
2231  }
2237  {
2238  SET_NEXT();
2240  StopStuff(true, true, false);
2241  }
2244  {
2245  SET_NEXT();
2246  }
2247 
2248  // Print state changed message...
2249  if (!changed)
2250  {
2251  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unknown state transition: %1 to %2")
2252  .arg(StateToString(m_playerContext.GetState())).arg(StateToString(desiredNextState)));
2253  }
2254  else if (m_playerContext.GetState() != nextState)
2255  {
2256  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Changing from %1 to %2")
2257  .arg(StateToString(m_playerContext.GetState())).arg(StateToString(nextState)));
2258  }
2259 
2260  // update internal state variable
2261  TVState lastState = m_playerContext.GetState();
2262  m_playerContext.m_playingState = nextState;
2264 
2266  {
2267  LOG(VB_GENERAL, LOG_INFO, LOC + "State is LiveTV");
2268  UpdateOSDInput();
2269  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateOSDInput done");
2270  UpdateLCD();
2271  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateLCD done");
2272  ITVRestart(true);
2273  LOG(VB_GENERAL, LOG_INFO, LOC + "ITVRestart done");
2274  }
2275  else if (StateIsPlaying(m_playerContext.GetState()) && lastState == kState_None)
2276  {
2277  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2278  int count = PlayGroup::GetCount();
2279  QString msg = tr("%1 Settings")
2281  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2282  if (count > 0)
2283  emit ChangeOSDMessage(msg);
2284  ITVRestart(false);
2285  }
2286 
2288  {
2289  UpdateLCD();
2290  }
2291 
2294 
2300 
2304 
2306  {
2308  }
2309 
2316  {
2318  // m_playerBounds is not applicable when switching modes so
2319  // skip this logic in that case.
2320  if (!m_dbUseVideoModes)
2322 
2323  if (!m_weDisabledGUI)
2324  {
2325  m_weDisabledGUI = true;
2327  }
2328  // we no longer need the contents of myWindow
2329  if (m_myWindow)
2331 
2332  LOG(VB_GENERAL, LOG_INFO, LOC + "Main UI disabled.");
2333  }
2334 
2335  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + " -- end");
2336 }
2337 
2338 #undef TRANSITION
2339 #undef SET_NEXT
2340 #undef SET_LAST
2341 
2347 bool TV::StartRecorder(int MaxWait)
2348 {
2350  MaxWait = (MaxWait <= 0) ? 40000 : MaxWait;
2351  MythTimer t;
2352  t.start();
2353  bool recording = false;
2354  bool ok = true;
2355  if (!rec)
2356  {
2357  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid Remote Encoder");
2358  SetErrored();
2359  return false;
2360  }
2361  while (!(recording = rec->IsRecording(&ok)) && !m_exitPlayerTimerId && t.elapsed() < MaxWait)
2362  {
2363  if (!ok)
2364  {
2365  LOG(VB_GENERAL, LOG_ERR, LOC + "Lost contact with backend");
2366  SetErrored();
2367  return false;
2368  }
2369  std::this_thread::sleep_for(std::chrono::microseconds(5));
2370  }
2371 
2372  if (!recording || m_exitPlayerTimerId)
2373  {
2374  if (!m_exitPlayerTimerId)
2375  LOG(VB_GENERAL, LOG_ERR, LOC + "Timed out waiting for recorder to start");
2376  return false;
2377  }
2378 
2379  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Took %1 ms to start recorder.").arg(t.elapsed()));
2380  return true;
2381 }
2382 
2396 void TV::StopStuff(bool StopRingBuffer, bool StopPlayer, bool StopRecorder)
2397 {
2398  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
2399 
2400  emit PlaybackExiting(this);
2401 
2404 
2405  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
2406  if (StopPlayer)
2408  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
2409 
2410  if (StopRingBuffer)
2411  {
2412  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopping ring buffer");
2414  {
2418  }
2419  }
2420 
2421  if (StopRecorder)
2422  {
2423  LOG(VB_PLAYBACK, LOG_INFO, LOC + "stopping recorder");
2426  }
2427 
2428  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
2429 }
2430 
2431 void TV::timerEvent(QTimerEvent *Event)
2432 {
2433  const int timer_id = Event->timerId();
2434 
2436  bool errored = m_playerContext.IsErrored();
2437  ReturnPlayerLock();
2438  if (errored)
2439  return;
2440 
2441  bool handled = true;
2442  if (timer_id == m_lcdTimerId)
2444  else if (timer_id == m_lcdVolumeTimerId)
2446  else if (timer_id == m_sleepTimerId)
2447  ShowOSDSleep();
2448  else if (timer_id == m_sleepDialogTimerId)
2450  else if (timer_id == m_idleTimerId)
2451  ShowOSDIdle();
2452  else if (timer_id == m_idleDialogTimerId)
2454  else if (timer_id == m_endOfPlaybackTimerId)
2456  else if (timer_id == m_endOfRecPromptTimerId)
2458  else if (timer_id == m_videoExitDialogTimerId)
2460  else if (timer_id == m_pseudoChangeChanTimerId)
2462  else if (timer_id == m_speedChangeTimerId)
2464  else if (timer_id == m_saveLastPlayPosTimerId)
2466  else
2467  handled = false;
2468 
2469  if (handled)
2470  return;
2471 
2472  // Check if it matches a signalMonitorTimerId
2473  if (timer_id == m_signalMonitorTimerId)
2474  {
2478  if (!m_playerContext.m_lastSignalMsg.empty())
2479  {
2480  // set last signal msg, so we get some feedback...
2483  }
2485  ReturnPlayerLock();
2486  return;
2487  }
2488 
2489  // Check if it matches networkControlTimerId
2490  QString netCmd;
2491  if (timer_id == m_networkControlTimerId)
2492  {
2493  if (!m_networkControlCommands.empty())
2494  netCmd = m_networkControlCommands.dequeue();
2495  if (m_networkControlCommands.empty())
2496  {
2499  }
2500  }
2501 
2502  if (!netCmd.isEmpty())
2503  {
2506  ReturnPlayerLock();
2507  handled = true;
2508  }
2509 
2510  if (handled)
2511  return;
2512 
2513  // Check if it matches exitPlayerTimerId
2514  if (timer_id == m_exitPlayerTimerId)
2515  {
2517  emit DialogQuit();
2518  emit HideAll();
2519 
2521  {
2522  if (!m_lastProgram->IsFileReadable())
2523  {
2524  emit ChangeOSDMessage(tr("Last Program: %1 Doesn't Exist").arg(m_lastProgram->GetTitle()));
2525  lastProgramStringList.clear();
2526  SetLastProgram(nullptr);
2527  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Last Program File does not exist");
2528  m_jumpToProgram = false;
2529  }
2530  else
2531  {
2533  }
2534  }
2535  else
2537 
2538  ReturnPlayerLock();
2539 
2541  m_exitPlayerTimerId = 0;
2542  handled = true;
2543  }
2544 
2545  if (handled)
2546  return;
2547 
2548  if (timer_id == m_ccInputTimerId)
2549  {
2551  // Clear closed caption input mode when timer expires
2552  if (m_ccInputMode)
2553  {
2554  m_ccInputMode = false;
2555  ClearInputQueues(true);
2556  }
2557  ReturnPlayerLock();
2558 
2560  m_ccInputTimerId = 0;
2561  handled = true;
2562  }
2563 
2564  if (handled)
2565  return;
2566 
2567  if (timer_id == m_asInputTimerId)
2568  {
2570  // Clear closed caption input mode when timer expires
2571  if (m_asInputMode)
2572  {
2573  m_asInputMode = false;
2574  ClearInputQueues(true);
2575  }
2576  ReturnPlayerLock();
2577 
2579  m_asInputTimerId = 0;
2580  handled = true;
2581  }
2582 
2583  if (handled)
2584  return;
2585 
2586  if (timer_id == m_queueInputTimerId)
2587  {
2589  // Commit input when the OSD fades away
2590  if (HasQueuedChannel())
2591  {
2592  OSD *osd = GetOSDL();
2593  if (osd && !osd->IsWindowVisible(OSD_WIN_INPUT))
2594  {
2595  ReturnOSDLock();
2597  }
2598  else
2599  {
2600  ReturnOSDLock();
2601  }
2602  }
2603  ReturnPlayerLock();
2604 
2605  if (!m_queuedChanID && m_queuedChanNum.isEmpty() && m_queueInputTimerId)
2606  {
2608  m_queueInputTimerId = 0;
2609  }
2610  handled = true;
2611  }
2612 
2613  if (handled)
2614  return;
2615 
2616  if (timer_id == m_browseTimerId)
2617  {
2619  BrowseEnd(false);
2620  ReturnPlayerLock();
2621  handled = true;
2622  }
2623 
2624  if (handled)
2625  return;
2626 
2627  if (timer_id == m_errorRecoveryTimerId)
2628  {
2632  {
2633  SetExitPlayer(true, false);
2635  }
2636  ReturnPlayerLock();
2637 
2641  return;
2642  }
2643 
2644  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Unknown timer: %1").arg(timer_id));
2645 }
2646 
2648 {
2650  LCD *lcd = LCD::Get();
2651  if (lcd)
2652  {
2653  float progress = 0.0F;
2654  QString lcd_time_string;
2655  bool showProgress = true;
2656 
2657  if (StateIsLiveTV(GetState()))
2659 
2661  {
2662  ShowLCDDVDInfo();
2664  }
2665 
2666  if (showProgress)
2667  {
2668  osdInfo info;
2669  if (CalcPlayerSliderPosition(info)) {
2670  progress = info.values["position"] * 0.001F;
2671 
2672  lcd_time_string = info.text["playedtime"] + " / " + info.text["totaltime"];
2673  // if the string is longer than the LCD width, remove all spaces
2674  if (lcd_time_string.length() > static_cast<int>(lcd->getLCDWidth()))
2675  lcd_time_string.remove(' ');
2676  }
2677  }
2678  lcd->setChannelProgress(lcd_time_string, progress);
2679  }
2680  ReturnPlayerLock();
2681 
2683  m_lcdTimerId = StartTimer(kLCDTimeout, __LINE__);
2684 
2685  return true;
2686 }
2687 
2689 {
2691  LCD *lcd = LCD::Get();
2692  if (lcd)
2693  {
2696  }
2697  ReturnPlayerLock();
2698 
2700  m_lcdVolumeTimerId = 0;
2701 }
2702 
2703 int TV::StartTimer(int Interval, int Line)
2704 {
2705  int timer = startTimer(Interval);
2706  if (!timer)
2707  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to start timer on line %1 of %2").arg(Line).arg(__FILE__));
2708  return timer;
2709 }
2710 
2711 void TV::KillTimer(int Id)
2712 {
2713  killTimer(Id);
2714 }
2715 
2717 {
2720 }
2721 
2723 {
2724  auto StateChange = [&]()
2725  {
2727  if (!m_playerContext.m_nextState.empty())
2728  {
2730  if ((kState_None == m_playerContext.GetState() ||
2732  {
2733  ReturnPlayerLock();
2736  m_player = nullptr;
2737  }
2738  }
2739  ReturnPlayerLock();
2740  };
2741 
2742  QTimer::singleShot(0, this, StateChange);
2743 }
2744 
2746 {
2747  auto InputChange = [&]()
2748  {
2750  if (m_switchToInputId)
2751  {
2753  m_switchToInputId = 0;
2754  SwitchInputs(0, QString(), tmp);
2755  }
2756  ReturnPlayerLock();
2757  };
2758 
2759  QTimer::singleShot(0, this, InputChange);
2760 }
2761 
2763 {
2764  m_playerContext.m_errored = true;
2766  m_errorRecoveryTimerId = StartTimer(1, __LINE__);
2767 }
2768 
2770 {
2771  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switching to program: %1")
2774  PrepareToExitPlayer(__LINE__);
2775  m_jumpToProgram = true;
2776  SetExitPlayer(true, true);
2777 }
2778 
2780 {
2781  bool bm_allowed = IsBookmarkAllowed();
2782  m_playerContext.LockDeletePlayer(__FILE__, Line);
2783  if (m_player)
2784  {
2785  if (bm_allowed)
2786  {
2787  // If we're exiting in the middle of the recording, we
2788  // automatically save a bookmark when "Action on playback
2789  // exit" is set to "Save position and exit".
2790  bool allow_set_before_end =
2791  (Bookmark == kBookmarkAlways ||
2792  (Bookmark == kBookmarkAuto &&
2793  m_dbPlaybackExitPrompt == 2));
2794  // If we're exiting at the end of the recording, we
2795  // automatically clear the bookmark when "Action on
2796  // playback exit" is set to "Save position and exit" and
2797  // "Clear bookmark on playback" is set to true.
2798  bool allow_clear_at_end =
2799  (Bookmark == kBookmarkAlways ||
2800  (Bookmark == kBookmarkAuto &&
2801  m_dbPlaybackExitPrompt == 2 &&
2803  // Whether to set/clear a bookmark depends on whether we're
2804  // exiting at the end of a recording.
2805  bool at_end = (m_player->IsNearEnd() || GetEndOfRecording());
2806  // Don't consider ourselves at the end if the recording is
2807  // in-progress.
2808  at_end &= !StateIsRecording(GetState());
2809  bool clear_lastplaypos = false;
2810  if (at_end && allow_clear_at_end)
2811  {
2812  SetBookmark(true);
2813  // Tidy up the lastplaypos mark only when we clear the
2814  // bookmark due to exiting at the end.
2815  clear_lastplaypos = true;
2816  }
2817  else if (!at_end && allow_set_before_end)
2818  {
2819  SetBookmark(false);
2820  }
2821  if (clear_lastplaypos && m_playerContext.m_playingInfo)
2823  }
2824  if (m_dbAutoSetWatched)
2825  m_player->SetWatched();
2826  }
2827  m_playerContext.UnlockDeletePlayer(__FILE__, Line);
2828 }
2829 
2830 void TV::SetExitPlayer(bool SetIt, bool WantsTo)
2831 {
2832  if (SetIt)
2833  {
2834  m_wantsToQuit = WantsTo;
2835  if (!m_exitPlayerTimerId)
2836  m_exitPlayerTimerId = StartTimer(1, __LINE__);
2837  }
2838  else
2839  {
2840  if (m_exitPlayerTimerId)
2842  m_exitPlayerTimerId = 0;
2843  m_wantsToQuit = WantsTo;
2844  }
2845 }
2846 
2848 {
2852 
2853  bool is_playing = false;
2855  if (StateIsPlaying(GetState()))
2856  {
2858  {
2859  is_playing = true;
2860  }
2861  // If the end of playback is destined to pop up the end of
2862  // recording delete prompt, then don't exit the player here.
2863  else if (!(GetState() == kState_WatchingPreRecorded &&
2865  {
2867  m_endOfRecording = true;
2868  PrepareToExitPlayer(__LINE__);
2869  SetExitPlayer(true, true);
2870  }
2871  }
2872  ReturnPlayerLock();
2873 
2874  if (is_playing)
2876 }
2877 
2879 {
2882  {
2883  return;
2884  }
2885 
2887  OSD *osd = GetOSDL();
2888  if (osd && osd->DialogVisible())
2889  {
2890  ReturnOSDLock();
2891  ReturnPlayerLock();
2892  return;
2893  }
2894  ReturnOSDLock();
2895 
2896  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
2897  bool do_prompt = (m_playerContext.GetState() == kState_WatchingPreRecorded &&
2899  !m_player->IsPlaying());
2900  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
2901 
2902  if (do_prompt)
2903  ShowOSDPromptDeleteRecording(tr("End Of Recording"));
2904 
2905  ReturnPlayerLock();
2906 }
2907 
2909 {
2913 
2914  // disable dialog and exit playback after timeout
2916  OSD *osd = GetOSDL();
2917  if (!osd || !osd->DialogVisible(OSD_DLG_VIDEOEXIT))
2918  {
2919  ReturnOSDLock();
2920  ReturnPlayerLock();
2921  return;
2922  }
2923  ReturnOSDLock();
2924  DoTogglePause(true);
2925  ClearOSD();
2926  PrepareToExitPlayer(__LINE__);
2927  ReturnPlayerLock();
2928 
2929  m_requestDelete = false;
2930  SetExitPlayer(true, true);
2931 }
2932 
2934 {
2937 
2938  bool restartTimer = false;
2941  {
2943  {
2944  restartTimer = true;
2945  }
2946  else
2947  {
2948  LOG(VB_CHANNEL, LOG_INFO, "REC_PROGRAM -- channel change");
2949 
2951  QString channum = m_playerContext.m_pseudoLiveTVRec->GetChanNum();
2953 
2954  m_playerContext.m_prevChan.clear();
2955  ChangeChannel(chanid, channum);
2958  }
2959  }
2960  ReturnPlayerLock();
2961 
2962  if (restartTimer)
2964  m_pseudoChangeChanTimerId = StartTimer(25, __LINE__);
2965 }
2966 
2967 void TV::SetSpeedChangeTimer(int When, int Line)
2968 {
2971  m_speedChangeTimerId = StartTimer(When, Line);
2972 }
2973 
2975 {
2979 
2981  bool update_msg = m_playerContext.HandlePlayerSpeedChangeFFRew();
2983  if (update_msg)
2985  ReturnPlayerLock();
2986 }
2987 
2989 bool TV::eventFilter(QObject* Object, QEvent* Event)
2990 {
2991  // We want to intercept all resize events sent to the main window
2992  if ((Event->type() == QEvent::Resize))
2993  return (m_mainWindow != Object) ? false : event(Event);
2994 
2995  // Intercept keypress events unless they need to be handled by a main UI
2996  // screen (e.g. GuideGrid, ProgramFinder)
2997 
2998  if ( (QEvent::KeyPress == Event->type() || QEvent::KeyRelease == Event->type())
2999  && m_ignoreKeyPresses )
3000  return TVPlaybackState::eventFilter(Object, Event);
3001 
3002  QScopedPointer<QEvent> sNewEvent(nullptr);
3003  if (m_mainWindow->KeyLongPressFilter(&Event, sNewEvent))
3004  return true;
3005 
3006  if (QEvent::KeyPress == Event->type())
3007  return event(Event);
3008 
3009  if (MythGestureEvent::kEventType == Event->type())
3010  return m_ignoreKeyPresses ? false : event(Event);
3011 
3012  if (Event->type() == MythEvent::MythEventMessage ||
3013  Event->type() == MythEvent::MythUserMessage ||
3015  Event->type() == MythMediaEvent::kEventType)
3016  {
3017  customEvent(Event);
3018  return true;
3019  }
3020 
3021  switch (Event->type())
3022  {
3023  case QEvent::Paint:
3024  case QEvent::UpdateRequest:
3025  case QEvent::Enter:
3026  {
3027  event(Event);
3028  return TVPlaybackState::eventFilter(Object, Event);
3029  }
3030  default:
3031  return TVPlaybackState::eventFilter(Object, Event);
3032  }
3033 }
3034 
3036 bool TV::event(QEvent* Event)
3037 {
3038  if (Event == nullptr)
3039  return TVPlaybackState::event(Event);
3040 
3041  if (QEvent::Resize == Event->type())
3042  {
3043  const auto *qre = dynamic_cast<const QResizeEvent*>(Event);
3044  if (qre)
3045  emit WindowResized(qre->size());
3046  return TVPlaybackState::event(Event);
3047  }
3048 
3049  if (QEvent::KeyPress == Event->type() || MythGestureEvent::kEventType == Event->type())
3050  {
3051 #if DEBUG_ACTIONS
3052  if (QEvent::KeyPress == Event->type())
3053  {
3054  const auto * ke = dynamic_cast<QKeyEvent*>(Event);
3055  if (ke)
3056  {
3057  LOG(VB_GENERAL, LOG_INFO, LOC + QString("keypress: %1 '%2'")
3058  .arg(ke->key()).arg(ke->text()));
3059  }
3060  }
3061  else
3062  {
3063  const auto * ge = dynamic_cast<MythGestureEvent*>(Event);
3064  if (ge)
3065  {
3066  LOG(VB_GENERAL, LOG_INFO, LOC + QString("mythgesture: g:%1 pos:%2,%3 b:%4")
3067  .arg(ge->GetGesture()).arg(ge->GetPosition().x())
3068  .arg(ge->GetPosition().y()).arg(ge->GetButton()));
3069  }
3070  }
3071 #endif
3072  bool handled = false;
3074  if (m_playerContext.HasPlayer())
3075  handled = ProcessKeypressOrGesture(Event);
3076  ReturnPlayerLock();
3077  if (handled)
3078  return true;
3079  }
3080 
3081  switch (Event->type())
3082  {
3083  case QEvent::Paint:
3084  case QEvent::UpdateRequest:
3085  case QEvent::Enter:
3086  return true;
3087  default:
3088  break;
3089  }
3090 
3091  return QObject::event(Event);
3092 }
3093 
3094 bool TV::HandleTrackAction(const QString &Action)
3095 {
3096  bool handled = true;
3097 
3100  else if (ACTION_ENABLEEXTTEXT == Action)
3102  else if (ACTION_DISABLEEXTTEXT == Action)
3104  else if (ACTION_ENABLEFORCEDSUBS == Action)
3105  emit ChangeAllowForcedSubtitles(true);
3106  else if (ACTION_DISABLEFORCEDSUBS == Action)
3107  emit ChangeAllowForcedSubtitles(false);
3108  else if (Action == ACTION_ENABLESUBS)
3109  emit SetCaptionsEnabled(true, true);
3110  else if (Action == ACTION_DISABLESUBS)
3111  emit SetCaptionsEnabled(false, true);
3113  {
3114  if (m_ccInputMode)
3115  {
3116  bool valid = false;
3117  int page = GetQueuedInputAsInt(&valid, 16);
3118  if (m_vbimode == VBIMode::PAL_TT && valid)
3119  emit SetTeletextPage(static_cast<uint>(page));
3120  else if (m_vbimode == VBIMode::NTSC_CC)
3121  emit SetTrack(kTrackTypeCC608, static_cast<uint>(std::max(std::min(page - 1, 1), 0)));
3122 
3123  ClearInputQueues(true);
3124 
3125  m_ccInputMode = false;
3126  if (m_ccInputTimerId)
3127  {
3129  m_ccInputTimerId = 0;
3130  }
3131  }
3133  {
3134  ClearInputQueues(false);
3135  AddKeyToInputQueue(0);
3136 
3137  m_ccInputMode = true;
3138  m_asInputMode = false;
3140  if (m_asInputTimerId)
3141  {
3143  m_asInputTimerId = 0;
3144  }
3145  }
3146  else
3147  {
3148  emit ToggleCaptions();
3149  }
3150  }
3151  else if (Action.startsWith("TOGGLE"))
3152  {
3153  int type = to_track_type(Action.mid(6));
3155  emit EnableTeletext();
3156  else if (type >= kTrackTypeSubtitle)
3157  emit ToggleCaptionsByType(static_cast<uint>(type));
3158  else
3159  handled = false;
3160  }
3161  else if (Action.startsWith("SELECT"))
3162  {
3163  int type = to_track_type(Action.mid(6));
3164  uint num = Action.section("_", -1).toUInt();
3165  if (type >= kTrackTypeAudio)
3166  emit SetTrack(static_cast<uint>(type), num);
3167  else
3168  handled = false;
3169  }
3170  else if (Action.startsWith("NEXT") || Action.startsWith("PREV"))
3171  {
3172  int dir = (Action.startsWith("NEXT")) ? +1 : -1;
3173  int type = to_track_type(Action.mid(4));
3174  if (type >= kTrackTypeAudio)
3175  emit ChangeTrack(static_cast<uint>(type), dir);
3176  else if (Action.endsWith("CC"))
3177  emit ChangeCaptionTrack(dir);
3178  else
3179  handled = false;
3180  }
3181  else
3182  handled = false;
3183  return handled;
3184 }
3185 
3186 // Make a special check for global system-related events.
3187 //
3188 // This check needs to be done early in the keypress event processing,
3189 // because FF/REW processing causes unknown events to stop FF/REW, and
3190 // manual zoom mode processing consumes all but a few event types.
3191 // Ideally, we would just call MythScreenType::keyPressEvent()
3192 // unconditionally, but we only want certain keypresses handled by
3193 // that method.
3194 //
3195 // As a result, some of the MythScreenType::keyPressEvent() string
3196 // compare logic is copied here.
3197 static bool SysEventHandleAction(MythMainWindow* MainWindow, QKeyEvent *e, const QStringList &actions)
3198 {
3199  QStringList::const_iterator it;
3200  for (it = actions.begin(); it != actions.end(); ++it)
3201  {
3202  if ((*it).startsWith("SYSEVENT") ||
3203  *it == ACTION_SCREENSHOT ||
3204  *it == ACTION_TVPOWERON ||
3205  *it == ACTION_TVPOWEROFF)
3206  {
3207  return MainWindow->GetMainStack()->GetTopScreen()->keyPressEvent(e);
3208  }
3209  }
3210  return false;
3211 }
3212 
3213 QList<QKeyEvent> TV::ConvertScreenPressKeyMap(const QString &KeyList)
3214 {
3215  QList<QKeyEvent> keyPressList;
3216  int i = 0;
3217  QStringList stringKeyList = KeyList.split(',');
3218  for (const auto & str : qAsConst(stringKeyList))
3219  {
3220  QKeySequence keySequence(str);
3221  for(i = 0; i < keySequence.count(); i++)
3222  {
3223  uint keynum = static_cast<uint>(keySequence[static_cast<uint>(i)]);
3224  QKeyEvent keyEvent(QEvent::None, keynum & ~Qt::KeyboardModifierMask,
3225  static_cast<Qt::KeyboardModifiers>(keynum & Qt::KeyboardModifierMask));
3226  keyPressList.append(keyEvent);
3227  }
3228  }
3229  if (stringKeyList.count() < kScreenPressRegionCount)
3230  {
3231  // add default remainders
3232  for(; i < kScreenPressRegionCount; i++)
3233  {
3234  QKeyEvent keyEvent(QEvent::None, Qt::Key_Escape, Qt::NoModifier);
3235  keyPressList.append(keyEvent);
3236  }
3237  }
3238  return keyPressList;
3239 }
3240 
3241 bool TV::TranslateGesture(const QString &Context, MythGestureEvent *Event,
3242  QStringList &Actions, bool IsLiveTV)
3243 {
3244  if (Event && Context == "TV Playback")
3245  {
3246  // TODO make this configuable via a similar mechanism to
3247  // TranslateKeyPress
3248  // possibly with configurable hot zones of various sizes in a theme
3249  // TODO enhance gestures to support other non Click types too
3250  if ((Event->GetGesture() == MythGestureEvent::Click) &&
3251  (Event->GetButton() == Qt::LeftButton))
3252  {
3253  // divide screen into 12 regions
3254  QSize size = m_mainWindow->size();
3255  QPoint pos = Event->GetPosition();
3256  int region = 0;
3257  const int widthDivider = 4;
3258  int w4 = size.width() / widthDivider;
3259  region = pos.x() / w4;
3260  int h3 = size.height() / 3;
3261  region += (pos.y() / h3) * widthDivider;
3262 
3263  if (IsLiveTV)
3264  return m_mainWindow->TranslateKeyPress(Context, &(m_screenPressKeyMapLiveTV[region]), Actions, true);
3265  return m_mainWindow->TranslateKeyPress(Context, &(m_screenPressKeyMapPlayback[region]), Actions, true);
3266  }
3267  return false;
3268  }
3269  return false;
3270 }
3271 
3272 bool TV::TranslateKeyPressOrGesture(const QString &Context, QEvent *Event,
3273  QStringList &Actions, bool IsLiveTV, bool AllowJumps)
3274 {
3275  if (Event)
3276  {
3277  if (QEvent::KeyPress == Event->type())
3278  return m_mainWindow->TranslateKeyPress(Context, dynamic_cast<QKeyEvent*>(Event), Actions, AllowJumps);
3279  if (MythGestureEvent::kEventType == Event->type())
3280  return TranslateGesture(Context, dynamic_cast<MythGestureEvent*>(Event), Actions, IsLiveTV);
3281  }
3282  return false;
3283 }
3284 
3286 {
3287  if (Event == nullptr)
3288  return false;
3289 
3290  bool ignoreKeys = m_playerContext.IsPlayerChangingBuffers();
3291 
3292 #if DEBUG_ACTIONS
3293  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ignoreKeys: %1").arg(ignoreKeys));
3294 #endif
3295 
3296  if (m_idleTimerId)
3297  {
3299  m_idleTimerId = StartTimer(static_cast<int>(m_dbIdleTimeout), __LINE__);
3300  }
3301 
3302 #ifdef Q_OS_LINUX
3303  // Fixups for _some_ linux native codes that QT doesn't know
3304  auto* eKeyEvent = dynamic_cast<QKeyEvent*>(Event);
3305  if (eKeyEvent) {
3306  if (eKeyEvent->key() <= 0)
3307  {
3308  int keycode = 0;
3309  switch(eKeyEvent->nativeScanCode())
3310  {
3311  case 209: // XF86AudioPause
3312  keycode = Qt::Key_MediaPause;
3313  break;
3314  default:
3315  break;
3316  }
3317 
3318  if (keycode > 0)
3319  {
3320  auto *key = new QKeyEvent(QEvent::KeyPress, keycode, eKeyEvent->modifiers());
3321  QCoreApplication::postEvent(this, key);
3322  }
3323  }
3324  }
3325 #endif
3326 
3327  QStringList actions;
3328  bool handled = false;
3329  bool alreadyTranslatedPlayback = false;
3330 
3331  TVState state = GetState();
3332  bool isLiveTV = StateIsLiveTV(state);
3333 
3334  if (ignoreKeys)
3335  {
3336  handled = TranslateKeyPressOrGesture("TV Playback", Event, actions, isLiveTV);
3337  alreadyTranslatedPlayback = true;
3338 
3339  if (handled || actions.isEmpty())
3340  return handled;
3341 
3342  bool esc = IsActionable({ "ESCAPE", "BACK" }, actions);
3343  bool pause = IsActionable(ACTION_PAUSE, actions);
3344  bool play = IsActionable(ACTION_PLAY, actions);
3345 
3346  if ((!esc || m_overlayState.m_browsing) && !pause && !play)
3347  return false;
3348  }
3349 
3350  OSD *osd = GetOSDL();
3351  if (osd && osd->DialogVisible())
3352  {
3353  if (QEvent::KeyPress == Event->type())
3354  {
3355  auto *qke = dynamic_cast<QKeyEvent*>(Event);
3356  handled = (qke != nullptr) && osd->DialogHandleKeypress(qke);
3357  }
3358  if (MythGestureEvent::kEventType == Event->type())
3359  {
3360  auto *mge = dynamic_cast<MythGestureEvent*>(Event);
3361  handled = (mge != nullptr) && osd->DialogHandleGesture(mge);
3362  }
3363  }
3364  ReturnOSDLock();
3365 
3366  if (m_overlayState.m_editing && !handled)
3367  {
3368  handled |= TranslateKeyPressOrGesture("TV Editing", Event, actions, isLiveTV);
3369 
3370  if (!handled && m_player)
3371  {
3372  if (IsActionable("MENU", actions))
3373  {
3374  ShowOSDCutpoint("EDIT_CUT_POINTS");
3375  handled = true;
3376  }
3377  if (IsActionable(ACTION_MENUCOMPACT, actions))
3378  {
3379  ShowOSDCutpoint("EDIT_CUT_POINTS_COMPACT");
3380  handled = true;
3381  }
3382  if (IsActionable("ESCAPE", actions))
3383  {
3384  if (!m_player->IsCutListSaved())
3385  ShowOSDCutpoint("EXIT_EDIT_MODE");
3386  else
3387  {
3388  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3389  m_player->DisableEdit(0);
3390  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3391  }
3392  handled = true;
3393  }
3394  else
3395  {
3396  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3397  auto current_frame = m_player->GetFramesPlayed();
3398  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3399  if ((IsActionable(ACTION_SELECT, actions)) && (m_player->IsInDelete(current_frame)) &&
3400  (!(m_player->HasTemporaryMark())))
3401  {
3402  ShowOSDCutpoint("EDIT_CUT_POINTS");
3403  handled = true;
3404  }
3405  else
3406  {
3407  handled |= m_player->HandleProgramEditorActions(actions);
3408  }
3409  }
3410  }
3411  }
3412 
3413  if (handled)
3414  return true;
3415 
3416  // If text is already queued up, be more lax on what is ok.
3417  // This allows hex teletext entry and minor channel entry.
3418  if (QEvent::KeyPress == Event->type())
3419  {
3420  auto *qke = dynamic_cast<QKeyEvent*>(Event);
3421  if (qke == nullptr)
3422  return false;
3423  const QString txt = qke->text();
3424  if (HasQueuedInput() && (1 == txt.length()))
3425  {
3426  bool ok = false;
3427  (void)txt.toInt(&ok, 16);
3428  if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
3429  {
3430  AddKeyToInputQueue(txt.at(0).toLatin1());
3431  return true;
3432  }
3433  }
3434  }
3435 
3436  // Teletext menu
3438  {
3439  QStringList tt_actions;
3440  handled = TranslateKeyPressOrGesture("Teletext Menu", Event, tt_actions, isLiveTV);
3441 
3442  if (!handled && !tt_actions.isEmpty())
3443  {
3444  for (int i = 0; i < tt_actions.size(); i++)
3445  {
3446  emit HandleTeletextAction(tt_actions[i], handled);
3447  if (handled)
3448  return true;
3449  }
3450  }
3451  }
3452 
3453  // Interactive television
3455  {
3456  if (!alreadyTranslatedPlayback)
3457  {
3458  handled = TranslateKeyPressOrGesture("TV Playback", Event, actions, isLiveTV);
3459  alreadyTranslatedPlayback = true;
3460  }
3461 
3462  if (!handled && !actions.isEmpty())
3463  {
3464  for (int i = 0; i < actions.size(); i++)
3465  {
3466  emit HandleITVAction(actions[i], handled);
3467  if (handled)
3468  return true;
3469  }
3470  }
3471  }
3472 
3473  if (!alreadyTranslatedPlayback)
3474  handled = TranslateKeyPressOrGesture("TV Playback", Event, actions, isLiveTV);
3475 
3476  if (handled || actions.isEmpty())
3477  return handled;
3478 
3479  handled = false;
3480 
3483 
3484  if (QEvent::KeyPress == Event->type())
3485  handled = handled || SysEventHandleAction(m_mainWindow, dynamic_cast<QKeyEvent*>(Event), actions);
3486  handled = handled || BrowseHandleAction(actions);
3487  handled = handled || ManualZoomHandleAction(actions);
3488  handled = handled || PictureAttributeHandleAction(actions);
3489  handled = handled || TimeStretchHandleAction(actions);
3490  handled = handled || AudioSyncHandleAction(actions);
3491  handled = handled || SubtitleZoomHandleAction(actions);
3492  handled = handled || SubtitleDelayHandleAction(actions);
3493  handled = handled || DiscMenuHandleAction(actions);
3494  handled = handled || ActiveHandleAction(actions, isDVD, isMenuOrStill);
3495  handled = handled || ToggleHandleAction(actions, isDVD);
3496  handled = handled || FFRewHandleAction(actions);
3497  handled = handled || ActivePostQHandleAction(actions);
3498 
3499 #if DEBUG_ACTIONS
3500  for (int i = 0; i < actions.size(); ++i)
3501  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("handled(%1) actions[%2](%3)")
3502  .arg(handled).arg(i).arg(actions[i]));
3503 #endif // DEBUG_ACTIONS
3504 
3505  if (handled)
3506  return true;
3507 
3508  if (!handled)
3509  {
3510  for (int i = 0; i < actions.size() && !handled; i++)
3511  {
3512  QString action = actions[i];
3513  bool ok = false;
3514  int val = action.toInt(&ok);
3515 
3516  if (ok)
3517  {
3518  AddKeyToInputQueue(static_cast<char>('0' + val));
3519  handled = true;
3520  }
3521  }
3522  }
3523 
3524  return true;
3525 }
3526 
3527 bool TV::BrowseHandleAction(const QStringList &Actions)
3528 {
3530  return false;
3531 
3532  bool handled = true;
3533 
3534  if (IsActionable({ ACTION_UP, ACTION_CHANNELUP }, Actions))
3536  else if (IsActionable( { ACTION_DOWN, ACTION_CHANNELDOWN }, Actions))
3538  else if (IsActionable(ACTION_LEFT, Actions))
3540  else if (IsActionable(ACTION_RIGHT, Actions))
3542  else if (IsActionable("NEXTFAV", Actions))
3544  else if (IsActionable(ACTION_SELECT, Actions))
3545  BrowseEnd(true);
3546  else if (IsActionable({ ACTION_CLEAROSD, "ESCAPE", "BACK", "TOGGLEBROWSE" }, Actions))
3547  BrowseEnd(false);
3548  else if (IsActionable(ACTION_TOGGLERECORD, Actions))
3549  QuickRecord();
3550  else
3551  {
3552  handled = false;
3553  for (const auto& action : qAsConst(Actions))
3554  {
3555  if (action.length() == 1 && action[0].isDigit())
3556  {
3557  AddKeyToInputQueue(action[0].toLatin1());
3558  handled = true;
3559  }
3560  }
3561  }
3562 
3563  // only pass-through actions listed below
3564  static const QStringList passthrough =
3565  {
3566  ACTION_VOLUMEUP, ACTION_VOLUMEDOWN, "STRETCHINC", "STRETCHDEC",
3567  ACTION_MUTEAUDIO, "CYCLEAUDIOCHAN", "BOTTOMLINEMOVE", "BOTTOMLINESAVE", "TOGGLEASPECT"
3568  };
3569  return handled || !IsActionable(passthrough, Actions);
3570 }
3571 
3572 bool TV::ManualZoomHandleAction(const QStringList &Actions)
3573 {
3574  if (!m_zoomMode)
3575  return false;
3576 
3577  bool endmanualzoom = false;
3578  bool handled = true;
3579  bool updateOSD = true;
3580  ZoomDirection zoom = kZoom_END;
3582  zoom = kZoomUp;
3584  zoom = kZoomDown;
3585  else if (IsActionable({ ACTION_ZOOMLEFT, ACTION_LEFT }, Actions))
3586  zoom = kZoomLeft;
3587  else if (IsActionable({ ACTION_ZOOMRIGHT, ACTION_RIGHT }, Actions))
3588  zoom = kZoomRight;
3589  else if (IsActionable({ ACTION_ZOOMASPECTUP, ACTION_VOLUMEUP }, Actions))
3590  zoom = kZoomAspectUp;
3591  else if (IsActionable({ ACTION_ZOOMASPECTDOWN, ACTION_VOLUMEDOWN }, Actions))
3592  zoom = kZoomAspectDown;
3593  else if (IsActionable({ ACTION_ZOOMIN, ACTION_JUMPFFWD }, Actions))
3594  zoom = kZoomIn;
3595  else if (IsActionable({ ACTION_ZOOMOUT, ACTION_JUMPRWND }, Actions))
3596  zoom = kZoomOut;
3597  else if (IsActionable(ACTION_ZOOMVERTICALIN, Actions))
3598  zoom = kZoomVerticalIn;
3599  else if (IsActionable(ACTION_ZOOMVERTICALOUT, Actions))
3600  zoom = kZoomVerticalOut;
3601  else if (IsActionable(ACTION_ZOOMHORIZONTALIN, Actions))
3602  zoom = kZoomHorizontalIn;
3603  else if (IsActionable(ACTION_ZOOMHORIZONTALOUT, Actions))
3604  zoom = kZoomHorizontalOut;
3605  else if (IsActionable({ ACTION_ZOOMQUIT, "ESCAPE", "BACK" }, Actions))
3606  {
3607  zoom = kZoomHome;
3608  endmanualzoom = true;
3609  }
3610  else if (IsActionable({ ACTION_ZOOMCOMMIT, ACTION_SELECT }, Actions))
3611  {
3612  endmanualzoom = true;
3613  SetManualZoom(false, tr("Zoom Committed"));
3614  }
3615  else
3616  {
3617  updateOSD = false;
3618  // only pass-through actions listed below
3619  static const QStringList passthrough =
3620  {
3621  "STRETCHINC", "STRETCHDEC", ACTION_MUTEAUDIO,
3622  "CYCLEAUDIOCHAN", ACTION_PAUSE, ACTION_CLEAROSD
3623  };
3624  handled = !IsActionable(passthrough, Actions);
3625  }
3626 
3627  QString msg = tr("Zoom Committed");
3628  if (zoom != kZoom_END)
3629  {
3630  emit ChangeZoom(zoom);
3631  msg = endmanualzoom ? tr("Zoom Ignored") :
3635  }
3636  else if (endmanualzoom)
3637  {
3638  msg = tr("%1 Committed").arg(GetZoomString(m_videoBoundsState.m_manualHorizScale,
3641  }
3642 
3643  if (updateOSD)
3644  SetManualZoom(!endmanualzoom, msg);
3645 
3646  return handled;
3647 }
3648 
3649 bool TV::PictureAttributeHandleAction(const QStringList &Actions)
3650 {
3651  if (!m_adjustingPicture)
3652  return false;
3653 
3654  bool up = IsActionable(ACTION_RIGHT, Actions);
3655  bool down = up ? false : IsActionable(ACTION_LEFT, Actions);
3656  if (!(up || down))
3657  return false;
3658 
3660  {
3662  VolumeChange(up);
3663  else
3665  return true;
3666  }
3667 
3668  int value = 99;
3672  UpdateOSDStatus(toTitleString(m_adjustingPicture), text, QString::number(value),
3674  emit ChangeOSDPositionUpdates(false);
3675  return true;
3676 }
3677 
3678 bool TV::TimeStretchHandleAction(const QStringList &Actions)
3679 {
3680  if (!m_stretchAdjustment)
3681  return false;
3682 
3683  bool handled = true;
3684 
3685  if (IsActionable(ACTION_LEFT, Actions))
3686  ChangeTimeStretch(-1);
3687  else if (IsActionable(ACTION_RIGHT, Actions))
3688  ChangeTimeStretch(1);
3689  else if (IsActionable(ACTION_DOWN, Actions))
3690  ChangeTimeStretch(-5);
3691  else if (IsActionable(ACTION_UP, Actions))
3692  ChangeTimeStretch(5);
3693  else if (IsActionable("ADJUSTSTRETCH", Actions))
3695  else if (IsActionable(ACTION_SELECT, Actions))
3696  ClearOSD();
3697  else
3698  handled = false;
3699 
3700  return handled;
3701 }
3702 
3703 bool TV::AudioSyncHandleAction(const QStringList& Actions)
3704 {
3705  if (!m_audiosyncAdjustment)
3706  return false;
3707 
3708  bool handled = true;
3709 
3710  if (IsActionable(ACTION_LEFT, Actions))
3711  emit ChangeAudioOffset(-1);
3712  else if (IsActionable(ACTION_RIGHT, Actions))
3713  emit ChangeAudioOffset(1);
3714  else if (IsActionable(ACTION_UP, Actions))
3715  emit ChangeAudioOffset(10);
3716  else if (IsActionable(ACTION_DOWN, Actions))
3717  emit ChangeAudioOffset(-10);
3718  else if (IsActionable({ ACTION_TOGGELAUDIOSYNC, ACTION_SELECT }, Actions))
3719  ClearOSD();
3720  else
3721  handled = false;
3722 
3723  return handled;
3724 }
3725 
3726 bool TV::SubtitleZoomHandleAction(const QStringList &Actions)
3727 {
3729  return false;
3730 
3731  bool handled = true;
3732 
3733  if (IsActionable(ACTION_LEFT, Actions))
3734  emit AdjustSubtitleZoom(-1);
3735  else if (IsActionable(ACTION_RIGHT, Actions))
3736  emit AdjustSubtitleZoom(1);
3737  else if (IsActionable(ACTION_UP, Actions))
3738  emit AdjustSubtitleZoom(10);
3739  else if (IsActionable(ACTION_DOWN, Actions))
3740  emit AdjustSubtitleZoom(-10);
3741  else if (IsActionable({ ACTION_TOGGLESUBTITLEZOOM, ACTION_SELECT }, Actions))
3742  ClearOSD();
3743  else
3744  handled = false;
3745 
3746  return handled;
3747 }
3748 
3749 bool TV::SubtitleDelayHandleAction(const QStringList &Actions)
3750 {
3752  return false;
3753 
3754  bool handled = true;
3755 
3756  if (IsActionable(ACTION_LEFT, Actions))
3757  emit AdjustSubtitleDelay(-5);
3758  else if (IsActionable(ACTION_RIGHT, Actions))
3759  emit AdjustSubtitleDelay(5);
3760  else if (IsActionable(ACTION_UP, Actions))
3761  emit AdjustSubtitleDelay(25);
3762  else if (IsActionable(ACTION_DOWN, Actions))
3763  emit AdjustSubtitleDelay(-25);
3764  else if (IsActionable({ ACTION_TOGGLESUBTITLEDELAY, ACTION_SELECT }, Actions))
3765  ClearOSD();
3766  else
3767  handled = false;
3768 
3769  return handled;
3770 }
3771 
3772 bool TV::DiscMenuHandleAction(const QStringList& Actions) const
3773 {
3774  int64_t pts = 0;
3776  if (output)
3777  {
3778  MythVideoFrame *frame = output->GetLastShownFrame();
3779  // convert timecode (msec) to pts (90kHz)
3780  if (frame)
3781  pts = static_cast<int64_t>(frame->m_timecode * 90);
3782  }
3784  return m_playerContext.m_buffer->HandleAction(Actions, pts);
3785  return false;
3786 }
3787 
3788 bool TV::ActiveHandleAction(const QStringList &Actions,
3789  bool IsDVD, bool IsDVDStillFrame)
3790 {
3791  bool handled = true;
3792 
3793  if (IsActionable("SKIPCOMMERCIAL", Actions) && !IsDVD)
3794  DoSkipCommercials(1);
3795  else if (IsActionable("SKIPCOMMBACK", Actions) && !IsDVD)
3796  DoSkipCommercials(-1);
3797  else if (IsActionable("QUEUETRANSCODE", Actions) && !IsDVD)
3798  DoQueueTranscode("Default");
3799  else if (IsActionable("QUEUETRANSCODE_AUTO", Actions) && !IsDVD)
3800  DoQueueTranscode("Autodetect");
3801  else if (IsActionable("QUEUETRANSCODE_HIGH", Actions) && !IsDVD)
3802  DoQueueTranscode("High Quality");
3803  else if (IsActionable("QUEUETRANSCODE_MEDIUM", Actions) && !IsDVD)
3804  DoQueueTranscode("Medium Quality");
3805  else if (IsActionable("QUEUETRANSCODE_LOW", Actions) && !IsDVD)
3806  DoQueueTranscode("Low Quality");
3807  else if (IsActionable(ACTION_PLAY, Actions))
3808  DoPlay();
3809  else if (IsActionable(ACTION_PAUSE, Actions))
3810  DoTogglePause(true);
3811  else if (IsActionable("SPEEDINC", Actions) && !IsDVDStillFrame)
3812  ChangeSpeed(1);
3813  else if (IsActionable("SPEEDDEC", Actions) && !IsDVDStillFrame)
3814  ChangeSpeed(-1);
3815  else if (IsActionable("ADJUSTSTRETCH", Actions))
3816  ChangeTimeStretch(0); // just display
3817  else if (IsActionable("CYCLECOMMSKIPMODE",Actions) && !IsDVD)
3819  else if (IsActionable("NEXTSCAN", Actions))
3820  {
3821  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3823  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3824  OverrideScan(scan);
3825  }
3826  else if (IsActionable(ACTION_SEEKARB, Actions) && !IsDVD)
3827  {
3828  if (m_asInputMode)
3829  {
3830  ClearInputQueues(true);
3831  emit ChangeOSDText(OSD_WIN_INPUT, {{"osd_number_entry", tr("Seek:")}}, kOSDTimeout_Med);
3832  m_asInputMode = false;
3833  if (m_asInputTimerId)
3834  {
3836  m_asInputTimerId = 0;
3837  }
3838  }
3839  else
3840  {
3841  ClearInputQueues(false);
3842  AddKeyToInputQueue(0);
3843  m_asInputMode = true;
3844  m_ccInputMode = false;
3846  if (m_ccInputTimerId)
3847  {
3849  m_ccInputTimerId = 0;
3850  }
3851  }
3852  }
3853  else if (IsActionable(ACTION_JUMPRWND, Actions))
3854  DoJumpRWND();
3855  else if (IsActionable(ACTION_JUMPFFWD, Actions))
3856  DoJumpFFWD();
3857  else if (IsActionable(ACTION_JUMPBKMRK, Actions))
3858  {
3859  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3860  uint64_t bookmark = m_player->GetBookmark();
3861  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3862 
3863  if (bookmark)
3864  {
3865  DoPlayerSeekToFrame(bookmark);
3866  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3867  UpdateOSDSeekMessage(tr("Jump to Bookmark"), kOSDTimeout_Med);
3868  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3869  }
3870  }
3871  else if (IsActionable(ACTION_JUMPSTART,Actions))
3872  {
3873  DoSeek(0, tr("Jump to Beginning"), /*timeIsOffset*/false, /*honorCutlist*/true);
3874  }
3875  else if (IsActionable(ACTION_CLEAROSD, Actions))
3876  {
3877  ClearOSD();
3878  }
3879  else if (IsActionable(ACTION_VIEWSCHEDULED, Actions))
3881  else if (HandleJumpToProgramAction(Actions))
3882  { // NOLINT(bugprone-branch-clone)
3883  }
3884  else if (IsActionable(ACTION_SIGNALMON, Actions))
3885  {
3887  {
3888  QString input = m_playerContext.m_recorder->GetInput();
3890 
3891  if (timeout == 0xffffffff)
3892  {
3893  emit ChangeOSDMessage("No Signal Monitor");
3894  return false;
3895  }
3896 
3897  int rate = m_sigMonMode ? 0 : 100;
3898  bool notify = !m_sigMonMode;
3899 
3900  PauseLiveTV();
3902  UnpauseLiveTV();
3903 
3904  m_lockTimerOn = false;
3906  }
3907  }
3908  else if (IsActionable(ACTION_SCREENSHOT, Actions))
3909  {
3911  }
3912  else if (IsActionable(ACTION_STOP, Actions))
3913  {
3914  PrepareToExitPlayer(__LINE__);
3915  SetExitPlayer(true, true);
3916  }
3917  else if (IsActionable(ACTION_EXITSHOWNOPROMPTS, Actions))
3918  {
3919  m_requestDelete = false;
3920  PrepareToExitPlayer(__LINE__);
3921  SetExitPlayer(true, true);
3922  }
3923  else if (IsActionable({ "ESCAPE", "BACK" }, Actions))
3924  {
3927  {
3928  ClearOSD();
3929  }
3930  else
3931  {
3932  bool visible = false;
3933  emit IsOSDVisible(visible);
3934  if (visible)
3935  {
3936  ClearOSD();
3937  return handled;
3938  }
3939  }
3940 
3941  NormalSpeed();
3942  StopFFRew();
3943  bool exit = false;
3944  if (StateIsLiveTV(GetState()))
3945  {
3947  {
3949  return handled;
3950  }
3951  exit = true;
3952  }
3953  else
3954  {
3956  !m_underNetworkControl && !IsDVDStillFrame)
3957  {
3959  return handled;
3960  }
3961  PrepareToExitPlayer(__LINE__);
3962  m_requestDelete = false;
3963  exit = true;
3964  }
3965 
3966  if (exit)
3967  {
3968  // If it's a DVD, and we're not trying to execute a
3969  // jumppoint, try to back up.
3970  if (IsDVD && !m_mainWindow->IsExitingToMain() && IsActionable("BACK", Actions) &&
3972  {
3973  return handled;
3974  }
3975  SetExitPlayer(true, true);
3976  }
3977  }
3978  else if (IsActionable(ACTION_ENABLEUPMIX, Actions))
3979  emit ChangeUpmix(true);
3980  else if (IsActionable(ACTION_DISABLEUPMIX, Actions))
3981  emit ChangeUpmix(false);
3982  else if (IsActionable(ACTION_VOLUMEDOWN, Actions))
3983  VolumeChange(false);
3984  else if (IsActionable(ACTION_VOLUMEUP, Actions))
3985  VolumeChange(true);
3986  else if (IsActionable("CYCLEAUDIOCHAN", Actions))
3987  emit ChangeMuteState(true);
3988  else if (IsActionable(ACTION_MUTEAUDIO, Actions))
3989  emit ChangeMuteState();
3990  else if (IsActionable("STRETCHINC", Actions))
3991  ChangeTimeStretch(1);
3992  else if (IsActionable("STRETCHDEC", Actions))
3993  ChangeTimeStretch(-1);
3994  else if (IsActionable("MENU", Actions))
3995  ShowOSDMenu();
3996  else if (IsActionable(ACTION_MENUCOMPACT, Actions))
3997  ShowOSDMenu(true);
3998  else if (IsActionable({ "INFO", "INFOWITHCUTLIST" }, Actions))
3999  {
4000  if (HasQueuedInput())
4001  DoArbSeek(ARBSEEK_SET, IsActionable("INFOWITHCUTLIST", Actions));
4002  else
4003  ToggleOSD(true);
4004  }
4005  else if (IsActionable(ACTION_TOGGLEOSDDEBUG, Actions))
4006  emit ChangeOSDDebug();
4007  else if (!IsDVDStillFrame && SeekHandleAction(Actions, IsDVD))
4008  {
4009  }
4010  else
4011  {
4012  handled = false;
4013  for (auto it = Actions.cbegin(); it != Actions.cend() && !handled; ++it)
4014  handled = HandleTrackAction(*it);
4015  }
4016 
4017  return handled;
4018 }
4019 
4020 bool TV::FFRewHandleAction(const QStringList &Actions)
4021 {
4022  bool handled = false;
4023 
4025  {
4026  for (int i = 0; i < Actions.size() && !handled; i++)
4027  {
4028  QString action = Actions[i];
4029  bool ok = false;
4030  int val = action.toInt(&ok);
4031 
4032  if (ok && val < static_cast<int>(m_ffRewSpeeds.size()))
4033  {
4034  SetFFRew(val);
4035  handled = true;
4036  }
4037  }
4038 
4039  if (!handled)
4040  {
4043  handled = true;
4044  }
4045  }
4046 
4048  {
4049  NormalSpeed();
4051  handled = true;
4052  }
4053 
4054  return handled;
4055 }
4056 
4057 bool TV::ToggleHandleAction(const QStringList &Actions, bool IsDVD)
4058 {
4059  bool handled = true;
4060  bool islivetv = StateIsLiveTV(GetState());
4061 
4062  if (IsActionable(ACTION_BOTTOMLINEMOVE, Actions))
4063  emit ToggleMoveBottomLine();
4064  else if (IsActionable(ACTION_BOTTOMLINESAVE, Actions))
4065  emit SaveBottomLine();
4066  else if (IsActionable("TOGGLEASPECT", Actions))
4067  emit ChangeAspectOverride();
4068  else if (IsActionable("TOGGLEFILL", Actions))
4069  emit ChangeAdjustFill();
4070  else if (IsActionable(ACTION_TOGGELAUDIOSYNC, Actions))
4071  emit ChangeAudioOffset(0); // just display
4072  else if (IsActionable(ACTION_TOGGLESUBTITLEZOOM, Actions))
4073  emit AdjustSubtitleZoom(0); // just display
4074  else if (IsActionable(ACTION_TOGGLESUBTITLEDELAY, Actions))
4075  emit AdjustSubtitleDelay(0); // just display
4076  else if (IsActionable(ACTION_TOGGLEVISUALISATION, Actions))
4077  emit EnableVisualiser(false, true);
4078  else if (IsActionable(ACTION_ENABLEVISUALISATION, Actions))
4079  emit EnableVisualiser(true);
4080  else if (IsActionable(ACTION_DISABLEVISUALISATION, Actions))
4081  emit EnableVisualiser(false);
4082  else if (IsActionable("TOGGLEPICCONTROLS", Actions))
4084  else if (IsActionable("TOGGLESTRETCH", Actions))
4086  else if (IsActionable(ACTION_TOGGLEUPMIX, Actions))
4087  emit ChangeUpmix(false, true);
4088  else if (IsActionable(ACTION_TOGGLESLEEP, Actions))
4089  ToggleSleepTimer();
4090  else if (IsActionable(ACTION_TOGGLERECORD, Actions) && islivetv)
4091  QuickRecord();
4092  else if (IsActionable(ACTION_TOGGLEFAV, Actions) && islivetv)
4094  else if (IsActionable(ACTION_TOGGLECHANCONTROLS, Actions) && islivetv)
4096  else if (IsActionable(ACTION_TOGGLERECCONTROLS, Actions) && islivetv)
4098  else if (IsActionable("TOGGLEBROWSE", Actions))
4099  {
4100  if (islivetv)
4101  BrowseStart();
4102  else if (!IsDVD)
4103  ShowOSDMenu();
4104  else
4105  handled = false;
4106  }
4107  else if (IsActionable("EDIT", Actions))
4108  {
4109  if (islivetv)
4111  else if (!IsDVD)
4113  }
4114  else if (IsActionable(ACTION_OSDNAVIGATION, Actions))
4115  {
4117  }
4118  else
4119  {
4120  handled = false;
4121  }
4122 
4123  return handled;
4124 }
4125 
4127 {
4128  if (Clear)
4129  {
4130  emit UpdateBookmark(true);
4131  emit ChangeOSDMessage(tr("Bookmark Cleared"));
4132  }
4133  else // if (IsBookmarkAllowed(ctx))
4134  {
4135  emit UpdateBookmark();
4136  osdInfo info;
4138  info.text["title"] = tr("Position");
4140  emit ChangeOSDMessage(tr("Bookmark Saved"));
4141  }
4142 }
4143 
4144 bool TV::ActivePostQHandleAction(const QStringList &Actions)
4145 {
4146  bool handled = true;
4147  TVState state = GetState();
4148  bool islivetv = StateIsLiveTV(state);
4149  bool isdvd = state == kState_WatchingDVD;
4150  bool isdisc = isdvd || state == kState_WatchingBD;
4151 
4152  if (IsActionable(ACTION_SETBOOKMARK, Actions))
4153  {
4154  if (!CommitQueuedInput())
4155  {
4156  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4157  SetBookmark(false);
4158  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4159  }
4160  }
4161  if (IsActionable(ACTION_TOGGLEBOOKMARK, Actions))
4162  {
4163  if (!CommitQueuedInput())
4164  {
4165  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4166  SetBookmark(m_player->GetBookmark() != 0U);
4167  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4168  }
4169  }
4170  else if (IsActionable("NEXTFAV", Actions) && islivetv)
4172  else if (IsActionable("NEXTSOURCE", Actions) && islivetv)
4174  else if (IsActionable("PREVSOURCE", Actions) && islivetv)
4176  else if (IsActionable("NEXTINPUT", Actions) && islivetv)
4177  SwitchInputs();
4178  else if (IsActionable(ACTION_GUIDE, Actions))
4180  else if (IsActionable("PREVCHAN", Actions) && islivetv)
4181  PopPreviousChannel(false);
4182  else if (IsActionable(ACTION_CHANNELUP, Actions))
4183  {
4184  if (islivetv)
4185  {
4186  if (m_dbBrowseAlways)
4188  else
4190  }
4191  else
4192  DoJumpRWND();
4193  }
4194  else if (IsActionable(ACTION_CHANNELDOWN, Actions))
4195  {
4196  if (islivetv)
4197  {
4198  if (m_dbBrowseAlways)
4200  else
4202  }
4203  else
4204  DoJumpFFWD();
4205  }
4206  else if (IsActionable("DELETE", Actions) && !islivetv)
4207  {
4208  NormalSpeed();
4209  StopFFRew();
4210  SetBookmark();
4211  ShowOSDPromptDeleteRecording(tr("Are you sure you want to delete:"));
4212  }
4213  else if (IsActionable(ACTION_JUMPTODVDROOTMENU, Actions) && isdisc)
4214  {
4215  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4216  if (m_player)
4217  m_player->GoToMenu("root");
4218  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4219  }
4220  else if (IsActionable(ACTION_JUMPTODVDCHAPTERMENU, Actions) && isdisc)
4221  {
4222  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4223  if (m_player)
4224  m_player->GoToMenu("chapter");
4225  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4226  }
4227  else if (IsActionable(ACTION_JUMPTODVDTITLEMENU, Actions) && isdisc)
4228  {
4229  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4230  if (m_player)
4231  m_player->GoToMenu("title");
4232  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4233  }
4234  else if (IsActionable(ACTION_JUMPTOPOPUPMENU, Actions) && isdisc)
4235  {
4236  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4237  if (m_player)
4238  m_player->GoToMenu("popup");
4239  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4240  }
4241  else if (IsActionable(ACTION_FINDER, Actions))
4243  else
4244  handled = false;
4245 
4246  return handled;
4247 }
4248 
4249 
4251 {
4252  bool ignoreKeys = m_playerContext.IsPlayerChangingBuffers();
4253 
4254 #ifdef DEBUG_ACTIONS
4255  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("(%1) ignoreKeys: %2").arg(Command).arg(ignoreKeys));
4256 #endif
4257 
4258  if (ignoreKeys)
4259  {
4260  LOG(VB_GENERAL, LOG_WARNING, LOC + "Ignoring network control command because ignoreKeys is set");
4261  return;
4262  }
4263 
4264 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
4265  QStringList tokens = Command.split(" ", QString::SkipEmptyParts);
4266 #else
4267  QStringList tokens = Command.split(" ", Qt::SkipEmptyParts);
4268 #endif
4269  if (tokens.size() < 2)
4270  {
4271  LOG(VB_GENERAL, LOG_ERR, LOC + "Not enough tokens in network control command " + QString("'%1'").arg(Command));
4272  return;
4273  }
4274 
4275  OSD *osd = GetOSDL();
4276  bool dlg = false;
4277  if (osd)
4278  dlg = osd->DialogVisible();
4279  ReturnOSDLock();
4280 
4281  if (dlg)
4282  {
4283  LOG(VB_GENERAL, LOG_WARNING, LOC +
4284  "Ignoring network control command\n\t\t\t" +
4285  QString("because dialog is waiting for a response"));
4286  return;
4287  }
4288 
4289  if (tokens[1] != "QUERY")
4290  ClearOSD();
4291 
4292  if (tokens.size() == 3 && tokens[1] == "CHANID")
4293  {
4294  m_queuedChanID = tokens[2].toUInt();
4295  m_queuedChanNum.clear();
4297  }
4298  else if (tokens.size() == 3 && tokens[1] == "CHANNEL")
4299  {
4300  if (StateIsLiveTV(GetState()))
4301  {
4302  if (tokens[2] == "UP")
4304  else if (tokens[2] == "DOWN")
4306  else if (tokens[2].contains(QRegularExpression(R"(^[-\.\d_#]+$)")))
4307  ChangeChannel(0, tokens[2]);
4308  }
4309  }
4310  else if (tokens.size() == 3 && tokens[1] == "SPEED")
4311  {
4312  bool paused = ContextIsPaused(__FILE__, __LINE__);
4313 
4314  if (tokens[2] == "0x")
4315  {
4316  NormalSpeed();
4317  StopFFRew();
4318  if (!paused)
4319  DoTogglePause(true);
4320  }
4321  else if (tokens[2] == "normal")
4322  {
4323  NormalSpeed();
4324  StopFFRew();
4325  if (paused)
4326  DoTogglePause(true);
4327  return;
4328  }
4329  else
4330  {
4331  float tmpSpeed = 1.0F;
4332  bool ok = false;
4333 
4334  if (tokens[2].contains(QRegularExpression(R"(^\-*(\d*\.)?\d+x$)")))
4335  {
4336  QString speed = tokens[2].left(tokens[2].length()-1);
4337  tmpSpeed = speed.toFloat(&ok);
4338  }
4339  else
4340  {
4341  QRegularExpression re { R"(^(\-*\d+)\/(\d+)x$)" };
4342  auto match = re.match(tokens[2]);
4343  if (match.hasMatch())
4344  {
4345  QStringList matches = match.capturedTexts();
4346  int numerator = matches[1].toInt(&ok);
4347  int denominator = matches[2].toInt(&ok);
4348 
4349  if (ok && denominator != 0)
4350  tmpSpeed = static_cast<float>(numerator) / static_cast<float>(denominator);
4351  else
4352  ok = false;
4353  }
4354  }
4355 
4356  if (ok)
4357  {
4358  float searchSpeed = fabs(tmpSpeed);
4359 
4360  if (paused)
4361  DoTogglePause(true);
4362 
4363  if (tmpSpeed == 0.0F)
4364  {
4365  NormalSpeed();
4366  StopFFRew();
4367 
4368  if (!paused)
4369  DoTogglePause(true);
4370  }
4371  else if (tmpSpeed == 1.0F)
4372  {
4373  StopFFRew();
4374  m_playerContext.m_tsNormal = 1.0F;
4375  ChangeTimeStretch(0, false);
4376  return;
4377  }
4378 
4379  NormalSpeed();
4380 
4381  size_t index = 0;
4382  for ( ; index < m_ffRewSpeeds.size(); index++)
4383  if (m_ffRewSpeeds[index] == static_cast<int>(searchSpeed))
4384  break;
4385 
4386  if ((index < m_ffRewSpeeds.size()) && (m_ffRewSpeeds[index] == static_cast<int>(searchSpeed)))
4387  {
4388  if (tmpSpeed < 0)
4390  else if (tmpSpeed > 1)
4392  else
4393  StopFFRew();
4394 
4396  SetFFRew(static_cast<int>(index));
4397  }
4398  else if (0.48F <= tmpSpeed && tmpSpeed <= 2.0F)
4399  {
4400  StopFFRew();
4401  m_playerContext.m_tsNormal = tmpSpeed; // alter speed before display
4402  ChangeTimeStretch(0, false);
4403  }
4404  else
4405  {
4406  LOG(VB_GENERAL, LOG_WARNING, QString("Couldn't find %1 speed. Setting Speed to 1x")
4407  .arg(static_cast<double>(searchSpeed)));
4410  }
4411  }
4412  else
4413  {
4414  LOG(VB_GENERAL, LOG_ERR, QString("Found an unknown speed of %1").arg(tokens[2]));
4415  }
4416  }
4417  }
4418  else if (tokens.size() == 2 && tokens[1] == "STOP")
4419  {
4420  SetBookmark();
4421  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4423  m_player->SetWatched();
4424  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4425  SetExitPlayer(true, true);
4426  }
4427  else if (tokens.size() >= 3 && tokens[1] == "SEEK" && m_playerContext.HasPlayer())
4428  {
4430  return;
4431 
4432  if (tokens[2] == "BEGINNING")
4433  {
4434  DoSeek(0, tr("Jump to Beginning"), /*timeIsOffset*/false, /*honorCutlist*/true);
4435  }
4436  else if (tokens[2] == "FORWARD")
4437  {
4438  DoSeek(m_playerContext.m_fftime, tr("Skip Ahead"), /*timeIsOffset*/true, /*honorCutlist*/true);
4439  }
4440  else if (tokens[2] == "BACKWARD")
4441  {
4442  DoSeek(-m_playerContext.m_rewtime, tr("Skip Back"), /*timeIsOffset*/true, /*honorCutlist*/true);
4443  }
4444  else if ((tokens[2] == "POSITION" ||
4445  tokens[2] == "POSITIONWITHCUTLIST") &&
4446  (tokens.size() == 4) &&
4447  (tokens[3].contains(QRegularExpression("^\\d+$"))))
4448  {
4449  DoSeekAbsolute(tokens[3].toInt(), tokens[2] == "POSITIONWITHCUTLIST");
4450  }
4451  }
4452  else if (tokens.size() >= 3 && tokens[1] == "SUBTITLES")
4453  {
4454  bool ok = false;
4455  uint track = tokens[2].toUInt(&ok);
4456 
4457  if (!ok)
4458  return;
4459 
4460  if (track == 0)
4461  {
4462  emit SetCaptionsEnabled(false, true);
4463  }
4464  else
4465  {
4466  QStringList subs = m_player->GetTracks(kTrackTypeSubtitle);
4467  uint size = static_cast<uint>(subs.size());
4468  uint start = 1;
4469  uint finish = start + size;
4470  if (track >= start && track < finish)
4471  {
4472  emit SetTrack(kTrackTypeSubtitle, track - start);
4474  return;
4475  }
4476 
4477  start = finish + 1;
4479  finish = start + size;
4480  if (track >= start && track < finish)
4481  {
4482  emit SetTrack(kTrackTypeCC708, track - start);
4484  return;
4485  }
4486 
4487  start = finish + 1;
4489  finish = start + size;
4490  if (track >= start && track < finish)
4491  {
4492  emit SetTrack(kTrackTypeCC608, track - start);
4494  return;
4495  }
4496 
4497  start = finish + 1;
4499  finish = start + size;
4500  if (track >= start && track < finish)
4501  {
4502  emit SetTrack(kTrackTypeTeletextCaptions, track - start);
4504  return;
4505  }
4506 
4507  start = finish + 1;
4509  finish = start + size;
4510  if (track >= start && track < finish)
4511  {
4512  emit SetTrack(kTrackTypeTeletextMenu, track - start);
4514  return;
4515  }
4516 
4517  start = finish + 1;
4519  finish = start + size;
4520  if (track >= start && track < finish)
4521  {
4522  emit SetTrack(kTrackTypeRawText, track - start);
4524  return;
4525  }
4526  }
4527  }
4528  else if (tokens.size() >= 3 && tokens[1] == "VOLUME")
4529  {
4530  QRegularExpression re { "(\\d+)%" };
4531  auto match = re.match(tokens[2]);
4532  if (match.hasMatch())
4533  {
4534  QStringList matches = match.capturedTexts();
4535 
4536  LOG(VB_GENERAL, LOG_INFO, QString("Set Volume to %1%").arg(matches[1]));
4537 
4538  bool ok = false;
4539  int vol = matches[1].toInt(&ok);
4540  if (!ok)
4541  return;
4542 
4543  if (0 <= vol && vol <= 100)
4544  emit ChangeVolume(true, vol);
4545  }
4546  }
4547  else if (tokens.size() >= 3 && tokens[1] == "QUERY")
4548  {
4549  if (tokens[2] == "POSITION")
4550  {
4551  if (!m_player)
4552  return;
4553  QString speedStr;
4554  if (ContextIsPaused(__FILE__, __LINE__))
4555  {
4556  speedStr = "pause";
4557  }
4558  else if (m_playerContext.m_ffRewState)
4559  {
4560  speedStr = QString("%1x").arg(m_playerContext.m_ffRewSpeed);
4561  }
4562  else
4563  {
4564  QRegularExpression re { "Play (.*)x" };
4565  auto match = re.match(m_playerContext.GetPlayMessage());
4566  if (match.hasMatch())
4567  {
4568  QStringList matches = match.capturedTexts();
4569  speedStr = QString("%1x").arg(matches[1]);
4570  }
4571  else
4572  {
4573  speedStr = "1x";
4574  }
4575  }
4576 
4577  osdInfo info;
4578  CalcPlayerSliderPosition(info, true);
4579 
4580  QDateTime respDate = MythDate::current(true);
4581  QString infoStr = "";
4582 
4583  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4584  uint64_t fplay = 0;
4585  double rate = 30.0;
4586  if (m_player)
4587  {
4588  fplay = m_player->GetFramesPlayed();
4589  rate = static_cast<double>(m_player->GetFrameRate()); // for display only
4590  }
4591  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4592 
4593  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
4595  {
4596  infoStr = "LiveTV";
4599  }
4600  else
4601  {
4603  infoStr = "DVD";
4605  infoStr = "Recorded";
4606  else
4607  infoStr = "Video";
4608 
4611  }
4612 
4613  QString bufferFilename =
4614  m_playerContext.m_buffer ? m_playerContext.m_buffer->GetFilename() : QString("no buffer");
4615  if ((infoStr == "Recorded") || (infoStr == "LiveTV"))
4616  {
4617  infoStr += QString(" %1 %2 %3 %4 %5 %6 %7")
4618  .arg(info.text["description"])
4619  .arg(speedStr)
4620  .arg(m_playerContext.m_playingInfo != nullptr ?
4622  .arg(respDate.toString(Qt::ISODate))
4623  .arg(fplay)
4624  .arg(bufferFilename)
4625  .arg(rate);
4626  }
4627  else
4628  {
4629  QString position = info.text["description"].section(" ",0,0);
4630  infoStr += QString(" %1 %2 %3 %4 %5")
4631  .arg(position)
4632  .arg(speedStr)
4633  .arg(bufferFilename)
4634  .arg(fplay)
4635  .arg(rate);
4636  }
4637 
4638  infoStr += QString(" Subtitles:");
4639 
4641 
4642  if (subtype == kDisplayNone)
4643  infoStr += QString(" *0:[None]*");
4644  else
4645  infoStr += QString(" 0:[None]");
4646 
4647  uint n = 1;
4648 
4649  QStringList subs = m_player->GetTracks(kTrackTypeSubtitle);
4650  for (int i = 0; i < subs.size(); i++)
4651  {
4652  if ((subtype & kDisplayAVSubtitle) && (m_player->GetTrack(kTrackTypeSubtitle) == i))
4653  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4654  else
4655  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4656  n++;
4657  }
4658 
4660  for (int i = 0; i < subs.size(); i++)
4661  {
4662  if ((subtype & kDisplayCC708) && (m_player->GetTrack(kTrackTypeCC708) == i))
4663  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4664  else
4665  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4666  n++;
4667  }
4668 
4670  for (int i = 0; i < subs.size(); i++)
4671  {
4672  if ((subtype & kDisplayCC608) && (m_player->GetTrack(kTrackTypeCC608) == i))
4673  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4674  else
4675  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4676  n++;
4677  }
4678 
4680  for (int i = 0; i < subs.size(); i++)
4681  {
4683  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4684  else
4685  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4686  n++;
4687  }
4688 
4690  for (int i = 0; i < subs.size(); i++)
4691  {
4693  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4694  else
4695  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4696  n++;
4697  }
4698 
4700  for (int i = 0; i < subs.size(); i++)
4701  {
4702  if ((subtype & kDisplayRawTextSubtitle) && m_player->GetTrack(kTrackTypeRawText) == i)
4703  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4704  else
4705  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4706  n++;
4707  }
4708 
4709  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
4710 
4711  QString message = QString("NETWORK_CONTROL ANSWER %1").arg(infoStr);
4712  MythEvent me(message);
4713  gCoreContext->dispatch(me);
4714  }
4715  else if (tokens[2] == "VOLUME")
4716  {
4717  QString infoStr = QString("%1%").arg(m_audioState.m_volume);
4718  QString message = QString("NETWORK_CONTROL ANSWER %1").arg(infoStr);
4719  MythEvent me(message);
4720  gCoreContext->dispatch(me);
4721  }
4722  }
4723 }
4724 
4725 bool TV::StartPlayer(TVState desiredState)
4726 {
4727  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1) -- begin").arg(StateToString(desiredState)));
4728  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Elapsed time since TV constructor was called: %1 ms")
4729  .arg(m_ctorTime.elapsed()));
4730 
4731  bool ok = CreatePlayer(desiredState);
4733 
4734  if (ok)
4735  {
4736  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created player."));
4737  SetSpeedChangeTimer(25, __LINE__);
4738  }
4739  else
4740  {
4741  LOG(VB_GENERAL, LOG_CRIT, LOC + QString("Failed to create player."));
4742  }
4743 
4744  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1) -- end %2")
4745  .arg(StateToString(desiredState)).arg((ok) ? "ok" : "error"));
4746 
4747  return ok;
4748 }
4749 
4751 {
4752  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4753  if (!m_player)
4754  {
4755  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4756  return;
4757  }
4758 
4759  float time = 0.0;
4760 
4762  m_player->IsPaused())
4763  {
4765  time = StopFFRew();
4766  else if (m_player->IsPaused())
4768 
4772  }
4773  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4774 
4775  DoPlayerSeek(time);
4777 
4779 
4780  SetSpeedChangeTimer(0, __LINE__);
4782 }
4783 
4785 {
4786 
4788  return 0.0F;
4789 
4791  float time = 0.0F;
4792 
4793  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4794  if (!m_player)
4795  {
4796  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4797  return 0.0F;
4798  }
4799  if (m_player->IsPaused())
4800  {
4802  }
4803  else
4804  {
4806  time = StopFFRew();
4807  m_player->Pause();
4808  }
4809  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4810  return time;
4811 }
4812 
4813 void TV::DoTogglePauseFinish(float Time, bool ShowOSD)
4814 {
4815  if (!m_playerContext.HasPlayer())
4816  return;
4817 
4819  return;
4820 
4821  if (ContextIsPaused(__FILE__, __LINE__))
4822  {
4825 
4826  DoPlayerSeek(Time);
4827  if (ShowOSD)
4828  UpdateOSDSeekMessage(tr("Paused"), kOSDTimeout_None);
4830  }
4831  else
4832  {
4833  DoPlayerSeek(Time);
4834  if (ShowOSD)
4837  }
4838 
4839  SetSpeedChangeTimer(0, __LINE__);
4840 }
4841 
4849 {
4850  bool paused = false;
4851  int dummy = 0;
4852  TV* tv = AcquireRelease(dummy, true);
4853  if (tv)
4854  {
4855  tv->GetPlayerReadLock();
4856  PlayerContext* context = tv->GetPlayerContext();
4857  if (!context->IsErrored())
4858  {
4859  context->LockDeletePlayer(__FILE__, __LINE__);
4860  if (context->m_player)
4861  paused = context->m_player->IsPaused();
4862  context->UnlockDeletePlayer(__FILE__, __LINE__);
4863  }
4864  tv->ReturnPlayerLock();
4865  AcquireRelease(dummy, false);
4866  }
4867  return paused;
4868 }
4869 
4870 void TV::DoTogglePause(bool ShowOSD)
4871 {
4872  bool ignore = false;
4873  bool paused = false;
4874  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4875  if (m_player)
4876  {
4877  ignore = m_player->GetEditMode();
4878  paused = m_player->IsPaused();
4879  }
4880  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4881 
4882  if (paused)
4884  else
4886 
4887  if (!ignore)
4889  // Emit Pause or Unpaused signal
4891 }
4892 
4893 bool TV::DoPlayerSeek(float Time)
4894 {
4896  return false;
4897 
4898  if (Time > -0.001F && Time < +0.001F)
4899  return false;
4900 
4901  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1 seconds").arg(static_cast<double>(Time)));
4902 
4903  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4904  if (!m_player)
4905  {
4906  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4907  return false;
4908  }
4909 
4911  {
4912  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4913  return false;
4914  }
4915 
4916  emit PauseAudioUntilReady();
4917 
4918  bool res = false;
4919 
4920  if (Time > 0.0F)
4921  res = m_player->FastForward(Time);
4922  else if (Time < 0.0F)
4923  res = m_player->Rewind(-Time);
4924  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4925 
4926  return res;
4927 }
4928 
4929 bool TV::DoPlayerSeekToFrame(uint64_t FrameNum)
4930 {
4932  return false;
4933 
4934  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1").arg(FrameNum));
4935 
4936  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4937  if (!m_player)
4938  {
4939  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4940  return false;
4941  }
4942 
4944  {
4945  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4946  return false;
4947  }
4948 
4949  emit PauseAudioUntilReady();
4950 
4951  bool res = m_player->JumpToFrame(FrameNum);
4952 
4953  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4954 
4955  return res;
4956 }
4957 
4958 bool TV::SeekHandleAction(const QStringList& Actions, const bool IsDVD)
4959 {
4960  const int kRewind = 4;
4961  const int kForward = 8;
4962  const int kSticky = 16;
4963  const int kSlippery = 32;
4964  const int kRelative = 64;
4965  const int kAbsolute = 128;
4966  const int kIgnoreCutlist = 256;
4967  const int kWhenceMask = 3;
4968  int flags = 0;
4969  if (IsActionable(ACTION_SEEKFFWD, Actions))
4970  flags = ARBSEEK_FORWARD | kForward | kSlippery | kRelative;
4971  else if (IsActionable("FFWDSTICKY", Actions))
4972  flags = ARBSEEK_END | kForward | kSticky | kAbsolute;
4973  else if (IsActionable(ACTION_RIGHT, Actions))
4974  flags = ARBSEEK_FORWARD | kForward | kSticky | kRelative;
4975  else if (IsActionable(ACTION_SEEKRWND, Actions))
4976  flags = ARBSEEK_REWIND | kRewind | kSlippery | kRelative;
4977  else if (IsActionable("RWNDSTICKY", Actions))
4978  flags = ARBSEEK_SET | kRewind | kSticky | kAbsolute;
4979  else if (IsActionable(ACTION_LEFT, Actions))
4980  flags = ARBSEEK_REWIND | kRewind | kSticky | kRelative;
4981  else
4982  return false;
4983 
4984  int direction = (flags & kRewind) ? -1 : 1;
4985  if (HasQueuedInput())
4986  {
4987  DoArbSeek(static_cast<ArbSeekWhence>(flags & kWhenceMask), (flags & kIgnoreCutlist) == 0);
4988  }
4989  else if (ContextIsPaused(__FILE__, __LINE__))
4990  {
4991  if (!IsDVD)
4992  {
4993  QString message = (flags & kRewind) ? tr("Rewind") :
4994  tr("Forward");
4995  if (flags & kAbsolute) // FFWDSTICKY/RWNDSTICKY
4996  {
4997  float time = direction;
4998  DoSeek(time, message, /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
4999  }
5000  else
5001  {
5002  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5003  uint64_t frameAbs = m_player->GetFramesPlayed();
5004  uint64_t frameRel = m_player->TranslatePositionAbsToRel(frameAbs);
5005  uint64_t targetRel = frameRel + static_cast<uint64_t>(direction);
5006  if (frameRel == 0 && direction < 0)
5007  targetRel = 0;
5008  uint64_t maxAbs = m_player->GetCurrentFrameCount();
5009  uint64_t maxRel = m_player->TranslatePositionAbsToRel(maxAbs);
5010  if (targetRel > maxRel)
5011  targetRel = maxRel;
5012  uint64_t targetAbs = m_player->TranslatePositionRelToAbs(targetRel);
5013  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5014  DoPlayerSeekToFrame(targetAbs);
5016  }
5017  }
5018  }
5019  else if (flags & kSticky)
5020  {
5021  ChangeFFRew(direction);
5022  }
5023  else if (flags & kRewind)
5024  {
5025  if (m_smartForward)
5026  m_doSmartForward = true;
5027  DoSeek(-m_playerContext.m_rewtime, tr("Skip Back"), /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
5028  }
5029  else
5030  {
5032  {
5033  DoSeek(m_playerContext.m_rewtime, tr("Skip Ahead"), /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
5034  }
5035  else
5036  {
5037  DoSeek(m_playerContext.m_fftime, tr("Skip Ahead"), /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
5038  }
5039  }
5040  return true;
5041 }
5042 
5043 void TV::DoSeek(float Time, const QString &Msg, bool TimeIsOffset, bool HonorCutlist)
5044 {
5045  if (!m_player)
5046  return;
5047 
5048  bool limitkeys = false;
5049 
5050  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5051  if (m_player->GetLimitKeyRepeat())
5052  limitkeys = true;
5053 
5054  if (!limitkeys || (m_keyRepeatTimer.elapsed() > static_cast<int>(kKeyRepeatTimeout)))
5055  {
5057  NormalSpeed();
5058  Time += StopFFRew();
5059  if (TimeIsOffset)
5060  {
5061  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5062  DoPlayerSeek(Time);
5063  }
5064  else
5065  {
5066  auto time = static_cast<uint64_t>(Time);
5067  uint64_t desiredFrameRel = m_player->TranslatePositionMsToFrame(time * 1000, HonorCutlist);
5068  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5069  DoPlayerSeekToFrame(desiredFrameRel);
5070  }
5071  bool paused = m_player->IsPaused();
5073  }
5074  else
5075  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5076 }
5077 
5078 void TV::DoSeekAbsolute(long long Seconds, bool HonorCutlist)
5079 {
5080  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5081  if (!m_player)
5082  {
5083  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5085  return;
5086  }
5087  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5088  DoSeek(Seconds, tr("Jump To"), /*timeIsOffset*/false, HonorCutlist);
5090 }
5091 
5092 void TV::DoArbSeek(ArbSeekWhence Whence, bool HonorCutlist)
5093 {
5094  bool ok = false;
5095  int seek = GetQueuedInputAsInt(&ok);
5096  ClearInputQueues(true);
5097  if (!ok)
5098  return;
5099 
5100  int64_t time = (int(seek / 100) * 3600) + ((seek % 100) * 60);
5101 
5102  if (Whence == ARBSEEK_FORWARD)
5103  {
5104  DoSeek(time, tr("Jump Ahead"), /*timeIsOffset*/true, HonorCutlist);
5105  }
5106  else if (Whence == ARBSEEK_REWIND)
5107  {
5108  DoSeek(-time, tr("Jump Back"), /*timeIsOffset*/true, HonorCutlist);
5109  }
5110  else if (Whence == ARBSEEK_END)
5111  {
5112  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5113  if (!m_player)
5114  {
5115  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5116  return;
5117  }
5118  uint64_t total_frames = m_player->GetCurrentFrameCount();
5119  float dur = m_player->ComputeSecs(total_frames, HonorCutlist);
5120  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5121  DoSeek(std::max(0.0F, dur - static_cast<float>(time)), tr("Jump To"), /*timeIsOffset*/false, HonorCutlist);
5122  }
5123  else
5124  {
5125  DoSeekAbsolute(time, HonorCutlist);
5126  }
5127 }
5128 
5130 {
5132  return;
5133 
5135 
5136  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5137  if (m_player)
5139  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5140 
5141  SetSpeedChangeTimer(0, __LINE__);
5142 }
5143 
5144 void TV::ChangeSpeed(int Direction)
5145 {
5146  int old_speed = m_playerContext.m_ffRewSpeed;
5147 
5148  if (ContextIsPaused(__FILE__, __LINE__))
5150 
5151  m_playerContext.m_ffRewSpeed += Direction;
5152 
5153  float time = StopFFRew();
5154  float speed = NAN;
5155  QString mesg;
5156 
5157  switch (m_playerContext.m_ffRewSpeed)
5158  {
5159  case 4: speed = 16.0F; mesg = tr("Speed 16X"); break;
5160  case 3: speed = 8.0F; mesg = tr("Speed 8X"); break;
5161  case 2: speed = 3.0F; mesg = tr("Speed 3X"); break;
5162  case 1: speed = 2.0F; mesg = tr("Speed 2X"); break;
5163  case 0: speed = 1.0F; mesg = m_playerContext.GetPlayMessage(); break;
5164  case -1: speed = 1.0F / 3; mesg = tr("Speed 1/3X"); break;
5165  case -2: speed = 1.0F / 8; mesg = tr("Speed 1/8X"); break;
5166  case -3: speed = 1.0F / 16; mesg = tr("Speed 1/16X"); break;
5167  case -4:
5168  DoTogglePause(true);
5169  return;
5170  default:
5171  m_playerContext.m_ffRewSpeed = old_speed;
5172  return;
5173  }
5174