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  ChannelInfoList::const_iterator it = m_dbAllChannels.begin();
48  for (; it != m_dbAllChannels.end(); ++it)
49  {
50  m_dbChanidToChannum[(*it).m_chanid] = (*it).m_channum;
51  m_dbChanidToSourceid[(*it).m_chanid] = (*it).m_sourceid;
52  m_dbChannumToChanids.insert((*it).m_channum,(*it).m_chanid);
53  }
54 
56  0, true, "channum, callsign");
58  m_dbAllVisibleChannels, db_channel_ordering, false);
59 
60  start();
61 }
62 
63 
66 bool TVBrowseHelper::BrowseStart(PlayerContext *ctx, bool skip_browse)
67 {
68  if (!gCoreContext->IsUIThread())
69  return false;
70 
71  QMutexLocker locker(&m_lock);
72 
73  if (m_ctx)
74  return m_ctx == ctx;
75 
76  m_tv->ClearOSD(ctx);
77 
78  ctx->LockPlayingInfo(__FILE__, __LINE__);
79  if (ctx->m_playingInfo)
80  {
81  m_ctx = ctx;
85  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
86 
87  if (!skip_browse)
88  {
90  locker.unlock();
91  BrowseDispInfo(ctx, bi);
92  }
93  return true;
94  }
95  ctx->UnlockPlayingInfo(__FILE__, __LINE__);
96  return false;
97 }
98 
105 void TVBrowseHelper::BrowseEnd(PlayerContext *ctx, bool change_channel)
106 {
107  if (!gCoreContext->IsUIThread())
108  return;
109 
110  QMutexLocker locker(&m_lock);
111 
112  if (ctx && m_ctx != ctx)
113  return;
114 
115  if (!m_ctx)
116  return;
117 
118  {
119  QMutexLocker locker2(&m_tv->m_timerIdLock);
120  if (m_tv->m_browseTimerId)
121  {
123  m_tv->m_browseTimerId = 0;
124  }
125  }
126 
127  m_list.clear();
128  m_wait.wakeAll();
129 
130  OSD *osd = m_tv->GetOSDLock(ctx);
131  if (osd)
132  osd->HideWindow("browse_info");
133  m_tv->ReturnOSDLock(ctx, osd);
134 
135  if (change_channel)
136  m_tv->ChangeChannel(ctx, 0, m_channum);
137 
138  m_ctx = nullptr;
139 }
140 
142 {
143  if (!gCoreContext->IsUIThread())
144  return;
145 
146  if (!BrowseStart(ctx, true))
147  return;
148 
149  {
150  QMutexLocker locker(&m_tv->m_timerIdLock);
151  if (m_tv->m_browseTimerId)
152  {
155  m_tv->StartTimer(TV::kBrowseTimeout, __LINE__);
156  }
157  }
158 
159  QMutexLocker locker(&m_lock);
160  if (BROWSE_SAME == bi.m_dir)
161  m_list.clear();
162  m_list.push_back(bi);
163  m_wait.wakeAll();
164 }
165 
166 void TVBrowseHelper::BrowseChannel(PlayerContext *ctx, const QString &channum)
167 {
168  if (!gCoreContext->IsUIThread())
169  return;
170 
172  {
173  BrowseInfo bi(channum, 0);
174  BrowseDispInfo(ctx, bi);
175  return;
176  }
177 
178  if (!ctx->m_recorder || !ctx->m_lastCardid)
179  return;
180 
181  uint inputid = ctx->m_lastCardid;
182  uint sourceid = CardUtil::GetSourceID(inputid);
183  if (sourceid)
184  {
185  BrowseInfo bi(channum, sourceid);
186  BrowseDispInfo(ctx, bi);
187  }
188 }
189 
191 {
192  QMutexLocker locker(&m_lock);
194  if (m_ctx != nullptr)
195  {
196  bi.m_channum = m_channum;
197  bi.m_chanid = m_chanid;
199  }
200  return bi;
201 }
202 
207 {
208  if (!gCoreContext->IsUIThread())
209  return true;
210 
211  return m_ctx != nullptr;
212 }
213 
222  const QString &channum, uint pref_cardid, uint pref_sourceid) const
223 {
224  if (pref_sourceid)
225  {
226  ChannelInfoList::const_iterator it = m_dbAllChannels.begin();
227  for (; it != m_dbAllChannels.end(); ++it)
228  {
229  if ((*it).m_sourceid == pref_sourceid && (*it).m_channum == channum)
230  return (*it).m_chanid;
231  }
232  }
233 
234  if (pref_cardid)
235  {
236  ChannelInfoList::const_iterator it = m_dbAllChannels.begin();
237  for (; it != m_dbAllChannels.end(); ++it)
238  {
239  if ((*it).GetInputIds().contains(pref_cardid) &&
240  (*it).m_channum == channum)
241  return (*it).m_chanid;
242  }
243  }
244 
246  {
247  ChannelInfoList::const_iterator it = m_dbAllChannels.begin();
248  for (; it != m_dbAllChannels.end(); ++it)
249  {
250  if ((*it).m_channum == channum)
251  return (*it).m_chanid;
252  }
253  }
254 
255  return 0;
256 }
257 
264  BrowseDirection direction, InfoMap &infoMap) const
265 {
266  if (!m_ctx || !m_ctx->m_recorder)
267  return;
268 
269  QString title, subtitle, desc, category, endtime, callsign, iconpath;
270  QDateTime begts, endts;
271 
272  QString starttime = infoMap["dbstarttime"];
273  QString chanid = infoMap["chanid"];
274  QString channum = infoMap["channum"];
275  QString seriesid = infoMap["seriesid"];
276  QString programid = infoMap["programid"];
277 
279  direction,
280  title, subtitle, desc, category,
281  starttime, endtime, callsign, iconpath,
282  channum, chanid, seriesid, programid);
283 
284  if (!starttime.isEmpty())
285  begts = MythDate::fromString(starttime);
286  else
287  begts = MythDate::fromString(infoMap["dbstarttime"]);
288 
289  infoMap["starttime"] = MythDate::toString(begts, MythDate::kTime);
290  infoMap["startdate"] = MythDate::toString(
292 
293  infoMap["endtime"] = infoMap["enddate"] = "";
294  if (!endtime.isEmpty())
295  {
296  endts = MythDate::fromString(endtime);
297  infoMap["endtime"] = MythDate::toString(endts, MythDate::kTime);
298  infoMap["enddate"] = MythDate::toString(
300  }
301 
302  infoMap["lenmins"] = TV::tr("%n minute(s)", "", 0);
303  infoMap["lentime"] = "0:00";
304  if (begts.isValid() && endts.isValid())
305  {
306  QString lenM, lenHM;
307  format_time(begts.secsTo(endts), lenM, lenHM);
308  infoMap["lenmins"] = lenM;
309  infoMap["lentime"] = lenHM;
310  }
311 
312  infoMap["dbstarttime"] = starttime;
313  infoMap["dbendtime"] = endtime;
314  infoMap["title"] = title;
315  infoMap["subtitle"] = subtitle;
316  infoMap["description"] = desc;
317  infoMap["category"] = category;
318  infoMap["callsign"] = callsign;
319  infoMap["channum"] = channum;
320  infoMap["chanid"] = chanid;
321  infoMap["iconpath"] = iconpath;
322  infoMap["seriesid"] = seriesid;
323  infoMap["programid"] = programid;
324 }
325 
327  BrowseDirection direction, InfoMap &infoMap) const
328 {
329  uint chanid = infoMap["chanid"].toUInt();
330  if (!chanid)
331  {
332  LOG(VB_GENERAL, LOG_ERR, LOC + "GetNextProgramDB() requires a chanid");
333  return;
334  }
335 
336  int chandir = -1;
337  switch (direction)
338  {
339  case BROWSE_UP: chandir = CHANNEL_DIRECTION_UP; break;
340  case BROWSE_DOWN: chandir = CHANNEL_DIRECTION_DOWN; break;
341  case BROWSE_FAVORITE: chandir = CHANNEL_DIRECTION_FAVORITE; break;
342  case BROWSE_SAME:
343  case BROWSE_LEFT:
344  case BROWSE_RIGHT:
345  case BROWSE_INVALID:
346  default: break; // quiet -Wswitch-enum
347  }
348  if (chandir != -1)
349  {
351  m_dbAllVisibleChannels, chanid, 0 /*mplexid_restriction*/,
352  0 /* chanid restriction */,
353  static_cast<ChannelChangeDirection>(chandir),
354  true /*skip non visible*/, true /*skip same callsign*/);
355  }
356 
357  infoMap["chanid"] = QString::number(chanid);
358  infoMap["channum"] = m_dbChanidToChannum[chanid];
359 
360  QDateTime nowtime = MythDate::current();
361  QDateTime latesttime = nowtime.addSecs(6*60*60);
362  QDateTime browsetime = MythDate::fromString(infoMap["dbstarttime"]);
363 
364  MSqlBindings bindings;
365  bindings[":CHANID"] = chanid;
366  bindings[":NOWTS"] = nowtime;
367  bindings[":LATESTTS"] = latesttime;
368  bindings[":BROWSETS"] = browsetime;
369  bindings[":BROWSETS2"] = browsetime;
370 
371  QString querystr = " WHERE program.chanid = :CHANID ";
372  switch (direction)
373  {
374  case BROWSE_LEFT:
375  querystr += " AND program.endtime <= :BROWSETS "
376  " AND program.endtime > :NOWTS ";
377  break;
378 
379  case BROWSE_RIGHT:
380  querystr += " AND program.starttime > :BROWSETS "
381  " AND program.starttime < :LATESTTS ";
382  break;
383 
384  default:
385  querystr += " AND program.starttime <= :BROWSETS "
386  " AND program.endtime > :BROWSETS2 ";
387  };
388 
389  ProgramList progList;
390  ProgramList dummySched;
391  LoadFromProgram(progList, querystr, bindings, dummySched);
392 
393  if (progList.empty())
394  {
395  infoMap["dbstarttime"] = "";
396  return;
397  }
398 
399  const ProgramInfo *prog = (direction == BROWSE_LEFT) ?
400  progList[progList.size() - 1] : progList[0];
401 
402  infoMap["dbstarttime"] = prog->GetScheduledStartTime(MythDate::ISODate);
403 }
404 
406 {
407  RunProlog();
408  QMutexLocker locker(&m_lock);
409  while (true)
410  {
411  while (m_list.empty() && m_run)
412  m_wait.wait(&m_lock);
413 
414  if (!m_run)
415  break;
416 
417  BrowseInfo bi = m_list.front();
418  m_list.pop_front();
419 
420  PlayerContext *ctx = m_ctx;
421 
422  vector<uint> chanids;
423  if (BROWSE_SAME == bi.m_dir)
424  {
425  if (!bi.m_chanid)
426  {
427  vector<uint> chanids_extra;
428  uint sourceid = m_dbChanidToSourceid[m_chanid];
429  QMultiMap<QString,uint>::iterator it;
430  it = m_dbChannumToChanids.lowerBound(bi.m_channum);
431  for ( ; (it != m_dbChannumToChanids.end()) &&
432  (it.key() == bi.m_channum); ++it)
433  {
434  if (m_dbChanidToSourceid[*it] == sourceid)
435  chanids.push_back(*it);
436  else
437  chanids_extra.push_back(*it);
438  }
439  chanids.insert(chanids.end(),
440  chanids_extra.begin(),
441  chanids_extra.end());
442  }
443  m_channum = bi.m_channum;
444  m_chanid = (chanids.empty()) ? bi.m_chanid : chanids[0];
446  }
447 
448  BrowseDirection direction = bi.m_dir;
449 
450  QDateTime lasttime = MythDate::fromString(m_starttime);
451  QDateTime curtime = MythDate::current();
452  if (lasttime < curtime)
453  m_starttime = curtime.toString(Qt::ISODate);
454 
455  QDateTime maxtime = curtime.addSecs(m_dbBrowseMaxForward);
456  if ((lasttime > maxtime) && (direction == BROWSE_RIGHT))
457  continue;
458 
459  m_lock.unlock();
460 
461  // if browsing channel groups is enabled or
462  // direction if BROWSE_FAVORITES
463  // Then pick the next channel in the channel group list to browse
464  // If channel group is ALL CHANNELS (-1), then bypass picking from
465  // the channel group list
466  if ((m_dbUseChannelGroups || (direction == BROWSE_FAVORITE)) &&
467  (direction != BROWSE_RIGHT) && (direction != BROWSE_LEFT) &&
468  (direction != BROWSE_SAME))
469  {
470  m_tv->m_channelGroupLock.lock();
471  if (m_tv->m_channelGroupId > -1)
472  {
474  if ((direction == BROWSE_UP) || (direction == BROWSE_FAVORITE))
475  dir = CHANNEL_DIRECTION_UP;
476  else if (direction == BROWSE_DOWN)
478 
481  direction = BROWSE_SAME;
482 
483  m_tv->m_channelGroupLock.unlock();
484 
485  m_lock.lock();
486  m_chanid = chanid;
487  m_channum.clear();
488  m_lock.unlock();
489  }
490  else
491  m_tv->m_channelGroupLock.unlock();
492  }
493 
494  if (direction == BROWSE_FAVORITE)
495  direction = BROWSE_UP;
496 
497  InfoMap infoMap;
498  infoMap["dbstarttime"] = m_starttime;
499  infoMap["channum"] = m_channum;
500  infoMap["chanid"] = QString::number(m_chanid);
501 
502  m_tv->GetPlayerReadLock(0,__FILE__,__LINE__);
503  bool still_there = false;
504  for (uint i = 0; i < m_tv->m_player.size() && !still_there; i++)
505  still_there |= (ctx == m_tv->m_player[i]);
506  if (still_there)
507  {
508  if (!m_dbBrowseAllTuners)
509  {
510  GetNextProgram(direction, infoMap);
511  }
512  else
513  {
514  if (!chanids.empty())
515  {
516  for (size_t i = 0; i < chanids.size(); i++)
517  {
518  if (m_tv->IsTunable(ctx, chanids[i]))
519  {
520  infoMap["chanid"] = QString::number(chanids[i]);
521  GetNextProgramDB(direction, infoMap);
522  break;
523  }
524  }
525  }
526  else
527  {
528  uint orig_chanid = infoMap["chanid"].toUInt();
529  GetNextProgramDB(direction, infoMap);
530  while (!m_tv->IsTunable(ctx, infoMap["chanid"].toUInt()) &&
531  (infoMap["chanid"].toUInt() != orig_chanid))
532  {
533  GetNextProgramDB(direction, infoMap);
534  }
535  }
536  }
537  }
538  m_tv->ReturnPlayerLock(ctx);
539 
540  m_lock.lock();
541  if (!m_ctx && !still_there)
542  continue;
543 
544  m_channum = infoMap["channum"];
545  m_chanid = infoMap["chanid"].toUInt();
546 
547  if (((direction == BROWSE_LEFT) || (direction == BROWSE_RIGHT)) &&
548  !infoMap["dbstarttime"].isEmpty())
549  {
550  m_starttime = infoMap["dbstarttime"];
551  }
552 
553  if (!m_list.empty())
554  {
555  // send partial info to UI for appearance of responsiveness
556  QCoreApplication::postEvent(
557  m_tv, new UpdateBrowseInfoEvent(infoMap));
558  continue;
559  }
560  m_lock.unlock();
561 
562  // pull in additional data from the DB...
564  infoMap["channelgroup"] = ChannelGroup::GetChannelGroupName(m_tv->m_channelGroupId);
565  else
566  infoMap["channelgroup"] = QObject::tr("All channels");
567 
568  QDateTime startts = MythDate::fromString(m_starttime);
569  RecordingInfo recinfo(m_chanid, startts, false);
570  recinfo.ToMap(infoMap);
571  infoMap["iconpath"] = ChannelUtil::GetIcon(recinfo.GetChanID());
572 
573  m_lock.lock();
574  if (m_ctx)
575  {
576  QCoreApplication::postEvent(
577  m_tv, new UpdateBrowseInfoEvent(infoMap));
578  }
579  }
580  RunEpilog();
581 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
QHash< uint, QString > m_dbChanidToChannum
void BrowseDispInfo(PlayerContext *ctx, BrowseInfo &bi)
Fetch information on previous channel.
Definition: tv.h:41
QString m_channum
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
QHash< uint, uint > m_dbChanidToSourceid
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:8463
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:907
void ReturnPlayerLock(PlayerContext *&)
Definition: tv_play.cpp:13423
BrowseDirection m_dir
void KillTimer(int id)
Definition: tv_play.cpp:3228
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:13360
Fetch browse information on current channel and time.
Definition: tv.h:40
unsigned int uint
Definition: compat.h:140
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:949
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:368
static void SortChannels(ChannelInfoList &list, const QString &order, bool eliminate_duplicates=false)
void ReturnOSDLock(const PlayerContext *, OSD *&)
Definition: tv_play.cpp:13334
bool IsBrowsing(void) const
Fetch information on next channel.
Definition: tv.h:42
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:382
ChannelInfoList m_channelGroupChannelList
Definition: tv_play.h:951
ProgramInfo * m_playingInfo
Currently playing info.
enum BrowseDirections BrowseDirection
Used to request ProgramInfo for channel browsing.
Holds information on recordings and videos.
Definition: programinfo.h:66
QMultiMap< QString, uint > m_dbChannumToChanids
bool BrowseStart(PlayerContext *ctx, bool skip_browse=false)
Begins channel browsing.
BrowseInfo GetBrowsedInfo(void) const
bool empty(void) const
Default local time.
Definition: mythdate.h:19
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
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:1078
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
void LockPlayingInfo(const char *file, int line) const
QString m_starttime
Default local time.
Definition: mythdate.h:16
static ChannelInfoList GetChannels(uint sourceid, bool visible_only, const QString &group_by=QString(), uint channel_groupid=0)
Definition: channelutil.h:234
int m_lastCardid
CardID of current/last recorder.
static QString GetIcon(uint chanid)
Control TV playback.
Definition: tv_play.h:284
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:23
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
void BrowseEnd(PlayerContext *ctx, bool change_channel)
Ends channel browsing.
QMutex m_timerIdLock
Definition: tv_play.h:959
volatile int m_channelGroupId
Definition: tv_play.h:950
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
#define LOC
volatile int m_browseTimerId
Definition: tv_play.h:969
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 current channel in the past.
Definition: tv.h:43
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:364
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
ChannelInfoList m_dbAllChannels
Fetch information on current channel in the future.
Definition: tv.h:44
void HideWindow(const QString &window)
Definition: osd.cpp:1135
Definition: osd.h:132
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:1725
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:98
void ChangeChannel(const PlayerContext *, const ChannelInfoList &options)
Definition: tv_play.cpp:7850
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
int StartTimer(int interval, int line)
Definition: tv_play.cpp:3216
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
Fetch information on the next favorite channel.
Definition: tv.h:45
Default UTC.
Definition: mythdate.h:14
bool ClearOSD(const PlayerContext *)
Definition: tv_play.cpp:7918
PlayerContext * m_ctx
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)