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  QMap<QString,AskProgramInfo>::iterator next = it;
1639  while (it != m_askAllowPrograms.end())
1640  {
1641  next = it; ++next;
1642  if ((*it).m_expiry <= timeNow)
1643  {
1644 #if 0
1645  LOG(VB_GENERAL, LOG_DEBUG, LOC + "-- " +
1646  QString("removing '%1'").arg((*it).m_info->m_title));
1647 #endif
1648  delete (*it).m_info;
1649  m_askAllowPrograms.erase(it);
1650  }
1651  it = next;
1652  }
1653  std::chrono::milliseconds timeuntil = 0ms;
1654  QString message;
1655  uint conflict_count = static_cast<uint>(m_askAllowPrograms.size());
1656 
1657  it = m_askAllowPrograms.begin();
1658  if ((1 == m_askAllowPrograms.size()) && ((*it).m_info->GetInputID() == cardid))
1659  {
1660  (*it).m_isInSameInputGroup = (*it).m_isConflicting = true;
1661  }
1662  else if (!m_askAllowPrograms.empty())
1663  {
1664  // get the currently used input on our card
1665  bool busy_input_grps_loaded = false;
1666  vector<uint> busy_input_grps;
1667  InputInfo busy_input;
1668  RemoteIsBusy(cardid, busy_input);
1669 
1670  // check if current input can conflict
1671  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1672  {
1673  (*it).m_isInSameInputGroup =
1674  (cardid == (*it).m_info->GetInputID());
1675 
1676  if ((*it).m_isInSameInputGroup)
1677  continue;
1678 
1679  // is busy_input in same input group as recording
1680  if (!busy_input_grps_loaded)
1681  {
1682  busy_input_grps = CardUtil::GetInputGroups(busy_input.m_inputId);
1683  busy_input_grps_loaded = true;
1684  }
1685 
1686  vector<uint> input_grps =
1687  CardUtil::GetInputGroups((*it).m_info->GetInputID());
1688 
1689  for (uint grp : input_grps)
1690  {
1691  if (find(busy_input_grps.begin(), busy_input_grps.end(),
1692  grp) != busy_input_grps.end())
1693  {
1694  (*it).m_isInSameInputGroup = true;
1695  break;
1696  }
1697  }
1698  }
1699 
1700  // check if inputs that can conflict are ok
1701  conflict_count = 0;
1702  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1703  {
1704  if (!(*it).m_isInSameInputGroup)
1705  (*it).m_isConflicting = false; // NOLINT(bugprone-branch-clone)
1706  else if (cardid == (*it).m_info->GetInputID())
1707  (*it).m_isConflicting = true; // NOLINT(bugprone-branch-clone)
1708  else if (!CardUtil::IsTunerShared(cardid, (*it).m_info->GetInputID()))
1709  (*it).m_isConflicting = true;
1710  else if ((busy_input.m_mplexId &&
1711  (busy_input.m_mplexId == (*it).m_info->QueryMplexID())) ||
1712  (!busy_input.m_mplexId &&
1713  (busy_input.m_chanId == (*it).m_info->GetChanID())))
1714  (*it).m_isConflicting = false;
1715  else
1716  (*it).m_isConflicting = true;
1717 
1718  conflict_count += (*it).m_isConflicting ? 1 : 0;
1719  }
1720  }
1721 
1722  it = m_askAllowPrograms.begin();
1723  for (; it != m_askAllowPrograms.end() && !(*it).m_isConflicting; ++it);
1724 
1725  if (conflict_count == 0)
1726  {
1727  LOG(VB_GENERAL, LOG_INFO, LOC + "The scheduler wants to make "
1728  "a non-conflicting recording.");
1729  // TODO take down mplexid and inform user of problem
1730  // on channel changes.
1731  }
1732  else if (conflict_count == 1 && ((*it).m_info->GetInputID() == cardid))
1733  {
1734 #if 0
1735  LOG(VB_GENERAL, LOG_DEBUG, LOC + "UpdateOSDAskAllowDialog -- " +
1736  "kAskAllowOneRec");
1737 #endif
1738 
1739  it = m_askAllowPrograms.begin();
1740 
1741  QString channel = m_dbChannelFormat;
1742  channel
1743  .replace("<num>", (*it).m_info->GetChanNum())
1744  .replace("<sign>", (*it).m_info->GetChannelSchedulingID())
1745  .replace("<name>", (*it).m_info->GetChannelName());
1746 
1747  message = single_rec.arg((*it).m_info->GetTitle()).arg(channel);
1748 
1749  BrowseEnd(false);
1750  timeuntil = MythDate::secsInFuture((*it).m_expiry);
1751  MythOSDDialogData dialog { OSD_DLG_ASKALLOW, message, timeuntil };
1752  dialog.m_buttons.push_back({ record_watch, "DIALOG_ASKALLOW_WATCH_0", false, !((*it).m_hasRec)} );
1753  dialog.m_buttons.push_back({ let_record1, "DIALOG_ASKALLOW_EXIT_0" });
1754  dialog.m_buttons.push_back({ ((*it).m_hasLater) ? record_later1 : do_not_record1,
1755  "DIALOG_ASKALLOW_CANCELRECORDING_0", false, ((*it).m_hasRec) });
1756  emit ChangeOSDDialog(dialog);
1757  }
1758  else
1759  {
1760  if (conflict_count > 1)
1761  {
1762  message = tr(
1763  "MythTV wants to record these programs in %d seconds:");
1764  message += "\n";
1765  }
1766 
1767  bool has_rec = false;
1768  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1769  {
1770  if (!(*it).m_isConflicting)
1771  continue;
1772 
1773  QString title = (*it).m_info->GetTitle();
1774  if ((title.length() < 10) && !(*it).m_info->GetSubtitle().isEmpty())
1775  title += ": " + (*it).m_info->GetSubtitle();
1776  if (title.length() > 20)
1777  title = title.left(17) + "...";
1778 
1779  QString channel = m_dbChannelFormat;
1780  channel
1781  .replace("<num>", (*it).m_info->GetChanNum())
1782  .replace("<sign>", (*it).m_info->GetChannelSchedulingID())
1783  .replace("<name>", (*it).m_info->GetChannelName());
1784 
1785  if (conflict_count > 1)
1786  {
1787  message += tr("\"%1\" on %2").arg(title).arg(channel);
1788  message += "\n";
1789  }
1790  else
1791  {
1792  message = single_rec.arg((*it).m_info->GetTitle()).arg(channel);
1793  has_rec = (*it).m_hasRec;
1794  }
1795  }
1796 
1797  if (conflict_count > 1)
1798  {
1799  message += "\n";
1800  message += tr("Do you want to:");
1801  }
1802 
1803  bool all_have_later = true;
1804  timeuntil = 9999999ms;
1805  for (it = m_askAllowPrograms.begin(); it != m_askAllowPrograms.end(); ++it)
1806  {
1807  if ((*it).m_isConflicting)
1808  {
1809  all_have_later &= (*it).m_hasLater;
1810  auto tmp = std::chrono::milliseconds(MythDate::secsInFuture((*it).m_expiry));
1811  timeuntil = std::clamp(tmp, 0ms, timeuntil);
1812  }
1813  }
1814  timeuntil = (9999999ms == timeuntil) ? 0ms : timeuntil;
1815 
1816  if (conflict_count > 1)
1817  {
1818  BrowseEnd(false);
1819  emit ChangeOSDDialog( { OSD_DLG_ASKALLOW, message, timeuntil, {
1820  { let_recordm, "DIALOG_ASKALLOW_EXIT_0", false, true },
1821  { all_have_later ? record_laterm : do_not_recordm, "DIALOG_ASKALLOW_CANCELCONFLICTING_0" }
1822  }});
1823  }
1824  else
1825  {
1826  BrowseEnd(false);
1827  emit ChangeOSDDialog( {OSD_DLG_ASKALLOW, message, timeuntil, {
1828  { let_record1, "DIALOG_ASKALLOW_EXIT_0", false, !has_rec},
1829  { all_have_later ? record_later1 : do_not_record1, "DIALOG_ASKALLOW_CANCELRECORDING_0", false, has_rec}
1830  }});
1831  }
1832  }
1833 }
1834 
1835 void TV::HandleOSDAskAllow(const QString& Action)
1836 {
1838  return;
1839 
1840  if (!m_askAllowLock.tryLock())
1841  {
1842  LOG(VB_GENERAL, LOG_ERR, "allowrecordingbox : askAllowLock is locked");
1843  return;
1844  }
1845 
1846  if (Action == "CANCELRECORDING")
1847  {
1850  }
1851  else if (Action == "CANCELCONFLICTING")
1852  {
1853  for (const auto& pgm : qAsConst(m_askAllowPrograms))
1854  {
1855  if (pgm.m_isConflicting)
1856  RemoteCancelNextRecording(pgm.m_info->GetInputID(), true);
1857  }
1858  }
1859  else if (Action == "WATCH")
1860  {
1863  }
1864  else // if (action == "EXIT")
1865  {
1866  PrepareToExitPlayer(__LINE__);
1867  SetExitPlayer(true, true);
1868  }
1869 
1870  m_askAllowLock.unlock();
1871 }
1872 
1874 {
1875  m_wantsToQuit = false;
1876  m_jumpToProgram = false;
1877  m_allowRerecord = false;
1878  m_requestDelete = false;
1880 
1883  {
1884  ReturnPlayerLock();
1885  return 0;
1886  }
1887 
1891 
1892  ReturnPlayerLock();
1893 
1894  if (LCD *lcd = LCD::Get())
1895  {
1896  lcd->switchToChannel(ProgInfo.GetChannelSchedulingID(), ProgInfo.GetTitle(), ProgInfo.GetSubtitle());
1897  lcd->setFunctionLEDs((ProgInfo.IsRecording())?FUNC_TV:FUNC_MOVIE, true);
1898  }
1899 
1900  return 1;
1901 }
1902 
1904 {
1906 }
1907 
1909 {
1910  return (State == kState_WatchingPreRecorded ||
1915 }
1916 
1918 {
1919  return (State == kState_WatchingLiveTV);
1920 }
1921 
1922 #define TRANSITION(ASTATE,BSTATE) ((ctxState == (ASTATE)) && (desiredNextState == (BSTATE)))
1923 
1924 #define SET_NEXT() do { nextState = desiredNextState; changed = true; } while(false)
1925 #define SET_LAST() do { nextState = ctxState; changed = true; } while(false)
1926 
1927 static QString tv_i18n(const QString &msg)
1928 {
1929  QByteArray msg_arr = msg.toLatin1();
1930  QString msg_i18n = TV::tr(msg_arr.constData());
1931  QByteArray msg_i18n_arr = msg_i18n.toLatin1();
1932  return (msg_arr == msg_i18n_arr) ? msg_i18n : msg;
1933 }
1934 
1944 {
1945  if (m_playerContext.IsErrored())
1946  {
1947  LOG(VB_GENERAL, LOG_ERR, LOC + "Called after fatal error detected.");
1948  return;
1949  }
1950 
1951  bool changed = false;
1952 
1954  TVState nextState = m_playerContext.GetState();
1955  if (m_playerContext.m_nextState.empty())
1956  {
1957  LOG(VB_GENERAL, LOG_WARNING, LOC + "Warning, called with no state to change to.");
1959  return;
1960  }
1961 
1962  TVState ctxState = m_playerContext.GetState();
1963  TVState desiredNextState = m_playerContext.DequeueNextState();
1964 
1965  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Attempting to change from %1 to %2")
1966  .arg(StateToString(nextState)).arg(StateToString(desiredNextState)));
1967 
1968  if (desiredNextState == kState_Error)
1969  {
1970  LOG(VB_GENERAL, LOG_ERR, LOC + "Attempting to set to an error state!");
1971  SetErrored();
1973  return;
1974  }
1975 
1976  bool ok = false;
1978  {
1980 
1982 
1983  QDateTime timerOffTime = MythDate::current();
1984  m_lockTimerOn = false;
1985 
1986  SET_NEXT();
1987 
1988  uint chanid = m_initialChanID;
1989  if (!chanid)
1990  chanid = static_cast<uint>(gCoreContext->GetNumSetting("DefaultChanid", 0));
1991 
1992  if (chanid && !IsTunablePriv(chanid))
1993  chanid = 0;
1994 
1995  QString channum = "";
1996 
1997  if (chanid)
1998  {
1999  QStringList reclist;
2000 
2001  MSqlQuery query(MSqlQuery::InitCon());
2002  query.prepare("SELECT channum FROM channel "
2003  "WHERE chanid = :CHANID");
2004  query.bindValue(":CHANID", chanid);
2005  if (query.exec() && query.isActive() && query.size() > 0 && query.next())
2006  channum = query.value(0).toString();
2007  else
2008  channum = QString::number(chanid);
2009 
2011  QString::number(chanid));
2012 
2013  if (getit)
2014  reclist = ChannelUtil::GetValidRecorderList(chanid, channum);
2015 
2016  if (!reclist.empty())
2017  {
2018  RemoteEncoder *testrec = RemoteRequestFreeRecorderFromList(reclist, 0);
2019  if (testrec && testrec->IsValidRecorder())
2020  {
2021  m_playerContext.SetRecorder(testrec);
2023  }
2024  else
2025  delete testrec; // If testrec isn't a valid recorder ...
2026  }
2027  else if (getit)
2028  chanid = 0;
2029  }
2030 
2031  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- begin");
2032 
2033  if (chanid && !channum.isEmpty())
2035  else
2037 
2038  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Spawning LiveTV Recorder -- end");
2039 
2041  {
2042  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
2044  m_playerContext.SetRecorder(nullptr);
2045  SetErrored();
2046  SET_LAST();
2047  }
2048  else
2049  {
2050  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2051  QString playbackURL = m_playerContext.m_playingInfo->GetPlaybackURL(true);
2052  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2053 
2054  bool opennow = (m_playerContext.m_tvchain->GetInputType(-1) != "DUMMY");
2055 
2056  LOG(VB_GENERAL, LOG_INFO, LOC +
2057  QString("playbackURL(%1) inputtype(%2)")
2058  .arg(playbackURL).arg(m_playerContext.m_tvchain->GetInputType(-1)));
2059 
2062  playbackURL, false, true,
2063  opennow ? MythMediaBuffer::kLiveTVOpenTimeout : -1ms));
2064 
2067  }
2068 
2069 
2071  {
2072  ok = StartPlayer(desiredNextState);
2073  }
2074  if (!ok)
2075  {
2076  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
2078  m_playerContext.SetRecorder(nullptr);
2079  SetErrored();
2080  SET_LAST();
2081  }
2082  else
2083  {
2084  if (!m_lastLockSeenTime.isValid() ||
2085  (m_lastLockSeenTime < timerOffTime))
2086  {
2087  m_lockTimer.start();
2088  m_lockTimerOn = true;
2089  }
2090  }
2091  }
2093  {
2094  SET_NEXT();
2096  StopStuff(true, true, true);
2097  }
2103  {
2104  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2105  QString playbackURL = m_playerContext.m_playingInfo->GetPlaybackURL(true);
2106  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2107 
2108  MythMediaBuffer *buffer = MythMediaBuffer::Create(playbackURL, false);
2109  if (buffer && !buffer->GetLastError().isEmpty())
2110  {
2111  ShowNotificationError(tr("Can't start playback"),
2112  TV::tr( "TV Player" ), buffer->GetLastError());
2113  delete buffer;
2114  buffer = nullptr;
2115  }
2117 
2119  {
2120  if (desiredNextState == kState_WatchingRecording)
2121  {
2122  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2124  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2125 
2127 
2129  {
2130  LOG(VB_GENERAL, LOG_ERR, LOC +
2131  "Couldn't find recorder for in-progress recording");
2132  desiredNextState = kState_WatchingPreRecorded;
2133  m_playerContext.SetRecorder(nullptr);
2134  }
2135  else
2136  {
2138  }
2139  }
2140 
2141  ok = StartPlayer(desiredNextState);
2142 
2143  if (ok)
2144  {
2145  SET_NEXT();
2146 
2147  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2149  {
2150  QString message = "COMMFLAG_REQUEST ";
2152  gCoreContext->SendMessage(message);
2153  }
2154  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2155  }
2156  }
2157 
2158  if (!ok)
2159  {
2160  SET_LAST();
2161  SetErrored();
2163  {
2165  TV::tr( "TV Player" ),
2166  playbackURL);
2167  // We're going to display this error as notification
2168  // no need to display it later as popup
2170  }
2171  }
2172  }
2178  {
2179  SET_NEXT();
2181  StopStuff(true, true, false);
2182  }
2185  {
2186  SET_NEXT();
2187  }
2188 
2189  // Print state changed message...
2190  if (!changed)
2191  {
2192  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unknown state transition: %1 to %2")
2193  .arg(StateToString(m_playerContext.GetState())).arg(StateToString(desiredNextState)));
2194  }
2195  else if (m_playerContext.GetState() != nextState)
2196  {
2197  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Changing from %1 to %2")
2198  .arg(StateToString(m_playerContext.GetState())).arg(StateToString(nextState)));
2199  }
2200 
2201  // update internal state variable
2202  TVState lastState = m_playerContext.GetState();
2203  m_playerContext.m_playingState = nextState;
2205 
2207  {
2208  LOG(VB_GENERAL, LOG_INFO, LOC + "State is LiveTV");
2209  UpdateOSDInput();
2210  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateOSDInput done");
2211  UpdateLCD();
2212  LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateLCD done");
2213  ITVRestart(true);
2214  LOG(VB_GENERAL, LOG_INFO, LOC + "ITVRestart done");
2215  }
2216  else if (StateIsPlaying(m_playerContext.GetState()) && lastState == kState_None)
2217  {
2218  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
2219  int count = PlayGroup::GetCount();
2220  QString msg = tr("%1 Settings")
2222  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
2223  if (count > 0)
2224  emit ChangeOSDMessage(msg);
2225  ITVRestart(false);
2226  }
2227 
2229  {
2230  UpdateLCD();
2231  }
2232 
2235 
2241 
2245 
2247  {
2249  }
2250 
2257  {
2259  // m_playerBounds is not applicable when switching modes so
2260  // skip this logic in that case.
2261  if (!m_dbUseVideoModes)
2263 
2264  if (!m_weDisabledGUI)
2265  {
2266  m_weDisabledGUI = true;
2268  }
2269  // we no longer need the contents of myWindow
2270  if (m_myWindow)
2272 
2273  LOG(VB_GENERAL, LOG_INFO, LOC + "Main UI disabled.");
2274  }
2275 
2276  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + " -- end");
2277 }
2278 
2279 #undef TRANSITION
2280 #undef SET_NEXT
2281 #undef SET_LAST
2282 
2288 bool TV::StartRecorder(std::chrono::milliseconds MaxWait)
2289 {
2291  MaxWait = (MaxWait <= 0ms) ? 40s : MaxWait;
2292  MythTimer t;
2293  t.start();
2294  bool recording = false;
2295  bool ok = true;
2296  if (!rec)
2297  {
2298  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid Remote Encoder");
2299  SetErrored();
2300  return false;
2301  }
2302  while (!(recording = rec->IsRecording(&ok)) && !m_exitPlayerTimerId && t.elapsed() < MaxWait)
2303  {
2304  if (!ok)
2305  {
2306  LOG(VB_GENERAL, LOG_ERR, LOC + "Lost contact with backend");
2307  SetErrored();
2308  return false;
2309  }
2310  std::this_thread::sleep_for(5us);
2311  }
2312 
2313  if (!recording || m_exitPlayerTimerId)
2314  {
2315  if (!m_exitPlayerTimerId)
2316  LOG(VB_GENERAL, LOG_ERR, LOC + "Timed out waiting for recorder to start");
2317  return false;
2318  }
2319 
2320  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Took %1 ms to start recorder.")
2321  .arg(t.elapsed().count()));
2322  return true;
2323 }
2324 
2338 void TV::StopStuff(bool StopRingBuffer, bool StopPlayer, bool StopRecorder)
2339 {
2340  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- begin");
2341 
2342  emit PlaybackExiting(this);
2343 
2346 
2347  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
2348  if (StopPlayer)
2350  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
2351 
2352  if (StopRingBuffer)
2353  {
2354  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopping ring buffer");
2356  {
2360  }
2361  }
2362 
2363  if (StopRecorder)
2364  {
2365  LOG(VB_PLAYBACK, LOG_INFO, LOC + "stopping recorder");
2368  }
2369 
2370  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
2371 }
2372 
2373 void TV::timerEvent(QTimerEvent *Event)
2374 {
2375  const int timer_id = Event->timerId();
2376 
2378  bool errored = m_playerContext.IsErrored();
2379  ReturnPlayerLock();
2380  if (errored)
2381  return;
2382 
2383  bool handled = true;
2384  if (timer_id == m_lcdTimerId)
2386  else if (timer_id == m_lcdVolumeTimerId)
2388  else if (timer_id == m_sleepTimerId)
2389  ShowOSDSleep();
2390  else if (timer_id == m_sleepDialogTimerId)
2392  else if (timer_id == m_idleTimerId)
2393  ShowOSDIdle();
2394  else if (timer_id == m_idleDialogTimerId)
2396  else if (timer_id == m_endOfPlaybackTimerId)
2398  else if (timer_id == m_endOfRecPromptTimerId)
2400  else if (timer_id == m_videoExitDialogTimerId)
2402  else if (timer_id == m_pseudoChangeChanTimerId)
2404  else if (timer_id == m_speedChangeTimerId)
2406  else if (timer_id == m_saveLastPlayPosTimerId)
2408  else
2409  handled = false;
2410 
2411  if (handled)
2412  return;
2413 
2414  // Check if it matches a signalMonitorTimerId
2415  if (timer_id == m_signalMonitorTimerId)
2416  {
2420  if (!m_playerContext.m_lastSignalMsg.empty())
2421  {
2422  // set last signal msg, so we get some feedback...
2425  }
2427  ReturnPlayerLock();
2428  return;
2429  }
2430 
2431  // Check if it matches networkControlTimerId
2432  QString netCmd;
2433  if (timer_id == m_networkControlTimerId)
2434  {
2435  if (!m_networkControlCommands.empty())
2436  netCmd = m_networkControlCommands.dequeue();
2437  if (m_networkControlCommands.empty())
2438  {
2441  }
2442  }
2443 
2444  if (!netCmd.isEmpty())
2445  {
2448  ReturnPlayerLock();
2449  handled = true;
2450  }
2451 
2452  if (handled)
2453  return;
2454 
2455  // Check if it matches exitPlayerTimerId
2456  if (timer_id == m_exitPlayerTimerId)
2457  {
2459  emit DialogQuit();
2460  emit HideAll();
2461 
2463  {
2464  if (!m_lastProgram->IsFileReadable())
2465  {
2466  emit ChangeOSDMessage(tr("Last Program: %1 Doesn't Exist").arg(m_lastProgram->GetTitle()));
2467  lastProgramStringList.clear();
2468  SetLastProgram(nullptr);
2469  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Last Program File does not exist");
2470  m_jumpToProgram = false;
2471  }
2472  else
2473  {
2475  }
2476  }
2477  else
2479 
2480  ReturnPlayerLock();
2481 
2483  m_exitPlayerTimerId = 0;
2484  handled = true;
2485  }
2486 
2487  if (handled)
2488  return;
2489 
2490  if (timer_id == m_ccInputTimerId)
2491  {
2493  // Clear closed caption input mode when timer expires
2494  if (m_ccInputMode)
2495  {
2496  m_ccInputMode = false;
2497  ClearInputQueues(true);
2498  }
2499  ReturnPlayerLock();
2500 
2502  m_ccInputTimerId = 0;
2503  handled = true;
2504  }
2505 
2506  if (handled)
2507  return;
2508 
2509  if (timer_id == m_asInputTimerId)
2510  {
2512  // Clear closed caption input mode when timer expires
2513  if (m_asInputMode)
2514  {
2515  m_asInputMode = false;
2516  ClearInputQueues(true);
2517  }
2518  ReturnPlayerLock();
2519 
2521  m_asInputTimerId = 0;
2522  handled = true;
2523  }
2524 
2525  if (handled)
2526  return;
2527 
2528  if (timer_id == m_queueInputTimerId)
2529  {
2531  // Commit input when the OSD fades away
2532  if (HasQueuedChannel())
2533  {
2534  OSD *osd = GetOSDL();
2535  if (osd && !osd->IsWindowVisible(OSD_WIN_INPUT))
2536  {
2537  ReturnOSDLock();
2539  }
2540  else
2541  {
2542  ReturnOSDLock();
2543  }
2544  }
2545  ReturnPlayerLock();
2546 
2547  if (!m_queuedChanID && m_queuedChanNum.isEmpty() && m_queueInputTimerId)
2548  {
2550  m_queueInputTimerId = 0;
2551  }
2552  handled = true;
2553  }
2554 
2555  if (handled)
2556  return;
2557 
2558  if (timer_id == m_browseTimerId)
2559  {
2561  BrowseEnd(false);
2562  ReturnPlayerLock();
2563  handled = true;
2564  }
2565 
2566  if (handled)
2567  return;
2568 
2569  if (timer_id == m_errorRecoveryTimerId)
2570  {
2574  {
2575  SetExitPlayer(true, false);
2577  }
2578  ReturnPlayerLock();
2579 
2583  return;
2584  }
2585 
2586  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Unknown timer: %1").arg(timer_id));
2587 }
2588 
2590 {
2592  LCD *lcd = LCD::Get();
2593  if (lcd)
2594  {
2595  float progress = 0.0F;
2596  QString lcd_time_string;
2597  bool showProgress = true;
2598 
2599  if (StateIsLiveTV(GetState()))
2601 
2603  {
2604  ShowLCDDVDInfo();
2606  }
2607 
2608  if (showProgress)
2609  {
2610  osdInfo info;
2611  if (CalcPlayerSliderPosition(info)) {
2612  progress = info.values["position"] * 0.001F;
2613 
2614  lcd_time_string = info.text["playedtime"] + " / " + info.text["totaltime"];
2615  // if the string is longer than the LCD width, remove all spaces
2616  if (lcd_time_string.length() > static_cast<int>(lcd->getLCDWidth()))
2617  lcd_time_string.remove(' ');
2618  }
2619  }
2620  lcd->setChannelProgress(lcd_time_string, progress);
2621  }
2622  ReturnPlayerLock();
2623 
2625  m_lcdTimerId = StartTimer(kLCDTimeout, __LINE__);
2626 
2627  return true;
2628 }
2629 
2631 {
2633  LCD *lcd = LCD::Get();
2634  if (lcd)
2635  {
2638  }
2639  ReturnPlayerLock();
2640 
2642  m_lcdVolumeTimerId = 0;
2643 }
2644 
2645 int TV::StartTimer(std::chrono::milliseconds Interval, int Line)
2646 {
2647  int timer = startTimer(Interval);
2648  if (!timer)
2649  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to start timer on line %1 of %2").arg(Line).arg(__FILE__));
2650  return timer;
2651 }
2652 
2653 void TV::KillTimer(int Id)
2654 {
2655  killTimer(Id);
2656 }
2657 
2659 {
2662 }
2663 
2665 {
2666  auto StateChange = [&]()
2667  {
2669  if (!m_playerContext.m_nextState.empty())
2670  {
2672  if ((kState_None == m_playerContext.GetState() ||
2674  {
2675  ReturnPlayerLock();
2678  m_player = nullptr;
2679  }
2680  }
2681  ReturnPlayerLock();
2682  };
2683 
2684  QTimer::singleShot(0, this, StateChange);
2685 }
2686 
2688 {
2689  auto InputChange = [&]()
2690  {
2692  if (m_switchToInputId)
2693  {
2695  m_switchToInputId = 0;
2696  SwitchInputs(0, QString(), tmp);
2697  }
2698  ReturnPlayerLock();
2699  };
2700 
2701  QTimer::singleShot(0, this, InputChange);
2702 }
2703 
2705 {
2706  m_playerContext.m_errored = true;
2708  m_errorRecoveryTimerId = StartTimer(1ms, __LINE__);
2709 }
2710 
2712 {
2713  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switching to program: %1")
2714  .arg(ProgInfo.toString(ProgramInfo::kTitleSubtitle)));
2716  PrepareToExitPlayer(__LINE__);
2717  m_jumpToProgram = true;
2718  SetExitPlayer(true, true);
2719 }
2720 
2722 {
2723  bool bm_allowed = IsBookmarkAllowed();
2724  m_playerContext.LockDeletePlayer(__FILE__, Line);
2725  if (m_player)
2726  {
2727  if (bm_allowed)
2728  {
2729  // If we're exiting in the middle of the recording, we
2730  // automatically save a bookmark when "Action on playback
2731  // exit" is set to "Save position and exit".
2732  bool allow_set_before_end =
2733  (Bookmark == kBookmarkAlways ||
2734  (Bookmark == kBookmarkAuto &&
2735  m_dbPlaybackExitPrompt == 2));
2736  // If we're exiting at the end of the recording, we
2737  // automatically clear the bookmark when "Action on
2738  // playback exit" is set to "Save position and exit" and
2739  // "Clear bookmark on playback" is set to true.
2740  bool allow_clear_at_end =
2741  (Bookmark == kBookmarkAlways ||
2742  (Bookmark == kBookmarkAuto &&
2743  m_dbPlaybackExitPrompt == 2 &&
2745  // Whether to set/clear a bookmark depends on whether we're
2746  // exiting at the end of a recording.
2747  bool at_end = (m_player->IsNearEnd() || GetEndOfRecording());
2748  // Don't consider ourselves at the end if the recording is
2749  // in-progress.
2750  at_end &= !StateIsRecording(GetState());
2751  bool clear_lastplaypos = false;
2752  if (at_end && allow_clear_at_end)
2753  {
2754  SetBookmark(true);
2755  // Tidy up the lastplaypos mark only when we clear the
2756  // bookmark due to exiting at the end.
2757  clear_lastplaypos = true;
2758  }
2759  else if (!at_end && allow_set_before_end)
2760  {
2761  SetBookmark(false);
2762  }
2763  if (clear_lastplaypos && m_playerContext.m_playingInfo)
2765  }
2766  if (m_dbAutoSetWatched)
2767  m_player->SetWatched();
2768  }
2769  m_playerContext.UnlockDeletePlayer(__FILE__, Line);
2770 }
2771 
2772 void TV::SetExitPlayer(bool SetIt, bool WantsTo)
2773 {
2774  if (SetIt)
2775  {
2776  m_wantsToQuit = WantsTo;
2777  if (!m_exitPlayerTimerId)
2778  m_exitPlayerTimerId = StartTimer(1ms, __LINE__);
2779  }
2780  else
2781  {
2782  if (m_exitPlayerTimerId)
2784  m_exitPlayerTimerId = 0;
2785  m_wantsToQuit = WantsTo;
2786  }
2787 }
2788 
2790 {
2794 
2795  bool is_playing = false;
2797  if (StateIsPlaying(GetState()))
2798  {
2800  {
2801  is_playing = true;
2802  }
2803  // If the end of playback is destined to pop up the end of
2804  // recording delete prompt, then don't exit the player here.
2805  else if (!(GetState() == kState_WatchingPreRecorded &&
2807  {
2809  m_endOfRecording = true;
2810  PrepareToExitPlayer(__LINE__);
2811  SetExitPlayer(true, true);
2812  }
2813  }
2814  ReturnPlayerLock();
2815 
2816  if (is_playing)
2818 }
2819 
2821 {
2824  {
2825  return;
2826  }
2827 
2829  OSD *osd = GetOSDL();
2830  if (osd && osd->DialogVisible())
2831  {
2832  ReturnOSDLock();
2833  ReturnPlayerLock();
2834  return;
2835  }
2836  ReturnOSDLock();
2837 
2838  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
2839  bool do_prompt = (m_playerContext.GetState() == kState_WatchingPreRecorded &&
2841  !m_player->IsPlaying());
2842  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
2843 
2844  if (do_prompt)
2845  ShowOSDPromptDeleteRecording(tr("End Of Recording"));
2846 
2847  ReturnPlayerLock();
2848 }
2849 
2851 {
2855 
2856  // disable dialog and exit playback after timeout
2858  OSD *osd = GetOSDL();
2859  if (!osd || !osd->DialogVisible(OSD_DLG_VIDEOEXIT))
2860  {
2861  ReturnOSDLock();
2862  ReturnPlayerLock();
2863  return;
2864  }
2865  ReturnOSDLock();
2866  DoTogglePause(true);
2867  ClearOSD();
2868  PrepareToExitPlayer(__LINE__);
2869  ReturnPlayerLock();
2870 
2871  m_requestDelete = false;
2872  SetExitPlayer(true, true);
2873 }
2874 
2876 {
2879 
2880  bool restartTimer = false;
2883  {
2885  {
2886  restartTimer = true;
2887  }
2888  else
2889  {
2890  LOG(VB_CHANNEL, LOG_INFO, "REC_PROGRAM -- channel change");
2891 
2893  QString channum = m_playerContext.m_pseudoLiveTVRec->GetChanNum();
2895 
2896  m_playerContext.m_prevChan.clear();
2897  ChangeChannel(chanid, channum);
2900  }
2901  }
2902  ReturnPlayerLock();
2903 
2904  if (restartTimer)
2906  m_pseudoChangeChanTimerId = StartTimer(25ms, __LINE__);
2907 }
2908 
2909 void TV::SetSpeedChangeTimer(std::chrono::milliseconds When, int Line)
2910 {
2913  m_speedChangeTimerId = StartTimer(When, Line);
2914 }
2915 
2917 {
2921 
2923  bool update_msg = m_playerContext.HandlePlayerSpeedChangeFFRew();
2925  if (update_msg)
2927  ReturnPlayerLock();
2928 }
2929 
2931 bool TV::eventFilter(QObject* Object, QEvent* Event)
2932 {
2933  // We want to intercept all resize events sent to the main window
2934  if ((Event->type() == QEvent::Resize))
2935  return (m_mainWindow != Object) ? false : event(Event);
2936 
2937  // Intercept keypress events unless they need to be handled by a main UI
2938  // screen (e.g. GuideGrid, ProgramFinder)
2939 
2940  if ( (QEvent::KeyPress == Event->type() || QEvent::KeyRelease == Event->type())
2941  && m_ignoreKeyPresses )
2942  return TVPlaybackState::eventFilter(Object, Event);
2943 
2944  QScopedPointer<QEvent> sNewEvent(nullptr);
2945  if (m_mainWindow->KeyLongPressFilter(&Event, sNewEvent))
2946  return true;
2947 
2948  if (QEvent::KeyPress == Event->type())
2949  return event(Event);
2950 
2951  if (MythGestureEvent::kEventType == Event->type())
2952  return m_ignoreKeyPresses ? false : event(Event);
2953 
2954  if (Event->type() == MythEvent::MythEventMessage ||
2955  Event->type() == MythEvent::MythUserMessage ||
2957  Event->type() == MythMediaEvent::kEventType)
2958  {
2959  customEvent(Event);
2960  return true;
2961  }
2962 
2963  switch (Event->type())
2964  {
2965  case QEvent::Paint:
2966  case QEvent::UpdateRequest:
2967  case QEvent::Enter:
2968  {
2969  event(Event);
2970  return TVPlaybackState::eventFilter(Object, Event);
2971  }
2972  default:
2973  return TVPlaybackState::eventFilter(Object, Event);
2974  }
2975 }
2976 
2978 bool TV::event(QEvent* Event)
2979 {
2980  if (Event == nullptr)
2981  return TVPlaybackState::event(Event);
2982 
2983  if (QEvent::Resize == Event->type())
2984  {
2985  const auto *qre = dynamic_cast<const QResizeEvent*>(Event);
2986  if (qre)
2987  emit WindowResized(qre->size());
2988  return TVPlaybackState::event(Event);
2989  }
2990 
2991  if (QEvent::KeyPress == Event->type() || MythGestureEvent::kEventType == Event->type())
2992  {
2993 #if DEBUG_ACTIONS
2994  if (QEvent::KeyPress == Event->type())
2995  {
2996  const auto * ke = dynamic_cast<QKeyEvent*>(Event);
2997  if (ke)
2998  {
2999  LOG(VB_GENERAL, LOG_INFO, LOC + QString("keypress: %1 '%2'")
3000  .arg(ke->key()).arg(ke->text()));
3001  }
3002  }
3003  else
3004  {
3005  const auto * ge = dynamic_cast<MythGestureEvent*>(Event);
3006  if (ge)
3007  {
3008  LOG(VB_GENERAL, LOG_INFO, LOC + QString("mythgesture: g:%1 pos:%2,%3 b:%4")
3009  .arg(ge->GetGesture()).arg(ge->GetPosition().x())
3010  .arg(ge->GetPosition().y()).arg(ge->GetButton()));
3011  }
3012  }
3013 #endif
3014  bool handled = false;
3016  if (m_playerContext.HasPlayer())
3017  handled = ProcessKeypressOrGesture(Event);
3018  ReturnPlayerLock();
3019  if (handled)
3020  return true;
3021  }
3022 
3023  switch (Event->type())
3024  {
3025  case QEvent::Paint:
3026  case QEvent::UpdateRequest:
3027  case QEvent::Enter:
3028  return true;
3029  default:
3030  break;
3031  }
3032 
3033  return QObject::event(Event);
3034 }
3035 
3036 bool TV::HandleTrackAction(const QString &Action)
3037 {
3038  bool handled = true;
3039 
3042  else if (ACTION_ENABLEEXTTEXT == Action)
3044  else if (ACTION_DISABLEEXTTEXT == Action)
3046  else if (ACTION_ENABLEFORCEDSUBS == Action)
3047  emit ChangeAllowForcedSubtitles(true);
3048  else if (ACTION_DISABLEFORCEDSUBS == Action)
3049  emit ChangeAllowForcedSubtitles(false);
3050  else if (Action == ACTION_ENABLESUBS)
3051  emit SetCaptionsEnabled(true, true);
3052  else if (Action == ACTION_DISABLESUBS)
3053  emit SetCaptionsEnabled(false, true);
3055  {
3056  if (m_ccInputMode)
3057  {
3058  bool valid = false;
3059  int page = GetQueuedInputAsInt(&valid, 16);
3060  if (m_vbimode == VBIMode::PAL_TT && valid)
3061  emit SetTeletextPage(static_cast<uint>(page));
3062  else if (m_vbimode == VBIMode::NTSC_CC)
3063  emit SetTrack(kTrackTypeCC608, static_cast<uint>(std::max(std::min(page - 1, 1), 0)));
3064 
3065  ClearInputQueues(true);
3066 
3067  m_ccInputMode = false;
3068  if (m_ccInputTimerId)
3069  {
3071  m_ccInputTimerId = 0;
3072  }
3073  }
3075  {
3076  ClearInputQueues(false);
3077  AddKeyToInputQueue(0);
3078 
3079  m_ccInputMode = true;
3080  m_asInputMode = false;
3082  if (m_asInputTimerId)
3083  {
3085  m_asInputTimerId = 0;
3086  }
3087  }
3088  else
3089  {
3090  emit ToggleCaptions();
3091  }
3092  }
3093  else if (Action.startsWith("TOGGLE"))
3094  {
3095  int type = to_track_type(Action.mid(6));
3097  emit EnableTeletext();
3098  else if (type >= kTrackTypeSubtitle)
3099  emit ToggleCaptionsByType(static_cast<uint>(type));
3100  else
3101  handled = false;
3102  }
3103  else if (Action.startsWith("SELECT"))
3104  {
3105  int type = to_track_type(Action.mid(6));
3106  uint num = Action.section("_", -1).toUInt();
3107  if (type >= kTrackTypeAudio)
3108  emit SetTrack(static_cast<uint>(type), num);
3109  else
3110  handled = false;
3111  }
3112  else if (Action.startsWith("NEXT") || Action.startsWith("PREV"))
3113  {
3114  int dir = (Action.startsWith("NEXT")) ? +1 : -1;
3115  int type = to_track_type(Action.mid(4));
3116  if (type >= kTrackTypeAudio)
3117  emit ChangeTrack(static_cast<uint>(type), dir);
3118  else if (Action.endsWith("CC"))
3119  emit ChangeCaptionTrack(dir);
3120  else
3121  handled = false;
3122  }
3123  else
3124  handled = false;
3125  return handled;
3126 }
3127 
3128 // Make a special check for global system-related events.
3129 //
3130 // This check needs to be done early in the keypress event processing,
3131 // because FF/REW processing causes unknown events to stop FF/REW, and
3132 // manual zoom mode processing consumes all but a few event types.
3133 // Ideally, we would just call MythScreenType::keyPressEvent()
3134 // unconditionally, but we only want certain keypresses handled by
3135 // that method.
3136 //
3137 // As a result, some of the MythScreenType::keyPressEvent() string
3138 // compare logic is copied here.
3139 static bool SysEventHandleAction(MythMainWindow* MainWindow, QKeyEvent *e, const QStringList &actions)
3140 {
3141  QStringList::const_iterator it;
3142  for (it = actions.begin(); it != actions.end(); ++it)
3143  {
3144  if ((*it).startsWith("SYSEVENT") ||
3145  *it == ACTION_SCREENSHOT ||
3146  *it == ACTION_TVPOWERON ||
3147  *it == ACTION_TVPOWEROFF)
3148  {
3149  return MainWindow->GetMainStack()->GetTopScreen()->keyPressEvent(e);
3150  }
3151  }
3152  return false;
3153 }
3154 
3155 QList<QKeyEvent*> TV::ConvertScreenPressKeyMap(const QString &KeyList)
3156 {
3157  QList<QKeyEvent*> keyPressList;
3158  int i = 0;
3159  QStringList stringKeyList = KeyList.split(',');
3160  for (const auto & str : qAsConst(stringKeyList))
3161  {
3162  QKeySequence keySequence(str);
3163  for(i = 0; i < keySequence.count(); i++)
3164  {
3165  uint keynum = static_cast<uint>(keySequence[static_cast<uint>(i)]);
3166  auto * keyEvent = new QKeyEvent(QEvent::None, keynum & ~Qt::KeyboardModifierMask,
3167  static_cast<Qt::KeyboardModifiers>(keynum & Qt::KeyboardModifierMask));
3168  keyPressList.append(keyEvent);
3169  }
3170  }
3171  if (stringKeyList.count() < kScreenPressRegionCount)
3172  {
3173  // add default remainders
3174  for(; i < kScreenPressRegionCount; i++)
3175  {
3176  auto * keyEvent = new QKeyEvent(QEvent::None, Qt::Key_Escape, Qt::NoModifier);
3177  keyPressList.append(keyEvent);
3178  }
3179  }
3180  return keyPressList;
3181 }
3182 
3183 bool TV::TranslateGesture(const QString &Context, MythGestureEvent *Event,
3184  QStringList &Actions, bool IsLiveTV)
3185 {
3186  if (Event && Context == "TV Playback")
3187  {
3188  // TODO make this configuable via a similar mechanism to
3189  // TranslateKeyPress
3190  // possibly with configurable hot zones of various sizes in a theme
3191  // TODO enhance gestures to support other non Click types too
3192  if ((Event->GetGesture() == MythGestureEvent::Click) &&
3193  (Event->GetButton() == Qt::LeftButton))
3194  {
3195  // divide screen into 12 regions
3196  QSize size = m_mainWindow->size();
3197  QPoint pos = Event->GetPosition();
3198  int region = 0;
3199  const int widthDivider = 4;
3200  int w4 = size.width() / widthDivider;
3201  region = pos.x() / w4;
3202  int h3 = size.height() / 3;
3203  region += (pos.y() / h3) * widthDivider;
3204 
3205  if (IsLiveTV)
3206  return m_mainWindow->TranslateKeyPress(Context, m_screenPressKeyMapLiveTV[region], Actions, true);
3207  return m_mainWindow->TranslateKeyPress(Context, m_screenPressKeyMapPlayback[region], Actions, true);
3208  }
3209  return false;
3210  }
3211  return false;
3212 }
3213 
3214 bool TV::TranslateKeyPressOrGesture(const QString &Context, QEvent *Event,
3215  QStringList &Actions, bool IsLiveTV, bool AllowJumps)
3216 {
3217  if (Event)
3218  {
3219  if (QEvent::KeyPress == Event->type())
3220  return m_mainWindow->TranslateKeyPress(Context, dynamic_cast<QKeyEvent*>(Event), Actions, AllowJumps);
3221  if (MythGestureEvent::kEventType == Event->type())
3222  return TranslateGesture(Context, dynamic_cast<MythGestureEvent*>(Event), Actions, IsLiveTV);
3223  }
3224  return false;
3225 }
3226 
3228 {
3229  if (Event == nullptr)
3230  return false;
3231 
3232  bool ignoreKeys = m_playerContext.IsPlayerChangingBuffers();
3233 
3234 #if DEBUG_ACTIONS
3235  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ignoreKeys: %1").arg(ignoreKeys));
3236 #endif
3237 
3238  if (m_idleTimerId)
3239  {
3242  }
3243 
3244 #ifdef Q_OS_LINUX
3245  // Fixups for _some_ linux native codes that QT doesn't know
3246  auto* eKeyEvent = dynamic_cast<QKeyEvent*>(Event);
3247  if (eKeyEvent) {
3248  if (eKeyEvent->key() <= 0)
3249  {
3250  int keycode = 0;
3251  switch(eKeyEvent->nativeScanCode())
3252  {
3253  case 209: // XF86AudioPause
3254  keycode = Qt::Key_MediaPause;
3255  break;
3256  default:
3257  break;
3258  }
3259 
3260  if (keycode > 0)
3261  {
3262  auto *key = new QKeyEvent(QEvent::KeyPress, keycode, eKeyEvent->modifiers());
3263  QCoreApplication::postEvent(this, key);
3264  }
3265  }
3266  }
3267 #endif
3268 
3269  QStringList actions;
3270  bool handled = false;
3271  bool alreadyTranslatedPlayback = false;
3272 
3273  TVState state = GetState();
3274  bool isLiveTV = StateIsLiveTV(state);
3275 
3276  if (ignoreKeys)
3277  {
3278  handled = TranslateKeyPressOrGesture("TV Playback", Event, actions, isLiveTV);
3279  alreadyTranslatedPlayback = true;
3280 
3281  if (handled || actions.isEmpty())
3282  return handled;
3283 
3284  bool esc = IsActionable({ "ESCAPE", "BACK" }, actions);
3285  bool pause = IsActionable(ACTION_PAUSE, actions);
3286  bool play = IsActionable(ACTION_PLAY, actions);
3287 
3288  if ((!esc || m_overlayState.m_browsing) && !pause && !play)
3289  return false;
3290  }
3291 
3292  OSD *osd = GetOSDL();
3293  if (osd && osd->DialogVisible())
3294  {
3295  if (QEvent::KeyPress == Event->type())
3296  {
3297  auto *qke = dynamic_cast<QKeyEvent*>(Event);
3298  handled = (qke != nullptr) && osd->DialogHandleKeypress(qke);
3299  }
3300  if (MythGestureEvent::kEventType == Event->type())
3301  {
3302  auto *mge = dynamic_cast<MythGestureEvent*>(Event);
3303  handled = (mge != nullptr) && osd->DialogHandleGesture(mge);
3304  }
3305  }
3306  ReturnOSDLock();
3307 
3308  if (m_overlayState.m_editing && !handled)
3309  {
3310  handled |= TranslateKeyPressOrGesture("TV Editing", Event, actions, isLiveTV);
3311 
3312  if (!handled && m_player)
3313  {
3314  if (IsActionable("MENU", actions))
3315  {
3316  ShowOSDCutpoint("EDIT_CUT_POINTS");
3317  handled = true;
3318  }
3319  if (IsActionable(ACTION_MENUCOMPACT, actions))
3320  {
3321  ShowOSDCutpoint("EDIT_CUT_POINTS_COMPACT");
3322  handled = true;
3323  }
3324  if (IsActionable("ESCAPE", actions))
3325  {
3326  emit RefreshEditorState(true);
3327  if (!m_editorState.m_saved)
3328  ShowOSDCutpoint("EXIT_EDIT_MODE");
3329  else
3330  emit DisableEdit(0);
3331  handled = true;
3332  }
3333  else
3334  {
3335  emit RefreshEditorState();
3338  {
3339  ShowOSDCutpoint("EDIT_CUT_POINTS");
3340  handled = true;
3341  }
3342  else
3343  {
3344  handled |= m_player->HandleProgramEditorActions(actions);
3345  }
3346  }
3347  }
3348  }
3349 
3350  if (handled)
3351  return true;
3352 
3353  // If text is already queued up, be more lax on what is ok.
3354  // This allows hex teletext entry and minor channel entry.
3355  if (QEvent::KeyPress == Event->type())
3356  {
3357  auto *qke = dynamic_cast<QKeyEvent*>(Event);
3358  if (qke == nullptr)
3359  return false;
3360  const QString txt = qke->text();
3361  if (HasQueuedInput() && (1 == txt.length()))
3362  {
3363  bool ok = false;
3364  (void)txt.toInt(&ok, 16);
3365  if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
3366  {
3367  AddKeyToInputQueue(txt.at(0).toLatin1());
3368  return true;
3369  }
3370  }
3371  }
3372 
3373  // Teletext menu
3375  {
3376  QStringList tt_actions;
3377  handled = TranslateKeyPressOrGesture("Teletext Menu", Event, tt_actions, isLiveTV);
3378 
3379  if (!handled && !tt_actions.isEmpty())
3380  {
3381  for (int i = 0; i < tt_actions.size(); i++)
3382  {
3383  emit HandleTeletextAction(tt_actions[i], handled);
3384  if (handled)
3385  return true;
3386  }
3387  }
3388  }
3389 
3390  // Interactive television
3392  {
3393  if (!alreadyTranslatedPlayback)
3394  {
3395  handled = TranslateKeyPressOrGesture("TV Playback", Event, actions, isLiveTV);
3396  alreadyTranslatedPlayback = true;
3397  }
3398 
3399  if (!handled && !actions.isEmpty())
3400  {
3401  for (int i = 0; i < actions.size(); i++)
3402  {
3403  emit HandleITVAction(actions[i], handled);
3404  if (handled)
3405  return true;
3406  }
3407  }
3408  }
3409 
3410  if (!alreadyTranslatedPlayback)
3411  handled = TranslateKeyPressOrGesture("TV Playback", Event, actions, isLiveTV);
3412 
3413  if (handled || actions.isEmpty())
3414  return handled;
3415 
3416  handled = false;
3417 
3420 
3421  if (QEvent::KeyPress == Event->type())
3422  handled = handled || SysEventHandleAction(m_mainWindow, dynamic_cast<QKeyEvent*>(Event), actions);
3423  handled = handled || BrowseHandleAction(actions);
3424  handled = handled || ManualZoomHandleAction(actions);
3425  handled = handled || PictureAttributeHandleAction(actions);
3426  handled = handled || TimeStretchHandleAction(actions);
3427  handled = handled || AudioSyncHandleAction(actions);
3428  handled = handled || SubtitleZoomHandleAction(actions);
3429  handled = handled || SubtitleDelayHandleAction(actions);
3430  handled = handled || DiscMenuHandleAction(actions);
3431  handled = handled || ActiveHandleAction(actions, isDVD, isMenuOrStill);
3432  handled = handled || ToggleHandleAction(actions, isDVD);
3433  handled = handled || FFRewHandleAction(actions);
3434  handled = handled || ActivePostQHandleAction(actions);
3435 
3436 #if DEBUG_ACTIONS
3437  for (int i = 0; i < actions.size(); ++i)
3438  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("handled(%1) actions[%2](%3)")
3439  .arg(handled).arg(i).arg(actions[i]));
3440 #endif // DEBUG_ACTIONS
3441 
3442  if (handled)
3443  return true;
3444 
3445  if (!handled)
3446  {
3447  for (int i = 0; i < actions.size() && !handled; i++)
3448  {
3449  QString action = actions[i];
3450  bool ok = false;
3451  int val = action.toInt(&ok);
3452 
3453  if (ok)
3454  {
3455  AddKeyToInputQueue(static_cast<char>('0' + val));
3456  handled = true;
3457  }
3458  }
3459  }
3460 
3461  return true;
3462 }
3463 
3464 bool TV::BrowseHandleAction(const QStringList &Actions)
3465 {
3467  return false;
3468 
3469  bool handled = true;
3470 
3471  if (IsActionable({ ACTION_UP, ACTION_CHANNELUP }, Actions))
3473  else if (IsActionable( { ACTION_DOWN, ACTION_CHANNELDOWN }, Actions))
3475  else if (IsActionable(ACTION_LEFT, Actions))
3477  else if (IsActionable(ACTION_RIGHT, Actions))
3479  else if (IsActionable("NEXTFAV", Actions))
3481  else if (IsActionable(ACTION_SELECT, Actions))
3482  BrowseEnd(true);
3483  else if (IsActionable({ ACTION_CLEAROSD, "ESCAPE", "BACK", "TOGGLEBROWSE" }, Actions))
3484  BrowseEnd(false);
3485  else if (IsActionable(ACTION_TOGGLERECORD, Actions))
3486  QuickRecord();
3487  else
3488  {
3489  handled = false;
3490  for (const auto& action : qAsConst(Actions))
3491  {
3492  if (action.length() == 1 && action[0].isDigit())
3493  {
3494  AddKeyToInputQueue(action[0].toLatin1());
3495  handled = true;
3496  }
3497  }
3498  }
3499 
3500  // only pass-through actions listed below
3501  static const QStringList passthrough =
3502  {
3503  ACTION_VOLUMEUP, ACTION_VOLUMEDOWN, "STRETCHINC", "STRETCHDEC",
3504  ACTION_MUTEAUDIO, "CYCLEAUDIOCHAN", "BOTTOMLINEMOVE", "BOTTOMLINESAVE", "TOGGLEASPECT"
3505  };
3506  return handled || !IsActionable(passthrough, Actions);
3507 }
3508 
3509 bool TV::ManualZoomHandleAction(const QStringList &Actions)
3510 {
3511  if (!m_zoomMode)
3512  return false;
3513 
3514  bool endmanualzoom = false;
3515  bool handled = true;
3516  bool updateOSD = true;
3517  ZoomDirection zoom = kZoom_END;
3519  zoom = kZoomUp;
3521  zoom = kZoomDown;
3522  else if (IsActionable({ ACTION_ZOOMLEFT, ACTION_LEFT }, Actions))
3523  zoom = kZoomLeft;
3524  else if (IsActionable({ ACTION_ZOOMRIGHT, ACTION_RIGHT }, Actions))
3525  zoom = kZoomRight;
3526  else if (IsActionable({ ACTION_ZOOMASPECTUP, ACTION_VOLUMEUP }, Actions))
3527  zoom = kZoomAspectUp;
3528  else if (IsActionable({ ACTION_ZOOMASPECTDOWN, ACTION_VOLUMEDOWN }, Actions))
3529  zoom = kZoomAspectDown;
3530  else if (IsActionable({ ACTION_ZOOMIN, ACTION_JUMPFFWD }, Actions))
3531  zoom = kZoomIn;
3532  else if (IsActionable({ ACTION_ZOOMOUT, ACTION_JUMPRWND }, Actions))
3533  zoom = kZoomOut;
3534  else if (IsActionable(ACTION_ZOOMVERTICALIN, Actions))
3535  zoom = kZoomVerticalIn;
3536  else if (IsActionable(ACTION_ZOOMVERTICALOUT, Actions))
3537  zoom = kZoomVerticalOut;
3538  else if (IsActionable(ACTION_ZOOMHORIZONTALIN, Actions))
3539  zoom = kZoomHorizontalIn;
3540  else if (IsActionable(ACTION_ZOOMHORIZONTALOUT, Actions))
3541  zoom = kZoomHorizontalOut;
3542  else if (IsActionable({ ACTION_ZOOMQUIT, "ESCAPE", "BACK" }, Actions))
3543  {
3544  zoom = kZoomHome;
3545  endmanualzoom = true;
3546  }
3547  else if (IsActionable({ ACTION_ZOOMCOMMIT, ACTION_SELECT }, Actions))
3548  {
3549  endmanualzoom = true;
3550  SetManualZoom(false, tr("Zoom Committed"));
3551  }
3552  else
3553  {
3554  updateOSD = false;
3555  // only pass-through actions listed below
3556  static const QStringList passthrough =
3557  {
3558  "STRETCHINC", "STRETCHDEC", ACTION_MUTEAUDIO,
3559  "CYCLEAUDIOCHAN", ACTION_PAUSE, ACTION_CLEAROSD
3560  };
3561  handled = !IsActionable(passthrough, Actions);
3562  }
3563 
3564  QString msg = tr("Zoom Committed");
3565  if (zoom != kZoom_END)
3566  {
3567  emit ChangeZoom(zoom);
3568  msg = endmanualzoom ? tr("Zoom Ignored") :
3572  }
3573  else if (endmanualzoom)
3574  {
3575  msg = tr("%1 Committed").arg(GetZoomString(m_videoBoundsState.m_manualHorizScale,
3578  }
3579 
3580  if (updateOSD)
3581  SetManualZoom(!endmanualzoom, msg);
3582 
3583  return handled;
3584 }
3585 
3586 bool TV::PictureAttributeHandleAction(const QStringList &Actions)
3587 {
3588  if (!m_adjustingPicture)
3589  return false;
3590 
3591  bool up = IsActionable(ACTION_RIGHT, Actions);
3592  bool down = up ? false : IsActionable(ACTION_LEFT, Actions);
3593  if (!(up || down))
3594  return false;
3595 
3597  {
3599  VolumeChange(up);
3600  else
3602  return true;
3603  }
3604 
3605  int value = 99;
3609  UpdateOSDStatus(toTitleString(m_adjustingPicture), text, QString::number(value),
3611  emit ChangeOSDPositionUpdates(false);
3612  return true;
3613 }
3614 
3615 bool TV::TimeStretchHandleAction(const QStringList &Actions)
3616 {
3617  if (!m_stretchAdjustment)
3618  return false;
3619 
3620  bool handled = true;
3621 
3622  if (IsActionable(ACTION_LEFT, Actions))
3623  ChangeTimeStretch(-1);
3624  else if (IsActionable(ACTION_RIGHT, Actions))
3625  ChangeTimeStretch(1);
3626  else if (IsActionable(ACTION_DOWN, Actions))
3627  ChangeTimeStretch(-5);
3628  else if (IsActionable(ACTION_UP, Actions))
3629  ChangeTimeStretch(5);
3630  else if (IsActionable("ADJUSTSTRETCH", Actions))
3632  else if (IsActionable(ACTION_SELECT, Actions))
3633  ClearOSD();
3634  else
3635  handled = false;
3636 
3637  return handled;
3638 }
3639 
3640 bool TV::AudioSyncHandleAction(const QStringList& Actions)
3641 {
3642  if (!m_audiosyncAdjustment)
3643  return false;
3644 
3645  bool handled = true;
3646 
3647  if (IsActionable(ACTION_LEFT, Actions))
3648  emit ChangeAudioOffset(-1ms);
3649  else if (IsActionable(ACTION_RIGHT, Actions))
3650  emit ChangeAudioOffset(1ms);
3651  else if (IsActionable(ACTION_UP, Actions))
3652  emit ChangeAudioOffset(10ms);
3653  else if (IsActionable(ACTION_DOWN, Actions))
3654  emit ChangeAudioOffset(-10ms);
3655  else if (IsActionable({ ACTION_TOGGELAUDIOSYNC, ACTION_SELECT }, Actions))
3656  ClearOSD();
3657  else
3658  handled = false;
3659 
3660  return handled;
3661 }
3662 
3663 bool TV::SubtitleZoomHandleAction(const QStringList &Actions)
3664 {
3666  return false;
3667 
3668  bool handled = true;
3669 
3670  if (IsActionable(ACTION_LEFT, Actions))
3671  emit AdjustSubtitleZoom(-1);
3672  else if (IsActionable(ACTION_RIGHT, Actions))
3673  emit AdjustSubtitleZoom(1);
3674  else if (IsActionable(ACTION_UP, Actions))
3675  emit AdjustSubtitleZoom(10);
3676  else if (IsActionable(ACTION_DOWN, Actions))
3677  emit AdjustSubtitleZoom(-10);
3678  else if (IsActionable({ ACTION_TOGGLESUBTITLEZOOM, ACTION_SELECT }, Actions))
3679  ClearOSD();
3680  else
3681  handled = false;
3682 
3683  return handled;
3684 }
3685 
3686 bool TV::SubtitleDelayHandleAction(const QStringList &Actions)
3687 {
3689  return false;
3690 
3691  bool handled = true;
3692 
3693  if (IsActionable(ACTION_LEFT, Actions))
3694  emit AdjustSubtitleDelay(-5ms);
3695  else if (IsActionable(ACTION_RIGHT, Actions))
3696  emit AdjustSubtitleDelay(5ms);
3697  else if (IsActionable(ACTION_UP, Actions))
3698  emit AdjustSubtitleDelay(25ms);
3699  else if (IsActionable(ACTION_DOWN, Actions))
3700  emit AdjustSubtitleDelay(-25ms);
3701  else if (IsActionable({ ACTION_TOGGLESUBTITLEDELAY, ACTION_SELECT }, Actions))
3702  ClearOSD();
3703  else
3704  handled = false;
3705 
3706  return handled;
3707 }
3708 
3709 bool TV::DiscMenuHandleAction(const QStringList& Actions) const
3710 {
3711  mpeg::chrono::pts pts = 0_pts;
3713  if (output)
3714  {
3715  MythVideoFrame *frame = output->GetLastShownFrame();
3716  // convert timecode (msec) to pts (90kHz)
3717  if (frame)
3718  pts = duration_cast<mpeg::chrono::pts>(frame->m_timecode);
3719  }
3721  return m_playerContext.m_buffer->HandleAction(Actions, pts);
3722  return false;
3723 }
3724 
3725 bool TV::ActiveHandleAction(const QStringList &Actions,
3726  bool IsDVD, bool IsDVDStillFrame)
3727 {
3728  bool handled = true;
3729 
3730  if (IsActionable("SKIPCOMMERCIAL", Actions) && !IsDVD)
3731  DoSkipCommercials(1);
3732  else if (IsActionable("SKIPCOMMBACK", Actions) && !IsDVD)
3733  DoSkipCommercials(-1);
3734  else if (IsActionable("QUEUETRANSCODE", Actions) && !IsDVD)
3735  DoQueueTranscode("Default");
3736  else if (IsActionable("QUEUETRANSCODE_AUTO", Actions) && !IsDVD)
3737  DoQueueTranscode("Autodetect");
3738  else if (IsActionable("QUEUETRANSCODE_HIGH", Actions) && !IsDVD)
3739  DoQueueTranscode("High Quality");
3740  else if (IsActionable("QUEUETRANSCODE_MEDIUM", Actions) && !IsDVD)
3741  DoQueueTranscode("Medium Quality");
3742  else if (IsActionable("QUEUETRANSCODE_LOW", Actions) && !IsDVD)
3743  DoQueueTranscode("Low Quality");
3744  else if (IsActionable(ACTION_PLAY, Actions))
3745  DoPlay();
3746  else if (IsActionable(ACTION_PAUSE, Actions))
3747  DoTogglePause(true);
3748  else if (IsActionable("SPEEDINC", Actions) && !IsDVDStillFrame)
3749  ChangeSpeed(1);
3750  else if (IsActionable("SPEEDDEC", Actions) && !IsDVDStillFrame)
3751  ChangeSpeed(-1);
3752  else if (IsActionable("ADJUSTSTRETCH", Actions))
3753  ChangeTimeStretch(0); // just display
3754  else if (IsActionable("CYCLECOMMSKIPMODE",Actions) && !IsDVD)
3756  else if (IsActionable("NEXTSCAN", Actions))
3757  {
3758  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3760  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3761  OverrideScan(scan);
3762  }
3763  else if (IsActionable(ACTION_SEEKARB, Actions) && !IsDVD)
3764  {
3765  if (m_asInputMode)
3766  {
3767  ClearInputQueues(true);
3768  emit ChangeOSDText(OSD_WIN_INPUT, {{"osd_number_entry", tr("Seek:")}}, kOSDTimeout_Med);
3769  m_asInputMode = false;
3770  if (m_asInputTimerId)
3771  {
3773  m_asInputTimerId = 0;
3774  }
3775  }
3776  else
3777  {
3778  ClearInputQueues(false);
3779  AddKeyToInputQueue(0);
3780  m_asInputMode = true;
3781  m_ccInputMode = false;
3783  if (m_ccInputTimerId)
3784  {
3786  m_ccInputTimerId = 0;
3787  }
3788  }
3789  }
3790  else if (IsActionable(ACTION_JUMPRWND, Actions))
3791  DoJumpRWND();
3792  else if (IsActionable(ACTION_JUMPFFWD, Actions))
3793  DoJumpFFWD();
3794  else if (IsActionable(ACTION_JUMPBKMRK, Actions))
3795  {
3796  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3797  uint64_t bookmark = m_player->GetBookmark();
3798  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3799 
3800  if (bookmark)
3801  {
3802  DoPlayerSeekToFrame(bookmark);
3803  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
3804  UpdateOSDSeekMessage(tr("Jump to Bookmark"), kOSDTimeout_Med);
3805  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
3806  }
3807  }
3808  else if (IsActionable(ACTION_JUMPSTART,Actions))
3809  {
3810  DoSeek(0, tr("Jump to Beginning"), /*timeIsOffset*/false, /*honorCutlist*/true);
3811  }
3812  else if (IsActionable(ACTION_CLEAROSD, Actions))
3813  {
3814  ClearOSD();
3815  }
3816  else if (IsActionable(ACTION_VIEWSCHEDULED, Actions))
3818  else if (HandleJumpToProgramAction(Actions))
3819  { // NOLINT(bugprone-branch-clone)
3820  }
3821  else if (IsActionable(ACTION_SIGNALMON, Actions))
3822  {
3824  {
3825  QString input = m_playerContext.m_recorder->GetInput();
3827 
3828  if (timeout == 0xffffffff)
3829  {
3830  emit ChangeOSDMessage("No Signal Monitor");
3831  return false;
3832  }
3833 
3834  std::chrono::milliseconds rate = m_sigMonMode ? 0ms : 100ms;
3835  bool notify = !m_sigMonMode;
3836 
3837  PauseLiveTV();
3839  UnpauseLiveTV();
3840 
3841  m_lockTimerOn = false;
3843  }
3844  }
3845  else if (IsActionable(ACTION_SCREENSHOT, Actions))
3846  {
3848  }
3849  else if (IsActionable(ACTION_STOP, Actions))
3850  {
3851  PrepareToExitPlayer(__LINE__);
3852  SetExitPlayer(true, true);
3853  }
3854  else if (IsActionable(ACTION_EXITSHOWNOPROMPTS, Actions))
3855  {
3856  m_requestDelete = false;
3857  PrepareToExitPlayer(__LINE__);
3858  SetExitPlayer(true, true);
3859  }
3860  else if (IsActionable({ "ESCAPE", "BACK" }, Actions))
3861  {
3864  {
3865  ClearOSD();
3866  }
3867  else
3868  {
3869  bool visible = false;
3870  emit IsOSDVisible(visible);
3871  if (visible)
3872  {
3873  ClearOSD();
3874  return handled;
3875  }
3876  }
3877 
3878  NormalSpeed();
3879  StopFFRew();
3880  bool exit = false;
3881  if (StateIsLiveTV(GetState()))
3882  {
3884  {
3886  return handled;
3887  }
3888  exit = true;
3889  }
3890  else
3891  {
3893  !m_underNetworkControl && !IsDVDStillFrame)
3894  {
3896  return handled;
3897  }
3898  PrepareToExitPlayer(__LINE__);
3899  m_requestDelete = false;
3900  exit = true;
3901  }
3902 
3903  if (exit)
3904  {
3905  // If it's a DVD, and we're not trying to execute a
3906  // jumppoint, try to back up.
3907  if (IsDVD && !m_mainWindow->IsExitingToMain() && IsActionable("BACK", Actions) &&
3909  {
3910  return handled;
3911  }
3912  SetExitPlayer(true, true);
3913  }
3914  }
3915  else if (IsActionable(ACTION_ENABLEUPMIX, Actions))
3916  emit ChangeUpmix(true);
3917  else if (IsActionable(ACTION_DISABLEUPMIX, Actions))
3918  emit ChangeUpmix(false);
3919  else if (IsActionable(ACTION_VOLUMEDOWN, Actions))
3920  VolumeChange(false);
3921  else if (IsActionable(ACTION_VOLUMEUP, Actions))
3922  VolumeChange(true);
3923  else if (IsActionable("CYCLEAUDIOCHAN", Actions))
3924  emit ChangeMuteState(true);
3925  else if (IsActionable(ACTION_MUTEAUDIO, Actions))
3926  emit ChangeMuteState();
3927  else if (IsActionable("STRETCHINC", Actions))
3928  ChangeTimeStretch(1);
3929  else if (IsActionable("STRETCHDEC", Actions))
3930  ChangeTimeStretch(-1);
3931  else if (IsActionable("MENU", Actions))
3932  ShowOSDMenu();
3933  else if (IsActionable(ACTION_MENUCOMPACT, Actions))
3934  ShowOSDMenu(true);
3935  else if (IsActionable({ "INFO", "INFOWITHCUTLIST" }, Actions))
3936  {
3937  if (HasQueuedInput())
3938  DoArbSeek(ARBSEEK_SET, IsActionable("INFOWITHCUTLIST", Actions));
3939  else
3940  ToggleOSD(true);
3941  }
3942  else if (IsActionable(ACTION_TOGGLEOSDDEBUG, Actions))
3943  emit ChangeOSDDebug();
3944  else if (!IsDVDStillFrame && SeekHandleAction(Actions, IsDVD))
3945  {
3946  }
3947  else
3948  {
3949  handled = false;
3950  for (auto it = Actions.cbegin(); it != Actions.cend() && !handled; ++it)
3951  handled = HandleTrackAction(*it);
3952  }
3953 
3954  return handled;
3955 }
3956 
3957 bool TV::FFRewHandleAction(const QStringList &Actions)
3958 {
3959  bool handled = false;
3960 
3962  {
3963  for (int i = 0; i < Actions.size() && !handled; i++)
3964  {
3965  QString action = Actions[i];
3966  bool ok = false;
3967  int val = action.toInt(&ok);
3968 
3969  if (ok && val < static_cast<int>(m_ffRewSpeeds.size()))
3970  {
3971  SetFFRew(val);
3972  handled = true;
3973  }
3974  }
3975 
3976  if (!handled)
3977  {
3980  handled = true;
3981  }
3982  }
3983 
3985  {
3986  NormalSpeed();
3988  handled = true;
3989  }
3990 
3991  return handled;
3992 }
3993 
3994 bool TV::ToggleHandleAction(const QStringList &Actions, bool IsDVD)
3995 {
3996  bool handled = true;
3997  bool islivetv = StateIsLiveTV(GetState());
3998 
3999  if (IsActionable(ACTION_BOTTOMLINEMOVE, Actions))
4000  emit ToggleMoveBottomLine();
4001  else if (IsActionable(ACTION_BOTTOMLINESAVE, Actions))
4002  emit SaveBottomLine();
4003  else if (IsActionable("TOGGLEASPECT", Actions))
4004  emit ChangeAspectOverride();
4005  else if (IsActionable("TOGGLEFILL", Actions))
4006  emit ChangeAdjustFill();
4007  else if (IsActionable(ACTION_TOGGELAUDIOSYNC, Actions))
4008  emit ChangeAudioOffset(0ms); // just display
4009  else if (IsActionable(ACTION_TOGGLESUBTITLEZOOM, Actions))
4010  emit AdjustSubtitleZoom(0); // just display
4011  else if (IsActionable(ACTION_TOGGLESUBTITLEDELAY, Actions))
4012  emit AdjustSubtitleDelay(0ms); // just display
4013  else if (IsActionable(ACTION_TOGGLEVISUALISATION, Actions))
4014  emit EnableVisualiser(false, true);
4015  else if (IsActionable(ACTION_ENABLEVISUALISATION, Actions))
4016  emit EnableVisualiser(true);
4017  else if (IsActionable(ACTION_DISABLEVISUALISATION, Actions))
4018  emit EnableVisualiser(false);
4019  else if (IsActionable("TOGGLEPICCONTROLS", Actions))
4021  else if (IsActionable("TOGGLESTRETCH", Actions))
4023  else if (IsActionable(ACTION_TOGGLEUPMIX, Actions))
4024  emit ChangeUpmix(false, true);
4025  else if (IsActionable(ACTION_TOGGLESLEEP, Actions))
4026  ToggleSleepTimer();
4027  else if (IsActionable(ACTION_TOGGLERECORD, Actions) && islivetv)
4028  QuickRecord();
4029  else if (IsActionable(ACTION_TOGGLEFAV, Actions) && islivetv)
4031  else if (IsActionable(ACTION_TOGGLECHANCONTROLS, Actions) && islivetv)
4033  else if (IsActionable(ACTION_TOGGLERECCONTROLS, Actions) && islivetv)
4035  else if (IsActionable("TOGGLEBROWSE", Actions))
4036  {
4037  if (islivetv)
4038  BrowseStart();
4039  else if (!IsDVD)
4040  ShowOSDMenu();
4041  else
4042  handled = false;
4043  }
4044  else if (IsActionable("EDIT", Actions))
4045  {
4046  if (islivetv)
4048  else if (!IsDVD)
4050  }
4051  else if (IsActionable(ACTION_OSDNAVIGATION, Actions))
4052  {
4054  }
4055  else
4056  {
4057  handled = false;
4058  }
4059 
4060  return handled;
4061 }
4062 
4064 {
4065  if (Clear)
4066  {
4067  emit UpdateBookmark(true);
4068  emit ChangeOSDMessage(tr("Bookmark Cleared"));
4069  }
4070  else // if (IsBookmarkAllowed(ctx))
4071  {
4072  emit UpdateBookmark();
4073  osdInfo info;
4075  info.text["title"] = tr("Position");
4077  emit ChangeOSDMessage(tr("Bookmark Saved"));
4078  }
4079 }
4080 
4081 bool TV::ActivePostQHandleAction(const QStringList &Actions)
4082 {
4083  bool handled = true;
4084  TVState state = GetState();
4085  bool islivetv = StateIsLiveTV(state);
4086  bool isdvd = state == kState_WatchingDVD;
4087  bool isdisc = isdvd || state == kState_WatchingBD;
4088 
4089  if (IsActionable(ACTION_SETBOOKMARK, Actions))
4090  {
4091  if (!CommitQueuedInput())
4092  {
4093  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4094  SetBookmark(false);
4095  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4096  }
4097  }
4098  if (IsActionable(ACTION_TOGGLEBOOKMARK, Actions))
4099  {
4100  if (!CommitQueuedInput())
4101  {
4102  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4103  SetBookmark(m_player->GetBookmark() != 0U);
4104  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4105  }
4106  }
4107  else if (IsActionable("NEXTFAV", Actions) && islivetv)
4109  else if (IsActionable("NEXTSOURCE", Actions) && islivetv)
4111  else if (IsActionable("PREVSOURCE", Actions) && islivetv)
4113  else if (IsActionable("NEXTINPUT", Actions) && islivetv)
4114  SwitchInputs();
4115  else if (IsActionable(ACTION_GUIDE, Actions))
4117  else if (IsActionable("PREVCHAN", Actions) && islivetv)
4118  PopPreviousChannel(false);
4119  else if (IsActionable(ACTION_CHANNELUP, Actions))
4120  {
4121  if (islivetv)
4122  {
4123  if (m_dbBrowseAlways)
4125  else
4127  }
4128  else
4129  DoJumpRWND();
4130  }
4131  else if (IsActionable(ACTION_CHANNELDOWN, Actions))
4132  {
4133  if (islivetv)
4134  {
4135  if (m_dbBrowseAlways)
4137  else
4139  }
4140  else
4141  DoJumpFFWD();
4142  }
4143  else if (IsActionable("DELETE", Actions) && !islivetv)
4144  {
4145  NormalSpeed();
4146  StopFFRew();
4147  SetBookmark();
4148  ShowOSDPromptDeleteRecording(tr("Are you sure you want to delete:"));
4149  }
4150  else if (IsActionable(ACTION_JUMPTODVDROOTMENU, Actions) && isdisc)
4151  emit GoToMenu("root");
4152  else if (IsActionable(ACTION_JUMPTODVDCHAPTERMENU, Actions) && isdisc)
4153  emit GoToMenu("chapter");
4154  else if (IsActionable(ACTION_JUMPTODVDTITLEMENU, Actions) && isdisc)
4155  emit GoToMenu("title");
4156  else if (IsActionable(ACTION_JUMPTOPOPUPMENU, Actions) && isdisc)
4157  emit GoToMenu("popup");
4158  else if (IsActionable(ACTION_FINDER, Actions))
4160  else
4161  handled = false;
4162 
4163  return handled;
4164 }
4165 
4166 
4168 {
4169  bool ignoreKeys = m_playerContext.IsPlayerChangingBuffers();
4170 
4171 #ifdef DEBUG_ACTIONS
4172  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("(%1) ignoreKeys: %2").arg(Command).arg(ignoreKeys));
4173 #endif
4174 
4175  if (ignoreKeys)
4176  {
4177  LOG(VB_GENERAL, LOG_WARNING, LOC + "Ignoring network control command because ignoreKeys is set");
4178  return;
4179  }
4180 
4181 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
4182  QStringList tokens = Command.split(" ", QString::SkipEmptyParts);
4183 #else
4184  QStringList tokens = Command.split(" ", Qt::SkipEmptyParts);
4185 #endif
4186  if (tokens.size() < 2)
4187  {
4188  LOG(VB_GENERAL, LOG_ERR, LOC + "Not enough tokens in network control command " + QString("'%1'").arg(Command));
4189  return;
4190  }
4191 
4192  OSD *osd = GetOSDL();
4193  bool dlg = false;
4194  if (osd)
4195  dlg = osd->DialogVisible();
4196  ReturnOSDLock();
4197 
4198  if (dlg)
4199  {
4200  LOG(VB_GENERAL, LOG_WARNING, LOC +
4201  "Ignoring network control command\n\t\t\t" +
4202  QString("because dialog is waiting for a response"));
4203  return;
4204  }
4205 
4206  if (tokens[1] != "QUERY")
4207  ClearOSD();
4208 
4209  if (tokens.size() == 3 && tokens[1] == "CHANID")
4210  {
4211  m_queuedChanID = tokens[2].toUInt();
4212  m_queuedChanNum.clear();
4214  }
4215  else if (tokens.size() == 3 && tokens[1] == "CHANNEL")
4216  {
4217  if (StateIsLiveTV(GetState()))
4218  {
4219  if (tokens[2] == "UP")
4221  else if (tokens[2] == "DOWN")
4223  else if (tokens[2].contains(QRegularExpression(R"(^[-\.\d_#]+$)")))
4224  ChangeChannel(0, tokens[2]);
4225  }
4226  }
4227  else if (tokens.size() == 3 && tokens[1] == "SPEED")
4228  {
4229  bool paused = ContextIsPaused(__FILE__, __LINE__);
4230 
4231  if (tokens[2] == "0x")
4232  {
4233  NormalSpeed();
4234  StopFFRew();
4235  if (!paused)
4236  DoTogglePause(true);
4237  }
4238  else if (tokens[2] == "normal")
4239  {
4240  NormalSpeed();
4241  StopFFRew();
4242  if (paused)
4243  DoTogglePause(true);
4244  return;
4245  }
4246  else
4247  {
4248  float tmpSpeed = 1.0F;
4249  bool ok = false;
4250 
4251  if (tokens[2].contains(QRegularExpression(R"(^\-*(\d*\.)?\d+x$)")))
4252  {
4253  QString speed = tokens[2].left(tokens[2].length()-1);
4254  tmpSpeed = speed.toFloat(&ok);
4255  }
4256  else
4257  {
4258  QRegularExpression re { R"(^(\-*\d+)\/(\d+)x$)" };
4259  auto match = re.match(tokens[2]);
4260  if (match.hasMatch())
4261  {
4262  QStringList matches = match.capturedTexts();
4263  int numerator = matches[1].toInt(&ok);
4264  int denominator = matches[2].toInt(&ok);
4265 
4266  if (ok && denominator != 0)
4267  tmpSpeed = static_cast<float>(numerator) / static_cast<float>(denominator);
4268  else
4269  ok = false;
4270  }
4271  }
4272 
4273  if (ok)
4274  {
4275  float searchSpeed = fabs(tmpSpeed);
4276 
4277  if (paused)
4278  DoTogglePause(true);
4279 
4280  if (tmpSpeed == 0.0F)
4281  {
4282  NormalSpeed();
4283  StopFFRew();
4284 
4285  if (!paused)
4286  DoTogglePause(true);
4287  }
4288  else if (tmpSpeed == 1.0F)
4289  {
4290  StopFFRew();
4291  m_playerContext.m_tsNormal = 1.0F;
4292  ChangeTimeStretch(0, false);
4293  return;
4294  }
4295 
4296  NormalSpeed();
4297 
4298  size_t index = 0;
4299  for ( ; index < m_ffRewSpeeds.size(); index++)
4300  if (m_ffRewSpeeds[index] == static_cast<int>(searchSpeed))
4301  break;
4302 
4303  if ((index < m_ffRewSpeeds.size()) && (m_ffRewSpeeds[index] == static_cast<int>(searchSpeed)))
4304  {
4305  if (tmpSpeed < 0)
4307  else if (tmpSpeed > 1)
4309  else
4310  StopFFRew();
4311 
4313  SetFFRew(static_cast<int>(index));
4314  }
4315  else if (0.48F <= tmpSpeed && tmpSpeed <= 2.0F)
4316  {
4317  StopFFRew();
4318  m_playerContext.m_tsNormal = tmpSpeed; // alter speed before display
4319  ChangeTimeStretch(0, false);
4320  }
4321  else
4322  {
4323  LOG(VB_GENERAL, LOG_WARNING, QString("Couldn't find %1 speed. Setting Speed to 1x")
4324  .arg(static_cast<double>(searchSpeed)));
4327  }
4328  }
4329  else
4330  {
4331  LOG(VB_GENERAL, LOG_ERR, QString("Found an unknown speed of %1").arg(tokens[2]));
4332  }
4333  }
4334  }
4335  else if (tokens.size() == 2 && tokens[1] == "STOP")
4336  {
4337  SetBookmark();
4338  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4340  m_player->SetWatched();
4341  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4342  SetExitPlayer(true, true);
4343  }
4344  else if (tokens.size() >= 3 && tokens[1] == "SEEK" && m_playerContext.HasPlayer())
4345  {
4347  return;
4348 
4349  if (tokens[2] == "BEGINNING")
4350  {
4351  DoSeek(0, tr("Jump to Beginning"), /*timeIsOffset*/false, /*honorCutlist*/true);
4352  }
4353  else if (tokens[2] == "FORWARD")
4354  {
4355  DoSeek(m_playerContext.m_fftime, tr("Skip Ahead"), /*timeIsOffset*/true, /*honorCutlist*/true);
4356  }
4357  else if (tokens[2] == "BACKWARD")
4358  {
4359  DoSeek(-m_playerContext.m_rewtime, tr("Skip Back"), /*timeIsOffset*/true, /*honorCutlist*/true);
4360  }
4361  else if ((tokens[2] == "POSITION" ||
4362  tokens[2] == "POSITIONWITHCUTLIST") &&
4363  (tokens.size() == 4) &&
4364  (tokens[3].contains(QRegularExpression("^\\d+$"))))
4365  {
4366  DoSeekAbsolute(tokens[3].toInt(), tokens[2] == "POSITIONWITHCUTLIST");
4367  }
4368  }
4369  else if (tokens.size() >= 3 && tokens[1] == "SUBTITLES")
4370  {
4371  bool ok = false;
4372  uint track = tokens[2].toUInt(&ok);
4373 
4374  if (!ok)
4375  return;
4376 
4377  if (track == 0)
4378  {
4379  emit SetCaptionsEnabled(false, true);
4380  }
4381  else
4382  {
4383  QStringList subs = m_player->GetTracks(kTrackTypeSubtitle);
4384  uint size = static_cast<uint>(subs.size());
4385  uint start = 1;
4386  uint finish = start + size;
4387  if (track >= start && track < finish)
4388  {
4389  emit SetTrack(kTrackTypeSubtitle, track - start);
4391  return;
4392  }
4393 
4394  start = finish + 1;
4396  finish = start + size;
4397  if (track >= start && track < finish)
4398  {
4399  emit SetTrack(kTrackTypeCC708, track - start);
4401  return;
4402  }
4403 
4404  start = finish + 1;
4406  finish = start + size;
4407  if (track >= start && track < finish)
4408  {
4409  emit SetTrack(kTrackTypeCC608, track - start);
4411  return;
4412  }
4413 
4414  start = finish + 1;
4416  finish = start + size;
4417  if (track >= start && track < finish)
4418  {
4419  emit SetTrack(kTrackTypeTeletextCaptions, track - start);
4421  return;
4422  }
4423 
4424  start = finish + 1;
4426  finish = start + size;
4427  if (track >= start && track < finish)
4428  {
4429  emit SetTrack(kTrackTypeTeletextMenu, track - start);
4431  return;
4432  }
4433 
4434  start = finish + 1;
4436  finish = start + size;
4437  if (track >= start && track < finish)
4438  {
4439  emit SetTrack(kTrackTypeRawText, track - start);
4441  return;
4442  }
4443  }
4444  }
4445  else if (tokens.size() >= 3 && tokens[1] == "VOLUME")
4446  {
4447  QRegularExpression re { "(\\d+)%?" };
4448  auto match = re.match(tokens[2]);
4449  if (match.hasMatch())
4450  {
4451  QStringList matches = match.capturedTexts();
4452 
4453  LOG(VB_GENERAL, LOG_INFO, QString("Set Volume to %1%").arg(matches[1]));
4454 
4455  bool ok = false;
4456  int vol = matches[1].toInt(&ok);
4457  if (!ok)
4458  return;
4459 
4460  if (0 <= vol && vol <= 100)
4461  emit ChangeVolume(true, vol);
4462  }
4463  }
4464  else if (tokens.size() >= 3 && tokens[1] == "QUERY")
4465  {
4466  if (tokens[2] == "POSITION")
4467  {
4468  if (!m_player)
4469  return;
4470  QString speedStr;
4471  if (ContextIsPaused(__FILE__, __LINE__))
4472  {
4473  speedStr = "pause";
4474  }
4475  else if (m_playerContext.m_ffRewState)
4476  {
4477  speedStr = QString("%1x").arg(m_playerContext.m_ffRewSpeed);
4478  }
4479  else
4480  {
4481  QRegularExpression re { "Play (.*)x" };
4482  auto match = re.match(m_playerContext.GetPlayMessage());
4483  if (match.hasMatch())
4484  {
4485  QStringList matches = match.capturedTexts();
4486  speedStr = QString("%1x").arg(matches[1]);
4487  }
4488  else
4489  {
4490  speedStr = "1x";
4491  }
4492  }
4493 
4494  osdInfo info;
4495  CalcPlayerSliderPosition(info, true);
4496 
4497  QDateTime respDate = MythDate::current(true);
4498  QString infoStr = "";
4499 
4500  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4501  uint64_t fplay = 0;
4502  double rate = 30.0;
4503  if (m_player)
4504  {
4505  fplay = m_player->GetFramesPlayed();
4506  rate = static_cast<double>(m_player->GetFrameRate()); // for display only
4507  }
4508  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4509 
4510  m_playerContext.LockPlayingInfo(__FILE__, __LINE__);
4512  {
4513  infoStr = "LiveTV";
4516  }
4517  else
4518  {
4520  infoStr = "DVD";
4522  infoStr = "Recorded";
4523  else
4524  infoStr = "Video";
4525 
4528  }
4529 
4530  QString bufferFilename =
4531  m_playerContext.m_buffer ? m_playerContext.m_buffer->GetFilename() : QString("no buffer");
4532  if ((infoStr == "Recorded") || (infoStr == "LiveTV"))
4533  {
4534  infoStr += QString(" %1 %2 %3 %4 %5 %6 %7")
4535  .arg(info.text["description"])
4536  .arg(speedStr)
4537  .arg(m_playerContext.m_playingInfo != nullptr ?
4539  .arg(respDate.toString(Qt::ISODate))
4540  .arg(fplay)
4541  .arg(bufferFilename)
4542  .arg(rate);
4543  }
4544  else
4545  {
4546  QString position = info.text["description"].section(" ",0,0);
4547  infoStr += QString(" %1 %2 %3 %4 %5")
4548  .arg(position)
4549  .arg(speedStr)
4550  .arg(bufferFilename)
4551  .arg(fplay)
4552  .arg(rate);
4553  }
4554 
4555  infoStr += QString(" Subtitles:");
4556 
4558 
4559  if (subtype == kDisplayNone)
4560  infoStr += QString(" *0:[None]*");
4561  else
4562  infoStr += QString(" 0:[None]");
4563 
4564  uint n = 1;
4565 
4566  QStringList subs = m_player->GetTracks(kTrackTypeSubtitle);
4567  for (int i = 0; i < subs.size(); i++)
4568  {
4569  if ((subtype & kDisplayAVSubtitle) && (m_player->GetTrack(kTrackTypeSubtitle) == i))
4570  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4571  else
4572  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4573  n++;
4574  }
4575 
4577  for (int i = 0; i < subs.size(); i++)
4578  {
4579  if ((subtype & kDisplayCC708) && (m_player->GetTrack(kTrackTypeCC708) == i))
4580  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4581  else
4582  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4583  n++;
4584  }
4585 
4587  for (int i = 0; i < subs.size(); i++)
4588  {
4589  if ((subtype & kDisplayCC608) && (m_player->GetTrack(kTrackTypeCC608) == i))
4590  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4591  else
4592  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4593  n++;
4594  }
4595 
4597  for (int i = 0; i < subs.size(); i++)
4598  {
4600  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4601  else
4602  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4603  n++;
4604  }
4605 
4607  for (int i = 0; i < subs.size(); i++)
4608  {
4610  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4611  else
4612  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4613  n++;
4614  }
4615 
4617  for (int i = 0; i < subs.size(); i++)
4618  {
4619  if ((subtype & kDisplayRawTextSubtitle) && m_player->GetTrack(kTrackTypeRawText) == i)
4620  infoStr += QString(" *%1:[%2]*").arg(n).arg(subs[i]);
4621  else
4622  infoStr += QString(" %1:[%2]").arg(n).arg(subs[i]);
4623  n++;
4624  }
4625 
4626  m_playerContext.UnlockPlayingInfo(__FILE__, __LINE__);
4627 
4628  QString message = QString("NETWORK_CONTROL ANSWER %1").arg(infoStr);
4629  MythEvent me(message);
4630  gCoreContext->dispatch(me);
4631  }
4632  else if (tokens[2] == "VOLUME")
4633  {
4634  QString infoStr = QString("%1%").arg(m_audioState.m_volume);
4635  QString message = QString("NETWORK_CONTROL ANSWER %1").arg(infoStr);
4636  MythEvent me(message);
4637  gCoreContext->dispatch(me);
4638  }
4639  }
4640 }
4641 
4642 bool TV::StartPlayer(TVState desiredState)
4643 {
4644  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1) -- begin").arg(StateToString(desiredState)));
4645 
4646  bool ok = CreatePlayer(desiredState);
4648 
4649  if (ok)
4650  {
4651  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created player."));
4652  SetSpeedChangeTimer(25ms, __LINE__);
4653  }
4654  else
4655  {
4656  LOG(VB_GENERAL, LOG_CRIT, LOC + QString("Failed to create player."));
4657  }
4658 
4659  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("(%1) -- end %2")
4660  .arg(StateToString(desiredState)).arg((ok) ? "ok" : "error"));
4661 
4662  return ok;
4663 }
4664 
4666 {
4667  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4668  if (!m_player)
4669  {
4670  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4671  return;
4672  }
4673 
4674  float time = 0.0;
4675 
4677  m_player->IsPaused())
4678  {
4680  time = StopFFRew();
4681  else if (m_player->IsPaused())
4683 
4687  }
4688  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4689 
4690  DoPlayerSeek(time);
4692 
4694 
4695  SetSpeedChangeTimer(0ms, __LINE__);
4697 }
4698 
4700 {
4701 
4703  return 0.0F;
4704 
4706  float time = 0.0F;
4707 
4708  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4709  if (!m_player)
4710  {
4711  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4712  return 0.0F;
4713  }
4714  if (m_player->IsPaused())
4715  {
4717  }
4718  else
4719  {
4721  time = StopFFRew();
4722  m_player->Pause();
4723  }
4724  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4725  return time;
4726 }
4727 
4728 void TV::DoTogglePauseFinish(float Time, bool ShowOSD)
4729 {
4730  if (!m_playerContext.HasPlayer())
4731  return;
4732 
4734  return;
4735 
4736  if (ContextIsPaused(__FILE__, __LINE__))
4737  {
4740 
4741  DoPlayerSeek(Time);
4742  if (ShowOSD)
4743  UpdateOSDSeekMessage(tr("Paused"), kOSDTimeout_None);
4745  }
4746  else
4747  {
4748  DoPlayerSeek(Time);
4749  if (ShowOSD)
4752  }
4753 
4754  SetSpeedChangeTimer(0ms, __LINE__);
4755 }
4756 
4764 {
4765  bool paused = false;
4766  int dummy = 0;
4767  TV* tv = AcquireRelease(dummy, true);
4768  if (tv)
4769  {
4770  tv->GetPlayerReadLock();
4771  PlayerContext* context = tv->GetPlayerContext();
4772  if (!context->IsErrored())
4773  {
4774  context->LockDeletePlayer(__FILE__, __LINE__);
4775  if (context->m_player)
4776  paused = context->m_player->IsPaused();
4777  context->UnlockDeletePlayer(__FILE__, __LINE__);
4778  }
4779  tv->ReturnPlayerLock();
4780  AcquireRelease(dummy, false);
4781  }
4782  return paused;
4783 }
4784 
4785 void TV::DoTogglePause(bool ShowOSD)
4786 {
4787  bool ignore = false;
4788  bool paused = false;
4789  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4790  if (m_player)
4791  {
4792  ignore = m_player->GetEditMode();
4793  paused = m_player->IsPaused();
4794  }
4795  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4796 
4797  if (paused)
4799  else
4801 
4802  if (!ignore)
4804  // Emit Pause or Unpaused signal
4806 }
4807 
4808 bool TV::DoPlayerSeek(float Time)
4809 {
4811  return false;
4812 
4813  if (Time > -0.001F && Time < +0.001F)
4814  return false;
4815 
4816  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1 seconds").arg(static_cast<double>(Time)));
4817 
4818  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4819  if (!m_player)
4820  {
4821  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4822  return false;
4823  }
4824 
4826  {
4827  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4828  return false;
4829  }
4830 
4831  emit PauseAudioUntilReady();
4832 
4833  bool res = false;
4834 
4835  if (Time > 0.0F)
4836  res = m_player->FastForward(Time);
4837  else if (Time < 0.0F)
4838  res = m_player->Rewind(-Time);
4839  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4840 
4841  return res;
4842 }
4843 
4844 bool TV::DoPlayerSeekToFrame(uint64_t FrameNum)
4845 {
4847  return false;
4848 
4849  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1").arg(FrameNum));
4850 
4851  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4852  if (!m_player)
4853  {
4854  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4855  return false;
4856  }
4857 
4859  {
4860  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4861  return false;
4862  }
4863 
4864  emit PauseAudioUntilReady();
4865 
4866  bool res = m_player->JumpToFrame(FrameNum);
4867 
4868  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4869 
4870  return res;
4871 }
4872 
4873 bool TV::SeekHandleAction(const QStringList& Actions, const bool IsDVD)
4874 {
4875  const int kRewind = 4;
4876  const int kForward = 8;
4877  const int kSticky = 16;
4878  const int kSlippery = 32;
4879  const int kRelative = 64;
4880  const int kAbsolute = 128;
4881  const int kIgnoreCutlist = 256;
4882  const int kWhenceMask = 3;
4883  int flags = 0;
4884  if (IsActionable(ACTION_SEEKFFWD, Actions))
4885  flags = ARBSEEK_FORWARD | kForward | kSlippery | kRelative;
4886  else if (IsActionable("FFWDSTICKY", Actions))
4887  flags = ARBSEEK_END | kForward | kSticky | kAbsolute;
4888  else if (IsActionable(ACTION_RIGHT, Actions))
4889  flags = ARBSEEK_FORWARD | kForward | kSticky | kRelative;
4890  else if (IsActionable(ACTION_SEEKRWND, Actions))
4891  flags = ARBSEEK_REWIND | kRewind | kSlippery | kRelative;
4892  else if (IsActionable("RWNDSTICKY", Actions))
4893  flags = ARBSEEK_SET | kRewind | kSticky | kAbsolute;
4894  else if (IsActionable(ACTION_LEFT, Actions))
4895  flags = ARBSEEK_REWIND | kRewind | kSticky | kRelative;
4896  else
4897  return false;
4898 
4899  int direction = (flags & kRewind) ? -1 : 1;
4900  if (HasQueuedInput())
4901  {
4902  DoArbSeek(static_cast<ArbSeekWhence>(flags & kWhenceMask), (flags & kIgnoreCutlist) == 0);
4903  }
4904  else if (ContextIsPaused(__FILE__, __LINE__))
4905  {
4906  if (!IsDVD)
4907  {
4908  QString message = (flags & kRewind) ? tr("Rewind") :
4909  tr("Forward");
4910  if (flags & kAbsolute) // FFWDSTICKY/RWNDSTICKY
4911  {
4912  float time = direction;
4913  DoSeek(time, message, /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
4914  }
4915  else
4916  {
4917  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4918  uint64_t frameAbs = m_player->GetFramesPlayed();
4919  uint64_t frameRel = m_player->TranslatePositionAbsToRel(frameAbs);
4920  uint64_t targetRel = frameRel + static_cast<uint64_t>(direction);
4921  if (frameRel == 0 && direction < 0)
4922  targetRel = 0;
4923  uint64_t maxAbs = m_player->GetCurrentFrameCount();
4924  uint64_t maxRel = m_player->TranslatePositionAbsToRel(maxAbs);
4925  if (targetRel > maxRel)
4926  targetRel = maxRel;
4927  uint64_t targetAbs = m_player->TranslatePositionRelToAbs(targetRel);
4928  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4929  DoPlayerSeekToFrame(targetAbs);
4931  }
4932  }
4933  }
4934  else if (flags & kSticky)
4935  {
4936  ChangeFFRew(direction);
4937  }
4938  else if (flags & kRewind)
4939  {
4940  if (m_smartForward)
4941  m_doSmartForward = true;
4942  DoSeek(-m_playerContext.m_rewtime, tr("Skip Back"), /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
4943  }
4944  else
4945  {
4947  {
4948  DoSeek(m_playerContext.m_rewtime, tr("Skip Ahead"), /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
4949  }
4950  else
4951  {
4952  DoSeek(m_playerContext.m_fftime, tr("Skip Ahead"), /*timeIsOffset*/true, /*honorCutlist*/(flags & kIgnoreCutlist) == 0);
4953  }
4954  }
4955  return true;
4956 }
4957 
4958 void TV::DoSeek(float Time, const QString &Msg, bool TimeIsOffset, bool HonorCutlist)
4959 {
4960  if (!m_player)
4961  return;
4962 
4963  bool limitkeys = false;
4964 
4965  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4966  if (m_player->GetLimitKeyRepeat())
4967  limitkeys = true;
4968 
4969  if (!limitkeys || (m_keyRepeatTimer.elapsed() > kKeyRepeatTimeout))
4970  {
4972  NormalSpeed();
4973  Time += StopFFRew();
4974  if (TimeIsOffset)
4975  {
4976  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4977  DoPlayerSeek(Time);
4978  }
4979  else
4980  {
4981  auto time = millisecondsFromFloat(Time * 1000);
4982  uint64_t desiredFrameRel = m_player->TranslatePositionMsToFrame(time, HonorCutlist);
4983  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4984  DoPlayerSeekToFrame(desiredFrameRel);
4985  }
4986  bool paused = m_player->IsPaused();
4988  }
4989  else
4990  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
4991 }
4992 
4993 void TV::DoSeekAbsolute(long long Seconds, bool HonorCutlist)
4994 {
4995  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
4996  if (!m_player)
4997  {
4998  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5000  return;
5001  }
5002  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5003  DoSeek(Seconds, tr("Jump To"), /*timeIsOffset*/false, HonorCutlist);
5005 }
5006 
5007 void TV::DoArbSeek(ArbSeekWhence Whence, bool HonorCutlist)
5008 {
5009  bool ok = false;
5010  int seek = GetQueuedInputAsInt(&ok);
5011  ClearInputQueues(true);
5012  if (!ok)
5013  return;
5014 
5015  int64_t time = (int(seek / 100) * 3600) + ((seek % 100) * 60);
5016 
5017  if (Whence == ARBSEEK_FORWARD)
5018  {
5019  DoSeek(time, tr("Jump Ahead"), /*timeIsOffset*/true, HonorCutlist);
5020  }
5021  else if (Whence == ARBSEEK_REWIND)
5022  {
5023  DoSeek(-time, tr("Jump Back"), /*timeIsOffset*/true, HonorCutlist);
5024  }
5025  else if (Whence == ARBSEEK_END)
5026  {
5027  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5028  if (!m_player)
5029  {
5030  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5031  return;
5032  }
5033  uint64_t total_frames = m_player->GetCurrentFrameCount();
5034  float dur = m_player->ComputeSecs(total_frames, HonorCutlist);
5035  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5036  DoSeek(std::max(0.0F, dur - static_cast<float>(time)), tr("Jump To"), /*timeIsOffset*/false, HonorCutlist);
5037  }
5038  else
5039  {
5040  DoSeekAbsolute(time, HonorCutlist);
5041  }
5042 }
5043 
5045 {
5047  return;
5048 
5050 
5051  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5052  if (m_player)
5054  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5055 
5056  SetSpeedChangeTimer(0ms, __LINE__);
5057 }
5058 
5059 void TV::ChangeSpeed(int Direction)
5060 {
5061  int old_speed = m_playerContext.m_ffRewSpeed;
5062 
5063  if (ContextIsPaused(__FILE__, __LINE__))
5065 
5066  m_playerContext.m_ffRewSpeed += Direction;
5067 
5068  float time = StopFFRew();
5069  float speed = NAN;
5070  QString mesg;
5071 
5072  switch (m_playerContext.m_ffRewSpeed)
5073  {
5074  case 4: speed = 16.0F; mesg = tr("Speed 16X"); break;
5075  case 3: speed = 8.0F; mesg = tr("Speed 8X"); break;
5076  case 2: speed = 3.0F; mesg = tr("Speed 3X"); break;
5077  case 1: speed = 2.0F; mesg = tr("Speed 2X"); break;
5078  case 0: speed = 1.0F; mesg = m_playerContext.GetPlayMessage(); break;
5079  case -1: speed = 1.0F / 3; mesg = tr("Speed 1/3X"); break;
5080  case -2: speed = 1.0F / 8; mesg = tr("Speed 1/8X"); break;
5081  case -3: speed = 1.0F / 16; mesg = tr("Speed 1/16X"); break;
5082  case -4:
5083  DoTogglePause(true);
5084  return;
5085  default:
5086  m_playerContext.m_ffRewSpeed = old_speed;
5087  return;
5088  }
5089 
5090  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5091  if (m_player && !m_player->Play(
5093  {
5094  m_playerContext.m_ffRewSpeed = old_speed;
5095  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5096  return;
5097  }
5098  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5099  DoPlayerSeek(time);
5101 
5102  SetSpeedChangeTimer(0ms, __LINE__);
5103 }
5104 
5106 {
5107  float time = 0.0;
5108 
5110  return time;
5111 
5112  if (m_playerContext.m_ffRewState > 0)
5113  time = -m_ffRewSpeeds[static_cast<size_t>(m_playerContext.m_ffRewIndex)] * m_ffRewRepos;
5114  else
5115  time = m_ffRewSpeeds[static_cast<size_t>(m_playerContext.m_ffRewIndex)] * m_ffRewRepos;
5116 
5119 
5120  m_playerContext.LockDeletePlayer(__FILE__, __LINE__);
5121  if (m_player)
5123  m_playerContext.UnlockDeletePlayer(__FILE__, __LINE__);
5124 
5125  SetSpeedChangeTimer(0ms, __LINE__);
5126 
5127  return time;
5128 }
5129 
5130 void TV::ChangeFFRew(int Direction)
5131 {
5132  if (m_playerContext.m_ffRewState == Direction)
5133  {
5134  while (++m_playerContext.m_ffRewIndex < static_cast<int>(m_ffRewSpeeds.size()))
5135  if (m_ffRewSpeeds[static_cast<size_t>(m_playerContext.m_ffRewIndex)])
5136  break;
5137  if (m_playerContext.m_ffRewIndex >= static_cast<int>(m_ffRewSpeeds.size()))
5140  }
5141  else if (!m_ffRewReverse && m_playerContext.m_ffRewState == -Direction)
5142  {
5144  if (m_ffRewSpeeds[static_cast<size_t>(m_playerContext.m_ffRewIndex)])
5145  break;
5148  else
5149  {
5150  float time = StopFFRew();
5151  DoPlayerSeek(time);
5153  }
5154  }
5155  else
5156  {
5157  NormalSpeed();
5158  m_playerContext.m_ffRewState = Direction;
5160  }
5161 }
5162 
5163 void TV::SetFFRew(int Index)
5164 {
5166  return;
5167 
5168  auto index = static_cast<size_t>(Index);
5169  if (!m_ffRewSpeeds[index])
5170  return;
5171 
5172  auto ffrewindex = static_cast<size_t>(m_playerContext.m_ffRewIndex);
5173  int speed = 0;
5174  QString mesg;
5175  if (m_playerContext.m_ffRewState > 0)
5176  {
5177  speed = m_ffRewSpeeds[index];
5178  // Don't allow ffwd if seeking is needed but not available
5180  return;
5181 
5182  m_playerContext.m_ffRewIndex = Index;
5183  mesg = tr("Forward %1X").arg(m_ffRewSpeeds[ffrewindex]);
5184  m_playerContext.m_ffRewSpeed = speed;
5185  }
5186  else
5187  {
5188  // Don't rewind if we cannot seek
5190  return;
5191 
5192  m_playerContext.m_ffRewIndex = Index;
5193  mesg = tr("Rewind %1X").arg(m_ffRewSpeeds[ffrewindex]);
5194  speed = -m_ffRewSpeeds[ffrewindex];
5195  m_playerContext.m_ffRewSpeed = speed;
5196  }