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