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