MythTV  master
tvbrowsehelper.cpp
Go to the documentation of this file.
1 
2 #include "mythcorecontext.h"
3 
4 #include <QCoreApplication>
5 
6 #include "tvbrowsehelper.h"
7 #include "playercontext.h"
8 #include "remoteencoder.h"
9 #include "recordinginfo.h"
10 #include "channelutil.h"
11 #include "mythlogging.h"
12 #include "cardutil.h"
13 #include "tv_play.h"
14 
15 #define LOC QString("BH: ")
16 
17 #define GetPlayer(X,Y) GetPlayerHaveLock(X, Y, __FILE__ , __LINE__)
18 #define GetOSDLock(X) GetOSDL(X, __FILE__, __LINE__)
19 
20 static void format_time(int seconds, QString &tMin, QString &tHrsMin)
21 {
22  int minutes = seconds / 60;
23  int hours = minutes / 60;
24  int min = minutes % 60;
25 
26  tMin = TV::tr("%n minute(s)", "", minutes);
27  tHrsMin = QString("%1:%2").arg(hours).arg(min, 2, 10, QChar('0'));
28 }
29 
31  TV *tv,
32  uint browse_max_forward,
33  bool browse_all_tuners,
34  bool use_channel_groups,
35  const QString& db_channel_ordering) :
36  MThread("TVBrowseHelper"),
37  m_tv(tv),
38  m_dbBrowseMaxForward(browse_max_forward),
39  m_dbBrowseAllTuners(browse_all_tuners),
40  m_dbUseChannelGroups(use_channel_groups)
41 {
43  0, true, "channum, callsign");
45  m_dbAllChannels, db_channel_ordering, false);
46 
47  for (const auto & chan : m_dbAllChannels)
48  {
49  m_dbChanidToChannum[chan.m_chanId] = chan.m_chanNum;
50  m_dbChanidToSourceid[chan.m_chanId] = chan.m_sourceId;
51  m_dbChannumToChanids.insert(chan.m_chanNum,chan.m_chanId);
52  }
53 
55  0, true, "channum, callsign");
57  m_dbAllVisibleChannels, db_channel_ordering, false);
58 
59  start();
60 }
61 
62 
65 bool TVBrowseHelper::BrowseStart(PlayerContext *ctx, bool skip_browse)
66 {
67  if (!gCoreContext->IsUIThread())
68  return false;
69 
70  QMutexLocker locker(&m_lock);
71 
72  if (m_ctx)
73  return m_ctx == ctx;
74 
75  m_tv->ClearOSD(ctx);
76 
77  ctx->LockPlayingInfo(__FILE__, __LINE__);
78  if (ctx->m_playingInfo)
79  {
80  m_ctx = ctx;
84  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
85 
86  if (!skip_browse)
87  {
89  locker.unlock();
90  BrowseDispInfo(ctx, bi);
91  }
92  return true;
93  }
94  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
95  return false;
96 }
97 
104 void TVBrowseHelper::BrowseEnd(PlayerContext *ctx, bool change_channel)
105 {
106  if (!gCoreContext->IsUIThread())
107  return;
108 
109  QMutexLocker locker(&m_lock);
110 
111  if (ctx && m_ctx != ctx)
112  return;
113 
114  if (!m_ctx)
115  return;
116 
117  {
118  QMutexLocker locker2(&m_tv->m_timerIdLock);
119  if (m_tv->m_browseTimerId)
120  {
122  m_tv->m_browseTimerId = 0;
123  }
124  }
125 
126  m_list.clear();
127  m_wait.wakeAll();
128 
129  OSD *osd = m_tv->GetOSDLock(ctx);
130  if (osd)
131  osd->HideWindow("browse_info");
132  m_tv->ReturnOSDLock(ctx, osd);
133 
134  if (change_channel)
135  m_tv->ChangeChannel(ctx, 0, m_chanNum);
136 
137  m_ctx = nullptr;
138 }
139 
141 {
142  if (!gCoreContext->IsUIThread())
143  return;
144 
145  if (!BrowseStart(ctx, true))
146  return;
147 
148  {
149  QMutexLocker locker(&m_tv->m_timerIdLock);
150  if (m_tv->m_browseTimerId)
151  {
154  m_tv->StartTimer(TV::kBrowseTimeout, __LINE__);
155  }
156  }
157 
158  QMutexLocker locker(&m_lock);
159  if (BROWSE_SAME == bi.m_dir)
160  m_list.clear();
161  m_list.push_back(bi);
162  m_wait.wakeAll();
163 }
164 
165 void TVBrowseHelper::BrowseChannel(PlayerContext *ctx, const QString &channum)
166 {
167  if (!gCoreContext->IsUIThread())
168  return;
169 
171  {
172  BrowseInfo bi(channum, 0);
173  BrowseDispInfo(ctx, bi);
174  return;
175  }
176 
177  if (!ctx->m_recorder || !ctx->m_lastCardid)
178  return;
179 
180  uint inputid = ctx->m_lastCardid;
181  uint sourceid = CardUtil::GetSourceID(inputid);
182  if (sourceid)
183  {
184  BrowseInfo bi(channum, sourceid);
185  BrowseDispInfo(ctx, bi);
186  }
187 }
188 
190 {
191  QMutexLocker locker(&m_lock);
193  if (m_ctx != nullptr)
194  {
195  bi.m_chanNum = m_chanNum;
196  bi.m_chanId = m_chanId;
198  }
199  return bi;
200 }
201 
206 {
207  if (!gCoreContext->IsUIThread())
208  return true;
209 
210  return m_ctx != nullptr;
211 }
212 
221  const QString &channum, uint pref_cardid, uint pref_sourceid) const
222 {
223  if (pref_sourceid)
224  {
225  for (const auto & chan : m_dbAllChannels)
226  {
227  if (chan.m_sourceId == pref_sourceid && chan.m_chanNum == channum)
228  return chan.m_chanId;
229  }
230  }
231 
232  if (pref_cardid)
233  {
234  for (const auto & chan : m_dbAllChannels)
235  {
236  if (chan.GetInputIds().contains(pref_cardid) &&
237  chan.m_chanNum == channum)
238  return chan.m_chanId;
239  }
240  }
241 
243  {
244  for (const auto & chan : m_dbAllChannels)
245  {
246  if (chan.m_chanNum == channum)
247  return chan.m_chanId;
248  }
249  }
250 
251  return 0;
252 }
253 
260  BrowseDirection direction, InfoMap &infoMap) const
261 {
262  if (!m_ctx || !m_ctx->m_recorder)
263  return;
264 
265  QString title;
266  QString subtitle;
267  QString desc;
268  QString category;
269  QString endtime;
270  QString callsign;
271  QString iconpath;
272  QDateTime begts;
273  QDateTime endts;
274 
275  QString starttime = infoMap["dbstarttime"];
276  QString chanid = infoMap["chanid"];
277  QString channum = infoMap["channum"];
278  QString seriesid = infoMap["seriesid"];
279  QString programid = infoMap["programid"];
280 
282  direction,
283  title, subtitle, desc, category,
284  starttime, endtime, callsign, iconpath,
285  channum, chanid, seriesid, programid);
286 
287  if (!starttime.isEmpty())
288  begts = MythDate::fromString(starttime);
289  else
290  begts = MythDate::fromString(infoMap["dbstarttime"]);
291 
292  infoMap["starttime"] = MythDate::toString(begts, MythDate::kTime);
293  infoMap["startdate"] = MythDate::toString(
295 
296  infoMap["endtime"] = infoMap["enddate"] = "";
297  if (!endtime.isEmpty())
298  {
299  endts = MythDate::fromString(endtime);
300  infoMap["endtime"] = MythDate::toString(endts, MythDate::kTime);
301  infoMap["enddate"] = MythDate::toString(
303  }
304 
305  infoMap["lenmins"] = TV::tr("%n minute(s)", "", 0);
306  infoMap["lentime"] = "0:00";
307  if (begts.isValid() && endts.isValid())
308  {
309  QString lenM;
310  QString lenHM;
311  format_time(begts.secsTo(endts), lenM, lenHM);
312  infoMap["lenmins"] = lenM;
313  infoMap["lentime"] = lenHM;
314  }
315 
316  infoMap["dbstarttime"] = starttime;
317  infoMap["dbendtime"] = endtime;
318  infoMap["title"] = title;
319  infoMap["subtitle"] = subtitle;
320  infoMap["description"] = desc;
321  infoMap["category"] = category;
322  infoMap["callsign"] = callsign;
323  infoMap["channum"] = channum;
324  infoMap["chanid"] = chanid;
325  infoMap["iconpath"] = iconpath;
326  infoMap["seriesid"] = seriesid;
327  infoMap["programid"] = programid;
328 }
329 
331  BrowseDirection direction, InfoMap &infoMap) const
332 {
333  uint chanid = infoMap["chanid"].toUInt();
334  if (!chanid)
335  {
336  LOG(VB_GENERAL, LOG_ERR, LOC + "GetNextProgramDB() requires a chanid");
337  return;
338  }
339 
340  int chandir = -1;
341  switch (direction)
342  {
343  case BROWSE_UP: chandir = CHANNEL_DIRECTION_UP; break;
344  case BROWSE_DOWN: chandir = CHANNEL_DIRECTION_DOWN; break;
345  case BROWSE_FAVORITE: chandir = CHANNEL_DIRECTION_FAVORITE; break;
346  case BROWSE_SAME:
347  case BROWSE_LEFT:
348  case BROWSE_RIGHT:
349  case BROWSE_INVALID:
350  default: break; // quiet -Wswitch-enum
351  }
352  if (chandir != -1)
353  {
355  m_dbAllVisibleChannels, chanid, 0 /*mplexid_restriction*/,
356  0 /* chanid restriction */,
357  static_cast<ChannelChangeDirection>(chandir),
358  true /*skip non visible*/, true /*skip same callsign*/);
359  }
360 
361  infoMap["chanid"] = QString::number(chanid);
362  infoMap["channum"] = m_dbChanidToChannum[chanid];
363 
364  QDateTime nowtime = MythDate::current();
365  QDateTime latesttime = nowtime.addSecs(6*60*60);
366  QDateTime browsetime = MythDate::fromString(infoMap["dbstarttime"]);
367 
368  MSqlBindings bindings;
369  bindings[":CHANID"] = chanid;
370  bindings[":NOWTS"] = nowtime;
371  bindings[":LATESTTS"] = latesttime;
372  bindings[":BROWSETS"] = browsetime;
373  bindings[":BROWSETS2"] = browsetime;
374 
375  QString querystr = " WHERE program.chanid = :CHANID ";
376  switch (direction)
377  {
378  case BROWSE_LEFT:
379  querystr += " AND program.endtime <= :BROWSETS "
380  " AND program.endtime > :NOWTS ";
381  break;
382 
383  case BROWSE_RIGHT:
384  querystr += " AND program.starttime > :BROWSETS "
385  " AND program.starttime < :LATESTTS ";
386  break;
387 
388  default:
389  querystr += " AND program.starttime <= :BROWSETS "
390  " AND program.endtime > :BROWSETS2 ";
391  };
392 
393  ProgramList progList;
394  ProgramList dummySched;
395  LoadFromProgram(progList, querystr, bindings, dummySched);
396 
397  if (progList.empty())
398  {
399  infoMap["dbstarttime"] = "";
400  return;
401  }
402 
403  const ProgramInfo *prog = (direction == BROWSE_LEFT) ?
404  progList[progList.size() - 1] : progList[0];
405 
406  infoMap["dbstarttime"] = prog->GetScheduledStartTime(MythDate::ISODate);
407 }
408 
410 {
411  RunProlog();
412  QMutexLocker locker(&m_lock);
413  while (true)
414  {
415  while (m_list.empty() && m_run)
416  m_wait.wait(&m_lock);
417 
418  if (!m_run)
419  break;
420 
421  BrowseInfo bi = m_list.front();
422  m_list.pop_front();
423 
424  PlayerContext *ctx = m_ctx;
425 
426  vector<uint> chanids;
427  if (BROWSE_SAME == bi.m_dir)
428  {
429  if (!bi.m_chanId)
430  {
431  vector<uint> chanids_extra;
432  uint sourceid = m_dbChanidToSourceid[m_chanId];
433  QMultiMap<QString,uint>::iterator it;
434  it = m_dbChannumToChanids.lowerBound(bi.m_chanNum);
435  for ( ; (it != m_dbChannumToChanids.end()) &&
436  (it.key() == bi.m_chanNum); ++it)
437  {
438  if (m_dbChanidToSourceid[*it] == sourceid)
439  chanids.push_back(*it);
440  else
441  chanids_extra.push_back(*it);
442  }
443  chanids.insert(chanids.end(),
444  chanids_extra.begin(),
445  chanids_extra.end());
446  }
447  m_chanNum = bi.m_chanNum;
448  m_chanId = (chanids.empty()) ? bi.m_chanId : chanids[0];
450  }
451 
452  BrowseDirection direction = bi.m_dir;
453 
454  QDateTime lasttime = MythDate::fromString(m_startTime);
455  QDateTime curtime = MythDate::current();
456  if (lasttime < curtime)
457  m_startTime = curtime.toString(Qt::ISODate);
458 
459  QDateTime maxtime = curtime.addSecs(m_dbBrowseMaxForward);
460  if ((lasttime > maxtime) && (direction == BROWSE_RIGHT))
461  continue;
462 
463  m_lock.unlock();
464 
465  // if browsing channel groups is enabled or
466  // direction if BROWSE_FAVORITES
467  // Then pick the next channel in the channel group list to browse
468  // If channel group is ALL CHANNELS (-1), then bypass picking from
469  // the channel group list
470  if ((m_dbUseChannelGroups || (direction == BROWSE_FAVORITE)) &&
471  (direction != BROWSE_RIGHT) && (direction != BROWSE_LEFT) &&
472  (direction != BROWSE_SAME))
473  {
474  m_tv->m_channelGroupLock.lock();
475  if (m_tv->m_channelGroupId > -1)
476  {
478  if ((direction == BROWSE_UP) || (direction == BROWSE_FAVORITE))
479  dir = CHANNEL_DIRECTION_UP;
480  else if (direction == BROWSE_DOWN)
482 
485  direction = BROWSE_SAME;
486 
487  m_tv->m_channelGroupLock.unlock();
488 
489  m_lock.lock();
490  m_chanId = chanid;
491  m_chanNum.clear();
492  m_lock.unlock();
493  }
494  else
495  m_tv->m_channelGroupLock.unlock();
496  }
497 
498  if (direction == BROWSE_FAVORITE)
499  direction = BROWSE_UP;
500 
501  InfoMap infoMap;
502  infoMap["dbstarttime"] = m_startTime;
503  infoMap["channum"] = m_chanNum;
504  infoMap["chanid"] = QString::number(m_chanId);
505 
506  m_tv->GetPlayerReadLock(0,__FILE__,__LINE__);
507  bool still_there = false;
508  for (uint i = 0; i < m_tv->m_player.size() && !still_there; i++)
509  still_there |= (ctx == m_tv->m_player[i]);
510  if (still_there)
511  {
512  if (!m_dbBrowseAllTuners)
513  {
514  GetNextProgram(direction, infoMap);
515  }
516  else
517  {
518  if (!chanids.empty())
519  {
520  for (uint chanid : chanids)
521  {
522  if (TV::IsTunable(ctx, chanid))
523  {
524  infoMap["chanid"] = QString::number(chanid);
525  GetNextProgramDB(direction, infoMap);
526  break;
527  }
528  }
529  }
530  else
531  {
532  uint orig_chanid = infoMap["chanid"].toUInt();
533  GetNextProgramDB(direction, infoMap);
534  while (!TV::IsTunable(ctx, infoMap["chanid"].toUInt()) &&
535  (infoMap["chanid"].toUInt() != orig_chanid))
536  {
537  GetNextProgramDB(direction, infoMap);
538  }
539  }
540  }
541  }
542  m_tv->ReturnPlayerLock(ctx);
543 
544  m_lock.lock();
545  if (!m_ctx && !still_there)
546  continue;
547 
548  m_chanNum = infoMap["channum"];
549  m_chanId = infoMap["chanid"].toUInt();
550 
551  if (((direction == BROWSE_LEFT) || (direction == BROWSE_RIGHT)) &&
552  !infoMap["dbstarttime"].isEmpty())
553  {
554  m_startTime = infoMap["dbstarttime"];
555  }
556 
557  if (!m_list.empty())
558  {
559  // send partial info to UI for appearance of responsiveness
560  QCoreApplication::postEvent(
561  m_tv, new UpdateBrowseInfoEvent(infoMap));
562  continue;
563  }
564  m_lock.unlock();
565 
566  // pull in additional data from the DB...
568  infoMap["channelgroup"] = ChannelGroup::GetChannelGroupName(m_tv->m_channelGroupId);
569  else
570  infoMap["channelgroup"] = QObject::tr("All channels");
571 
572  QDateTime startts = MythDate::fromString(m_startTime);
573  RecordingInfo recinfo(m_chanId, startts, false);
574  recinfo.ToMap(infoMap);
575  infoMap["iconpath"] = ChannelUtil::GetIcon(recinfo.GetChanID());
576 
577  m_lock.lock();
578  if (m_ctx)
579  {
580  QCoreApplication::postEvent(
581  m_tv, new UpdateBrowseInfoEvent(infoMap));
582  }
583  }
584  RunEpilog();
585 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
QHash< uint, QString > m_dbChanidToChannum
void BrowseDispInfo(PlayerContext *ctx, BrowseInfo &bi)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
QHash< uint, uint > m_dbChanidToSourceid
Fetch browse information on current channel and time.
Definition: tv.h:40
void GetNextProgramDB(BrowseDirection direction, InfoMap &infoMap) const
virtual void ToMap(InfoMap &progMap, bool showrerecord=false, uint star_range=10) const
Converts ProgramInfo into QString QHash containing each field in ProgramInfo converted into localized...
ChannelChangeDirection
ChannelChangeDirection is an enumeration of possible channel changing directions.
Definition: tv.h:28
static bool IsTunable(uint chanid)
Definition: tv_play.cpp:8401
void GetNextProgram(int direction, QString &title, QString &subtitle, QString &desc, QString &category, QString &starttime, QString &endtime, QString &callsign, QString &iconpath, QString &channelname, QString &chanid, QString &seriesid, QString &programid)
Returns information about the program that would be seen if we changed the channel using ChangeChanne...
vector< PlayerContext * > m_player
Definition: tv_play.h:901
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
Fetch information on current channel in the past.
Definition: tv.h:43
BrowseDirection m_dir
void KillTimer(int id)
Definition: tv_play.cpp:3155
void UnlockPlayingInfo(const char *file, int line) const
void GetNextProgram(BrowseDirection direction, InfoMap &infoMap) const
Fetches information on the desired program from the backend.
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:34
PlayerContext * GetPlayerReadLock(int which, const char *file, int location)
Definition: tv_play.cpp:13241
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
size_t size(void) const
QMutex m_channelGroupLock
Lock necessary when modifying channel group variables.
Definition: tv_play.h:939
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:23
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:370
static void SortChannels(ChannelInfoList &list, const QString &order, bool eliminate_duplicates=false)
bool IsBrowsing(void) const
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:384
ChannelInfoList m_channelGroupChannelList
Definition: tv_play.h:941
ProgramInfo * m_playingInfo
Currently playing info.
Holds information on recordings and videos.
Definition: programinfo.h:67
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:98
QMultiMap< QString, uint > m_dbChannumToChanids
void HideWindow(const QString &Window)
Definition: osd.cpp:909
bool BrowseStart(PlayerContext *ctx, bool skip_browse=false)
Begins channel browsing.
BrowseInfo GetBrowsedInfo(void) const
bool empty(void) const
static void format_time(int seconds, QString &tMin, QString &tHrsMin)
static const uint kBrowseTimeout
Timeout for browse mode exit in msec.
Definition: tv_play.h:1063
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
Default local time.
Definition: mythdate.h:16
void LockPlayingInfo(const char *file, int line) const
static ChannelInfoList GetChannels(uint sourceid, bool visible_only, const QString &group_by=QString(), uint channel_groupid=0)
Definition: channelutil.h:239
int m_lastCardid
CardID of current/last recorder.
static QString GetIcon(uint chanid)
Control TV playback.
Definition: tv_play.h:279
void ReturnOSDLock(const PlayerContext *ctx, OSD *&osd)
Definition: tv_play.cpp:13215
unsigned int uint
Definition: compat.h:140
void BrowseChannel(PlayerContext *ctx, const QString &channum)
static QString GetChannelGroupName(int grpid)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
QWaitCondition m_wait
ChannelInfoList m_dbAllVisibleChannels
RemoteEncoder * m_recorder
QString m_startTime
void BrowseEnd(PlayerContext *ctx, bool change_channel)
Ends channel browsing.
QMutex m_timerIdLock
Definition: tv_play.h:949
volatile int m_channelGroupId
Definition: tv_play.h:940
BrowseDirection
Used to request ProgramInfo for channel browsing.
Definition: tv.h:37
Fetch information on the next favorite channel.
Definition: tv.h:45
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
Fetch information on next channel.
Definition: tv.h:42
#define LOC
volatile int m_browseTimerId
Definition: tv_play.h:959
QString m_chanNum
TVBrowseHelper(TV *tv, uint browse_max_forward, bool browse_all_tuners, bool use_channel_groups, const QString &db_channel_ordering)
bool LoadFromProgram(ProgramList &destination, const QString &where, const QString &groupBy, const QString &orderBy, const MSqlBindings &bindings, const ProgramList &schedList)
Fetch information on previous channel.
Definition: tv.h:41
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:366
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
ChannelInfoList m_dbAllChannels
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
Definition: osd.h:131
QList< BrowseInfo > m_list
uint GetChanId(const QString &channum, uint pref_cardid, uint pref_sourceid) const
Returns a chanid for the channum, or 0 if none is available.
static uint GetSourceID(uint inputid)
Definition: cardutil.cpp:1767
Default UTC.
Definition: mythdate.h:14
bool ClearOSD(const PlayerContext *ctx)
Definition: tv_play.cpp:7852
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void ChangeChannel(const PlayerContext *ctx, const ChannelInfoList &options)
Definition: tv_play.cpp:7784
int StartTimer(int interval, int line)
Definition: tv_play.cpp:3143
void ReturnPlayerLock(PlayerContext *&ctx)
Definition: tv_play.cpp:13304
Fetch information on current channel in the future.
Definition: tv.h:44
PlayerContext * m_ctx
Default local time.
Definition: mythdate.h:19
static uint GetNextChannel(const ChannelInfoList &sorted, uint old_chanid, uint mplexid_restriction, uint chanid_restriction, ChannelChangeDirection direction, bool skip_non_visible=true, bool skip_same_channum_and_callsign=false)