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