00001 #include <algorithm>
00002 using namespace std;
00003
00004 #include "mythcorecontext.h"
00005
00006 #include <QCoreApplication>
00007
00008 #include "tvbrowsehelper.h"
00009 #include "playercontext.h"
00010 #include "remoteencoder.h"
00011 #include "recordinginfo.h"
00012 #include "mythplayer.h"
00013 #include "cardutil.h"
00014 #include "tv_play.h"
00015 #include "mythlogging.h"
00016
00017 #define LOC QString("BH: ")
00018
00019 #define GetPlayer(X,Y) GetPlayerHaveLock(X, Y, __FILE__ , __LINE__)
00020 #define GetOSDLock(X) GetOSDL(X, __FILE__, __LINE__)
00021
00022 static void format_time(int seconds, QString &tMin, QString &tHrsMin)
00023 {
00024 int minutes = seconds / 60;
00025 int hours = minutes / 60;
00026 int min = minutes % 60;
00027
00028 tMin = TV::tr("%n minute(s)", "", minutes);
00029 tHrsMin.sprintf("%d:%02d", hours, min);
00030 }
00031
00032 TVBrowseHelper::TVBrowseHelper(
00033 TV *tv,
00034 QString time_format,
00035 QString short_date_format,
00036 uint browse_max_forward,
00037 bool browse_all_tuners,
00038 bool use_channel_groups,
00039 QString db_channel_ordering) :
00040 MThread("TVBrowseHelper"),
00041 m_tv(tv),
00042 db_time_format(time_format),
00043 db_short_date_format(short_date_format),
00044 db_browse_max_forward(browse_max_forward),
00045 db_browse_all_tuners(browse_all_tuners),
00046 db_use_channel_groups(use_channel_groups),
00047 m_ctx(NULL),
00048 m_chanid(0),
00049 m_run(true)
00050 {
00051 db_all_channels = ChannelUtil::GetChannels(
00052 0, true, "channum, callsign");
00053 ChannelUtil::SortChannels(
00054 db_all_channels, db_channel_ordering, false);
00055
00056 DBChanList::const_iterator it = db_all_channels.begin();
00057 for (; it != db_all_channels.end(); ++it)
00058 {
00059 db_chanid_to_channum[(*it).chanid] = (*it).channum;
00060 db_chanid_to_sourceid[(*it).chanid] = (*it).sourceid;
00061 db_channum_to_chanids.insert((*it).channum,(*it).chanid);
00062 }
00063
00064 db_all_visible_channels = ChannelUtil::GetChannels(
00065 0, true, "channum, callsign");
00066 ChannelUtil::SortChannels(
00067 db_all_visible_channels, db_channel_ordering, false);
00068
00069 start();
00070 }
00071
00072
00075 bool TVBrowseHelper::BrowseStart(PlayerContext *ctx, bool skip_browse)
00076 {
00077 if (!gCoreContext->IsUIThread())
00078 return false;
00079
00080 QMutexLocker locker(&m_lock);
00081
00082 if (m_ctx)
00083 return m_ctx == ctx;
00084
00085 m_tv->ClearOSD(ctx);
00086
00087 ctx->LockPlayingInfo(__FILE__, __LINE__);
00088 if (ctx->playingInfo)
00089 {
00090 m_ctx = ctx;
00091 m_channum = ctx->playingInfo->GetChanNum();
00092 m_chanid = ctx->playingInfo->GetChanID();
00093 m_starttime = ctx->playingInfo->GetScheduledStartTime(ISODate);
00094 ctx->UnlockPlayingInfo(__FILE__, __LINE__);
00095
00096 if (!skip_browse)
00097 {
00098 BrowseInfo bi(BROWSE_SAME, m_channum, m_chanid, m_starttime);
00099 locker.unlock();
00100 BrowseDispInfo(ctx, bi);
00101 }
00102 return true;
00103 }
00104 else
00105 {
00106 ctx->UnlockPlayingInfo(__FILE__, __LINE__);
00107 return false;
00108 }
00109 }
00110
00117 void TVBrowseHelper::BrowseEnd(PlayerContext *ctx, bool change_channel)
00118 {
00119 if (!gCoreContext->IsUIThread())
00120 return;
00121
00122 QMutexLocker locker(&m_lock);
00123
00124 if (ctx && m_ctx != ctx)
00125 return;
00126
00127 if (!m_ctx)
00128 return;
00129
00130 {
00131 QMutexLocker locker(&m_tv->timerIdLock);
00132 if (m_tv->browseTimerId)
00133 {
00134 m_tv->KillTimer(m_tv->browseTimerId);
00135 m_tv->browseTimerId = 0;
00136 }
00137 }
00138
00139 m_list.clear();
00140 m_wait.wakeAll();
00141
00142 OSD *osd = m_tv->GetOSDLock(ctx);
00143 if (osd)
00144 osd->HideWindow("browse_info");
00145 m_tv->ReturnOSDLock(ctx, osd);
00146
00147 if (change_channel)
00148 m_tv->ChangeChannel(ctx, 0, m_channum);
00149
00150 m_ctx = NULL;
00151 }
00152
00153 void TVBrowseHelper::BrowseDispInfo(PlayerContext *ctx, BrowseInfo &bi)
00154 {
00155 if (!gCoreContext->IsUIThread())
00156 return;
00157
00158 if (!BrowseStart(ctx, true))
00159 return;
00160
00161 {
00162 QMutexLocker locker(&m_tv->timerIdLock);
00163 if (m_tv->browseTimerId)
00164 {
00165 m_tv->KillTimer(m_tv->browseTimerId);
00166 m_tv->browseTimerId =
00167 m_tv->StartTimer(TV::kBrowseTimeout, __LINE__);
00168 }
00169 }
00170
00171 QMutexLocker locker(&m_lock);
00172 if (BROWSE_SAME == bi.m_dir)
00173 m_list.clear();
00174 m_list.push_back(bi);
00175 m_wait.wakeAll();
00176 }
00177
00178 void TVBrowseHelper::BrowseChannel(PlayerContext *ctx, const QString &channum)
00179 {
00180 if (!gCoreContext->IsUIThread())
00181 return;
00182
00183 if (db_browse_all_tuners)
00184 {
00185 BrowseInfo bi(channum, 0);
00186 BrowseDispInfo(ctx, bi);
00187 return;
00188 }
00189
00190 if (!ctx->recorder || !ctx->last_cardid)
00191 return;
00192
00193 QString inputname = ctx->recorder->GetInput();
00194 uint inputid = CardUtil::GetInputID(ctx->last_cardid, inputname);
00195 uint sourceid = CardUtil::GetSourceID(inputid);
00196 if (sourceid)
00197 {
00198 BrowseInfo bi(channum, sourceid);
00199 BrowseDispInfo(ctx, bi);
00200 }
00201 }
00202
00203 BrowseInfo TVBrowseHelper::GetBrowsedInfo(void) const
00204 {
00205 QMutexLocker locker(&m_lock);
00206 BrowseInfo bi(BROWSE_SAME);
00207 if (m_ctx != NULL)
00208 {
00209 bi.m_channum = m_channum;
00210 bi.m_chanid = m_chanid;
00211 bi.m_starttime = m_starttime;
00212 }
00213 return bi;
00214 }
00215
00219 bool TVBrowseHelper::IsBrowsing(void) const
00220 {
00221 if (!gCoreContext->IsUIThread())
00222 return true;
00223
00224 return m_ctx != NULL;
00225 }
00226
00234 uint TVBrowseHelper::GetChanId(
00235 const QString &channum, uint pref_cardid, uint pref_sourceid) const
00236 {
00237 if (pref_sourceid)
00238 {
00239 DBChanList::const_iterator it = db_all_channels.begin();
00240 for (; it != db_all_channels.end(); ++it)
00241 {
00242 if ((*it).sourceid == pref_sourceid && (*it).channum == channum)
00243 return (*it).chanid;
00244 }
00245 }
00246
00247 if (pref_cardid)
00248 {
00249 DBChanList::const_iterator it = db_all_channels.begin();
00250 for (; it != db_all_channels.end(); ++it)
00251 {
00252 if ((*it).cardid == pref_cardid && (*it).channum == channum)
00253 return (*it).chanid;
00254 }
00255 }
00256
00257 if (db_browse_all_tuners)
00258 {
00259 DBChanList::const_iterator it = db_all_channels.begin();
00260 for (; it != db_all_channels.end(); ++it)
00261 {
00262 if ((*it).channum == channum)
00263 return (*it).chanid;
00264 }
00265 }
00266
00267 return 0;
00268 }
00269
00276 void TVBrowseHelper::GetNextProgram(
00277 BrowseDirection direction, InfoMap &infoMap) const
00278 {
00279 if (!m_ctx->recorder)
00280 return;
00281
00282 QString title, subtitle, desc, category, endtime, callsign, iconpath;
00283 QDateTime begts, endts;
00284
00285 QString starttime = infoMap["dbstarttime"];
00286 QString chanid = infoMap["chanid"];
00287 QString channum = infoMap["channum"];
00288 QString seriesid = infoMap["seriesid"];
00289 QString programid = infoMap["programid"];
00290
00291 m_ctx->recorder->GetNextProgram(
00292 direction,
00293 title, subtitle, desc, category,
00294 starttime, endtime, callsign, iconpath,
00295 channum, chanid, seriesid, programid);
00296
00297 if (!starttime.isEmpty())
00298 begts = QDateTime::fromString(starttime, Qt::ISODate);
00299 else
00300 begts = QDateTime::fromString(infoMap["dbstarttime"], Qt::ISODate);
00301
00302 infoMap["starttime"] = begts.toString(db_time_format);
00303 infoMap["startdate"] = begts.toString(db_short_date_format);
00304
00305 infoMap["endtime"] = infoMap["enddate"] = "";
00306 if (!endtime.isEmpty())
00307 {
00308 endts = QDateTime::fromString(endtime, Qt::ISODate);
00309 infoMap["endtime"] = endts.toString(db_time_format);
00310 infoMap["enddate"] = endts.toString(db_short_date_format);
00311 }
00312
00313 infoMap["lenmins"] = TV::tr("%n minute(s)", "", 0);
00314 infoMap["lentime"] = "0:00";
00315 if (begts.isValid() && endts.isValid())
00316 {
00317 QString lenM, lenHM;
00318 format_time(begts.secsTo(endts), lenM, lenHM);
00319 infoMap["lenmins"] = lenM;
00320 infoMap["lentime"] = lenHM;
00321 }
00322
00323 infoMap["dbstarttime"] = starttime;
00324 infoMap["dbendtime"] = endtime;
00325 infoMap["title"] = title;
00326 infoMap["subtitle"] = subtitle;
00327 infoMap["description"] = desc;
00328 infoMap["category"] = category;
00329 infoMap["callsign"] = callsign;
00330 infoMap["channum"] = channum;
00331 infoMap["chanid"] = chanid;
00332 infoMap["iconpath"] = iconpath;
00333 infoMap["seriesid"] = seriesid;
00334 infoMap["programid"] = programid;
00335 }
00336
00337 void TVBrowseHelper::GetNextProgramDB(
00338 BrowseDirection direction, InfoMap &infoMap) const
00339 {
00340 uint chanid = infoMap["chanid"].toUInt();
00341 if (!chanid)
00342 {
00343 LOG(VB_GENERAL, LOG_ERR, LOC + "GetNextProgramDB() requires a chanid");
00344 return;
00345 }
00346
00347 int chandir = -1;
00348 switch (direction)
00349 {
00350 case BROWSE_UP: chandir = CHANNEL_DIRECTION_UP; break;
00351 case BROWSE_DOWN: chandir = CHANNEL_DIRECTION_DOWN; break;
00352 case BROWSE_FAVORITE: chandir = CHANNEL_DIRECTION_FAVORITE; break;
00353 }
00354 if (chandir != -1)
00355 {
00356 chanid = ChannelUtil::GetNextChannel(
00357 db_all_visible_channels, chanid, 0 ,
00358 chandir, true , true );
00359 }
00360
00361 infoMap["chanid"] = QString::number(chanid);
00362 infoMap["channum"] = db_chanid_to_channum[chanid];
00363
00364 QDateTime nowtime = QDateTime::currentDateTime();
00365 QDateTime latesttime = nowtime.addSecs(6*60*60);
00366 QDateTime browsetime = QDateTime::fromString(
00367 infoMap["dbstarttime"], Qt::ISODate);
00368
00369 MSqlBindings bindings;
00370 bindings[":CHANID"] = chanid;
00371 bindings[":NOWTS"] = nowtime;
00372 bindings[":LATESTTS"] = latesttime;
00373 bindings[":BROWSETS"] = browsetime;
00374 bindings[":BROWSETS2"] = browsetime;
00375
00376 QString querystr = " WHERE program.chanid = :CHANID ";
00377 switch (direction)
00378 {
00379 case BROWSE_LEFT:
00380 querystr += " AND program.endtime <= :BROWSETS "
00381 " AND program.endtime > :NOWTS ";
00382 break;
00383
00384 case BROWSE_RIGHT:
00385 querystr += " AND program.starttime > :BROWSETS "
00386 " AND program.starttime < :LATESTTS ";
00387 break;
00388
00389 default:
00390 querystr += " AND program.starttime <= :BROWSETS "
00391 " AND program.endtime > :BROWSETS2 ";
00392 };
00393
00394 ProgramList progList;
00395 ProgramList dummySched;
00396 LoadFromProgram(progList, querystr, bindings, dummySched);
00397
00398 if (progList.empty())
00399 {
00400 infoMap["dbstarttime"] = "";
00401 return;
00402 }
00403
00404 const ProgramInfo *prog = (direction == BROWSE_LEFT) ?
00405 progList[progList.size() - 1] : progList[0];
00406
00407 infoMap["dbstarttime"] = prog->GetScheduledStartTime(ISODate);
00408 }
00409
00410 inline static QString toString(const InfoMap &infoMap, const QString sep="\n")
00411 {
00412 QString str("");
00413 InfoMap::const_iterator it = infoMap.begin();
00414 for (; it != infoMap.end() ; ++it)
00415 str += QString("[%1]:%2%3").arg(it.key()).arg(*it).arg(sep);
00416 return str;
00417 }
00418
00419 void TVBrowseHelper::run()
00420 {
00421 RunProlog();
00422 QMutexLocker locker(&m_lock);
00423 while (true)
00424 {
00425 while (m_list.empty() && m_run)
00426 m_wait.wait(&m_lock);
00427
00428 if (!m_run)
00429 break;
00430
00431 BrowseInfo bi = m_list.front();
00432 m_list.pop_front();
00433
00434 PlayerContext *ctx = m_ctx;
00435
00436 vector<uint> chanids;
00437 if (BROWSE_SAME == bi.m_dir)
00438 {
00439 if (!bi.m_chanid)
00440 {
00441 vector<uint> chanids_extra;
00442 uint sourceid = db_chanid_to_sourceid[m_chanid];
00443 QMultiMap<QString,uint>::iterator it;
00444 it = db_channum_to_chanids.lowerBound(bi.m_channum);
00445 for ( ; (it != db_channum_to_chanids.end()) &&
00446 (it.key() == bi.m_channum); ++it)
00447 {
00448 if (db_chanid_to_sourceid[*it] == sourceid)
00449 chanids.push_back(*it);
00450 else
00451 chanids_extra.push_back(*it);
00452 }
00453 chanids.insert(chanids.end(),
00454 chanids_extra.begin(),
00455 chanids_extra.end());
00456 }
00457 m_channum = bi.m_channum;
00458 m_chanid = (chanids.empty()) ? bi.m_chanid : chanids[0];
00459 m_starttime = bi.m_starttime;
00460 }
00461
00462 BrowseDirection direction = bi.m_dir;
00463
00464 QDateTime lasttime = QDateTime::fromString(
00465 m_starttime, Qt::ISODate);
00466 QDateTime curtime = QDateTime::currentDateTime();
00467 if (lasttime < curtime)
00468 m_starttime = curtime.toString(Qt::ISODate);
00469
00470 QDateTime maxtime = curtime.addSecs(db_browse_max_forward);
00471 if ((lasttime > maxtime) && (direction == BROWSE_RIGHT))
00472 continue;
00473
00474 m_lock.unlock();
00475
00476
00477
00478
00479
00480
00481 if ((db_use_channel_groups || (direction == BROWSE_FAVORITE)) &&
00482 (direction != BROWSE_RIGHT) && (direction != BROWSE_LEFT) &&
00483 (direction != BROWSE_SAME))
00484 {
00485 m_tv->channelGroupLock.lock();
00486 if (m_tv->channelGroupId > -1)
00487 {
00488 int dir = direction;
00489 if ((direction == BROWSE_UP) || (direction == BROWSE_FAVORITE))
00490 dir = CHANNEL_DIRECTION_UP;
00491 else if (direction == BROWSE_DOWN)
00492 dir = CHANNEL_DIRECTION_DOWN;
00493
00494 uint chanid = ChannelUtil::GetNextChannel(
00495 m_tv->channelGroupChannelList, m_chanid, 0, dir);
00496 direction = BROWSE_SAME;
00497
00498 m_tv->channelGroupLock.unlock();
00499
00500 m_lock.lock();
00501 m_chanid = chanid;
00502 m_channum = QString::null;
00503 m_lock.unlock();
00504 }
00505 else
00506 m_tv->channelGroupLock.unlock();
00507 }
00508
00509 if (direction == BROWSE_FAVORITE)
00510 direction = BROWSE_UP;
00511
00512 InfoMap infoMap;
00513 infoMap["dbstarttime"] = m_starttime;
00514 infoMap["channum"] = m_channum;
00515 infoMap["chanid"] = QString::number(m_chanid);
00516
00517 m_tv->GetPlayerReadLock(0,__FILE__,__LINE__);
00518 bool still_there = false;
00519 for (uint i = 0; i < m_tv->player.size() && !still_there; i++)
00520 still_there |= (ctx == m_tv->player[i]);
00521 if (still_there)
00522 {
00523 if (!db_browse_all_tuners)
00524 {
00525 GetNextProgram(direction, infoMap);
00526 }
00527 else
00528 {
00529 if (!chanids.empty())
00530 {
00531 for (uint i = 0; i < chanids.size(); i++)
00532 {
00533 if (m_tv->IsTunable(ctx, chanids[i]))
00534 {
00535 infoMap["chanid"] = QString::number(chanids[i]);
00536 GetNextProgramDB(direction, infoMap);
00537 break;
00538 }
00539 }
00540 }
00541 else
00542 {
00543 uint orig_chanid = infoMap["chanid"].toUInt();
00544 GetNextProgramDB(direction, infoMap);
00545 while (!m_tv->IsTunable(ctx, infoMap["chanid"].toUInt()) &&
00546 (infoMap["chanid"].toUInt() != orig_chanid))
00547 {
00548 GetNextProgramDB(direction, infoMap);
00549 }
00550 }
00551 }
00552 }
00553 m_tv->ReturnPlayerLock(ctx);
00554
00555 m_lock.lock();
00556 if (!m_ctx && !still_there)
00557 continue;
00558
00559 m_channum = infoMap["channum"];
00560 m_chanid = infoMap["chanid"].toUInt();
00561
00562 if (((direction == BROWSE_LEFT) || (direction == BROWSE_RIGHT)) &&
00563 !infoMap["dbstarttime"].isEmpty())
00564 {
00565 m_starttime = infoMap["dbstarttime"];
00566 }
00567
00568 if (!m_list.empty())
00569 {
00570
00571 QCoreApplication::postEvent(
00572 m_tv, new UpdateBrowseInfoEvent(infoMap));
00573 continue;
00574 }
00575 m_lock.unlock();
00576
00577
00578 QDateTime startts = QDateTime::fromString(
00579 m_starttime, Qt::ISODate);
00580 RecordingInfo recinfo(m_chanid, startts, false);
00581 recinfo.ToMap(infoMap);
00582 infoMap["iconpath"] = ChannelUtil::GetIcon(recinfo.GetChanID());
00583
00584 m_lock.lock();
00585 if (m_ctx)
00586 {
00587 QCoreApplication::postEvent(
00588 m_tv, new UpdateBrowseInfoEvent(infoMap));
00589 }
00590 }
00591 RunEpilog();
00592 }