MythTV  0.26-pre
tv_play.cpp
Go to the documentation of this file.
00001 #include <cstdlib>
00002 #include <cstdarg>
00003 #include <cstring>
00004 #include <cmath>
00005 #include <unistd.h>
00006 #include <stdint.h>
00007 
00008 #include <algorithm>
00009 using namespace std;
00010 
00011 #include <QCoreApplication>
00012 #include <QKeyEvent>
00013 #include <QRunnable>
00014 #include <QRegExp>
00015 #include <QTimer>
00016 #include <QEvent>
00017 #include <QFile>
00018 #include <QDir>
00019 
00020 #include "mythdb.h"
00021 #include "tv_play.h"
00022 #include "tv_rec.h"
00023 #include "mythcorecontext.h"
00024 #include "remoteencoder.h"
00025 #include "remoteutil.h"
00026 #include "tvremoteutil.h"
00027 #include "mythplayer.h"
00028 #include "subtitlescreen.h"
00029 #include "DetectLetterbox.h"
00030 #include "programinfo.h"
00031 #include "vsync.h"
00032 #include "lcddevice.h"
00033 #include "jobqueue.h"
00034 #include "audiooutput.h"
00035 #include "DisplayRes.h"
00036 #include "signalmonitorvalue.h"
00037 #include "scheduledrecording.h"
00038 #include "recordingrule.h"
00039 #include "previewgenerator.h"
00040 #include "mythconfig.h"
00041 #include "livetvchain.h"
00042 #include "playgroup.h"
00043 #include "datadirect.h"
00044 #include "sourceutil.h"
00045 #include "cardutil.h"
00046 #include "channelutil.h"
00047 #include "compat.h"
00048 #include "mythuihelper.h"
00049 #include "mythdialogbox.h"
00050 #include "mythmainwindow.h"
00051 #include "mythscreenstack.h"
00052 #include "mythscreentype.h"
00053 #include "tv_play_win.h"
00054 #include "recordinginfo.h"
00055 #include "mythsystemevent.h"
00056 #include "videometadatautil.h"
00057 #include "mythdirs.h"
00058 #include "tvbrowsehelper.h"
00059 #include "mythlogging.h"
00060 #include "mythuistatetracker.h"
00061 #include "DVD/dvdringbuffer.h"
00062 #include "Bluray/bdringbuffer.h"
00063 
00064 #if ! HAVE_ROUND
00065 #define round(x) ((int) ((x) + 0.5))
00066 #endif
00067 
00068 #define DEBUG_CHANNEL_PREFIX 0 
00069 #define DEBUG_ACTIONS        0 
00071 #define LOC      QString("TV: ")
00072 
00073 #define GetPlayer(X,Y) GetPlayerHaveLock(X, Y, __FILE__ , __LINE__)
00074 #define GetOSDLock(X) GetOSDL(X, __FILE__, __LINE__)
00075 
00076 #define SetOSDText(CTX, GROUP, FIELD, TEXT, TIMEOUT) { \
00077     OSD *osd = GetOSDLock(CTX); \
00078     if (osd) \
00079     { \
00080         QHash<QString,QString> map; \
00081         map.insert(FIELD,TEXT); \
00082         osd->SetText(GROUP, map, TIMEOUT); \
00083     } \
00084     ReturnOSDLock(CTX, osd); }
00085 
00086 #define SetOSDMessage(CTX, MESSAGE) \
00087     SetOSDText(CTX, "osd_message", "message_text", MESSAGE, kOSDTimeout_Med)
00088 
00089 #define HideOSDWindow(CTX, WINDOW) { \
00090     OSD *osd = GetOSDLock(CTX); \
00091     if (osd) \
00092         osd->HideWindow(WINDOW); \
00093     ReturnOSDLock(CTX, osd); }
00094 
00095 const int  TV::kInitFFRWSpeed                = 0;
00096 const uint TV::kInputKeysMax                 = 6;
00097 const uint TV::kNextSource                   = 1;
00098 const uint TV::kPreviousSource               = 2;
00099 const uint TV::kMaxPIPCount                  = 4;
00100 const uint TV::kMaxPBPCount                  = 2;
00101 
00102 
00103 const uint TV::kInputModeTimeout             = 5000;
00104 const uint TV::kLCDTimeout                   = 1000;
00105 const uint TV::kBrowseTimeout                = 30000;
00106 const uint TV::kKeyRepeatTimeout             = 300;
00107 const uint TV::kPrevChanTimeout              = 750;
00108 const uint TV::kSleepTimerDialogTimeout      = 45000;
00109 const uint TV::kIdleTimerDialogTimeout       = 45000;
00110 const uint TV::kVideoExitDialogTimeout       = 120000;
00111 
00112 const uint TV::kEndOfPlaybackCheckFrequency  = 250;
00113 const uint TV::kEndOfRecPromptCheckFrequency = 250;
00114 const uint TV::kEmbedCheckFrequency          = 250;
00115 const uint TV::kSpeedChangeCheckFrequency    = 250;
00116 const uint TV::kErrorRecoveryCheckFrequency  = 250;
00117 #ifdef USING_VALGRIND
00118 const uint TV::kEndOfPlaybackFirstCheckTimer = 60000;
00119 #else
00120 const uint TV::kEndOfPlaybackFirstCheckTimer = 5000;
00121 #endif
00122 
00127 QStringList TV::lastProgramStringList = QStringList();
00128 
00132 EMBEDRETURNVOID TV::RunPlaybackBoxPtr = NULL;
00133 
00137 EMBEDRETURNVOID TV::RunViewScheduledPtr = NULL;
00138 
00142 EMBEDRETURNVOIDSCHEDIT TV::RunScheduleEditorPtr = NULL;
00143 
00147 EMBEDRETURNVOIDEPG TV::RunProgramGuidePtr = NULL;
00148 
00152 EMBEDRETURNVOIDFINDER TV::RunProgramFinderPtr = NULL;
00153 
00155 class DDLoader : public QRunnable
00156 {
00157   public:
00158     DDLoader(TV *parent) : m_parent(parent), m_sourceid(0)
00159     {
00160         setAutoDelete(false);
00161     }
00162 
00163     void SetParent(TV *parent) { m_parent = parent; }
00164     void SetSourceID(uint sourceid) { m_sourceid = sourceid; }
00165 
00166     virtual void run(void)
00167     {
00168         if (m_parent)
00169             m_parent->RunLoadDDMap(m_sourceid);
00170         else
00171             SourceUtil::UpdateChannelsFromListings(m_sourceid);
00172 
00173         QMutexLocker locker(&m_lock);
00174         m_sourceid = 0;
00175         m_wait.wakeAll();
00176     }
00177 
00178     void wait(void)
00179     {
00180         QMutexLocker locker(&m_lock);
00181         while (m_sourceid)
00182             m_wait.wait(locker.mutex());
00183     }
00184 
00185   private:
00186     TV *m_parent;
00187     uint m_sourceid;
00188     QMutex m_lock;
00189     QWaitCondition m_wait;
00190 };
00191 
00195 int TV::ConfiguredTunerCards(void)
00196 {
00197     int count = 0;
00198 
00199     MSqlQuery query(MSqlQuery::InitCon());
00200     query.prepare("SELECT COUNT(cardid) FROM capturecard;");
00201     if (query.exec() && query.isActive() && query.size() && query.next())
00202         count = query.value(0).toInt();
00203 
00204     LOG(VB_RECORD, LOG_INFO,
00205         "ConfiguredTunerCards() = " + QString::number(count));
00206 
00207     return count;
00208 }
00209 
00210 static void multi_lock(QMutex *mutex0, ...)
00211 {
00212     vector<QMutex*> mutex;
00213     mutex.push_back(mutex0);
00214 
00215     va_list argp;
00216     va_start(argp, mutex0);
00217     QMutex *cur = va_arg(argp, QMutex*);
00218     while (cur)
00219     {
00220         mutex.push_back(cur);
00221         cur = va_arg(argp, QMutex*);
00222     }
00223     va_end(argp);
00224 
00225     for (bool success = false; !success;)
00226     {
00227         success = true;
00228         for (uint i = 0; success && (i < mutex.size()); i++)
00229         {
00230             if (!(success = mutex[i]->tryLock()))
00231             {
00232                 for (uint j = 0; j < i; j++)
00233                     mutex[j]->unlock();
00234                 usleep(25 * 1000);
00235             }
00236         }
00237     }
00238 }
00239 
00240 QMutex* TV::gTVLock = new QMutex();
00241 TV*     TV::gTV     = NULL;
00242 
00243 bool TV::IsTVRunning(void)
00244 {
00245     QMutexLocker locker(gTVLock);
00246     return gTV;
00247 }
00248 
00249 TV* TV::GetTV(void)
00250 {
00251     QMutexLocker locker(gTVLock);
00252     if (gTV)
00253     {
00254         LOG(VB_GENERAL, LOG_WARNING, LOC + "Already have a TV object.");
00255         return NULL;
00256     }
00257     gTV = new TV();
00258     return gTV;
00259 }
00260 
00261 void TV::ReleaseTV(TV* tv)
00262 {
00263     QMutexLocker locker(gTVLock);
00264     if (!tv || !gTV || (gTV != tv))
00265     {
00266         LOG(VB_GENERAL, LOG_ERR, LOC + "ReleaseTV - programmer error.");
00267         return;
00268     }
00269 
00270     delete gTV;
00271     gTV = NULL;
00272 }
00273 
00277 bool TV::StartTV(ProgramInfo *tvrec, uint flags)
00278 {
00279     TV *tv = GetTV();
00280     if (!tv)
00281         return false;
00282 
00283     LOG(VB_PLAYBACK, LOG_INFO, LOC + "StartTV() -- begin");
00284     bool startInGuide = flags & kStartTVInGuide;
00285     bool inPlaylist = flags & kStartTVInPlayList;
00286     bool initByNetworkCommand = flags & kStartTVByNetworkCommand;
00287     bool quitAll = false;
00288     bool showDialogs = true;
00289     bool playCompleted = false;
00290     ProgramInfo *curProgram = NULL;
00291     bool startSysEventSent = false;
00292 
00293 
00294     if (tvrec)
00295     {
00296         curProgram = new ProgramInfo(*tvrec);
00297         curProgram->SetIgnoreBookmark(flags & kStartTVIgnoreBookmark);
00298     }
00299 
00300     // Must be before Init() otherwise we swallow the PLAYBACK_START event
00301     // with the event filter
00302     sendPlaybackStart();
00303     GetMythMainWindow()->PauseIdleTimer(true);
00304 
00305     // Initialize TV
00306     if (!tv->Init())
00307     {
00308         LOG(VB_GENERAL, LOG_ERR, LOC + "Failed initializing TV");
00309         ReleaseTV(tv);
00310         sendPlaybackEnd();
00311         GetMythMainWindow()->PauseIdleTimer(false);
00312         delete curProgram;
00313         return false;
00314     }
00315 
00316     if (!lastProgramStringList.empty())
00317     {
00318         ProgramInfo pginfo(lastProgramStringList);
00319         if (pginfo.HasPathname() || pginfo.GetChanID())
00320             tv->SetLastProgram(&pginfo);
00321     }
00322 
00323     if (curProgram)
00324     {
00325         startSysEventSent = true;
00326         SendMythSystemPlayEvent("PLAY_STARTED", curProgram);
00327     }
00328 
00329     QString playerError = QString::null;
00330     while (!quitAll)
00331     {
00332         if (curProgram)
00333         {
00334             LOG(VB_PLAYBACK, LOG_INFO, LOC + "tv->Playback() -- begin");
00335             if (!tv->Playback(*curProgram))
00336             {
00337                 quitAll = true;
00338             }
00339             else if (!startSysEventSent)
00340             {
00341                 startSysEventSent = true;
00342                 SendMythSystemPlayEvent("PLAY_STARTED", curProgram);
00343             }
00344 
00345             LOG(VB_PLAYBACK, LOG_INFO, LOC + "tv->Playback() -- end");
00346         }
00347         else if (RemoteGetFreeRecorderCount())
00348         {
00349             LOG(VB_PLAYBACK, LOG_INFO, LOC + "tv->LiveTV() -- begin");
00350             if (!tv->LiveTV(showDialogs))
00351             {
00352                 tv->SetExitPlayer(true, true);
00353                 quitAll = true;
00354             }
00355             else if (!startSysEventSent)
00356             {
00357                 startSysEventSent = true;
00358                 gCoreContext->SendSystemEvent("LIVETV_STARTED");
00359             }
00360 
00361             if (!quitAll && (startInGuide || tv->StartLiveTVInGuide()))
00362                 tv->DoEditSchedule();
00363 
00364             LOG(VB_PLAYBACK, LOG_INFO, LOC + "tv->LiveTV() -- end");
00365         }
00366         else
00367         {
00368             if (!ConfiguredTunerCards())
00369                 LOG(VB_GENERAL, LOG_ERR, LOC + "No tuners configured");
00370             else
00371                 LOG(VB_GENERAL, LOG_ERR, LOC + "No tuners free for live tv");
00372             quitAll = true;
00373             continue;
00374         }
00375 
00376         tv->setInPlayList(inPlaylist);
00377         tv->setUnderNetworkControl(initByNetworkCommand);
00378 
00379         // Process Events
00380         LOG(VB_GENERAL, LOG_INFO, LOC + "Entering main playback loop.");
00381         tv->PlaybackLoop();
00382         LOG(VB_GENERAL, LOG_INFO, LOC + "Exiting main playback loop.");
00383 
00384         if (tv->getJumpToProgram())
00385         {
00386             ProgramInfo *nextProgram = tv->GetLastProgram();
00387 
00388             tv->SetLastProgram(curProgram);
00389             if (curProgram)
00390                 delete curProgram;
00391 
00392             curProgram = nextProgram;
00393 
00394             SendMythSystemPlayEvent("PLAY_CHANGED", curProgram);
00395             continue;
00396         }
00397 
00398         const PlayerContext *mctx =
00399             tv->GetPlayerReadLock(0, __FILE__, __LINE__);
00400         quitAll = tv->wantsToQuit || (mctx && mctx->errored);
00401         if (mctx)
00402         {
00403             mctx->LockDeletePlayer(__FILE__, __LINE__);
00404             if (mctx->player && mctx->player->IsErrored())
00405                 playerError = mctx->player->GetError();
00406             mctx->UnlockDeletePlayer(__FILE__, __LINE__);
00407         }
00408         tv->ReturnPlayerLock(mctx);
00409     }
00410 
00411     LOG(VB_PLAYBACK, LOG_INFO, LOC + "StartTV -- process events 2 begin");
00412     qApp->processEvents();
00413     LOG(VB_PLAYBACK, LOG_INFO, LOC + "StartTV -- process events 2 end");
00414 
00415     // check if the show has reached the end.
00416     if (tvrec && tv->getEndOfRecording())
00417         playCompleted = true;
00418 
00419     bool allowrerecord = tv->getAllowRerecord();
00420     bool deleterecording = tv->requestDelete;
00421 
00422     ReleaseTV(tv);
00423 
00424     if (curProgram)
00425     {
00426         SendMythSystemPlayEvent("PLAY_STOPPED", curProgram);
00427 
00428         if (deleterecording)
00429         {
00430             QStringList list;
00431             list.push_back(QString::number(curProgram->GetChanID()));
00432             list.push_back(curProgram->GetRecordingStartTime(ISODate));
00433             list.push_back("0"); // do not force delete
00434             list.push_back(allowrerecord ? "1" : "0");
00435             MythEvent me("LOCAL_PBB_DELETE_RECORDINGS", list);
00436             gCoreContext->dispatch(me);
00437         }
00438         else if (curProgram->IsRecording())
00439         {
00440             lastProgramStringList.clear();
00441             curProgram->ToStringList(lastProgramStringList);
00442         }
00443 
00444         delete curProgram;
00445     }
00446     else
00447         gCoreContext->SendSystemEvent("PLAY_STOPPED");
00448 
00449     if (!playerError.isEmpty())
00450     {
00451         MythScreenStack *ss = GetMythMainWindow()->GetStack("popup stack");
00452         MythConfirmationDialog *dlg = new MythConfirmationDialog(
00453             ss, playerError, false);
00454         if (!dlg->Create())
00455             delete dlg;
00456         else
00457             ss->AddScreen(dlg);
00458     }
00459 
00460     sendPlaybackEnd();
00461     GetMythMainWindow()->PauseIdleTimer(false);
00462 
00463     LOG(VB_PLAYBACK, LOG_INFO, LOC + "StartTV -- end");
00464 
00465     return playCompleted;
00466 }
00467 
00472 void TV::SetFuncPtr(const char *string, void *lptr)
00473 {
00474     QString name(string);
00475     if (name == "playbackbox")
00476         RunPlaybackBoxPtr = (EMBEDRETURNVOID)lptr;
00477     else if (name == "viewscheduled")
00478         RunViewScheduledPtr = (EMBEDRETURNVOID)lptr;
00479     else if (name == "programguide")
00480         RunProgramGuidePtr = (EMBEDRETURNVOIDEPG)lptr;
00481     else if (name == "programfinder")
00482         RunProgramFinderPtr = (EMBEDRETURNVOIDFINDER)lptr;
00483     else if (name == "scheduleeditor")
00484         RunScheduleEditorPtr = (EMBEDRETURNVOIDSCHEDIT)lptr;
00485 }
00486 
00487 void TV::InitKeys(void)
00488 {
00489     REG_KEY("TV Frontend", ACTION_PLAYBACK, QT_TRANSLATE_NOOP("MythControls",
00490             "Play Program"), "P");
00491     REG_KEY("TV Frontend", ACTION_STOP, QT_TRANSLATE_NOOP("MythControls",
00492             "Stop Program"), "");
00493     REG_KEY("TV Frontend", ACTION_TOGGLERECORD, QT_TRANSLATE_NOOP("MythControls",
00494             "Toggle recording status of current program"), "R");
00495     REG_KEY("TV Frontend", ACTION_DAYLEFT, QT_TRANSLATE_NOOP("MythControls",
00496             "Page the program guide back one day"), "Home");
00497     REG_KEY("TV Frontend", ACTION_DAYRIGHT, QT_TRANSLATE_NOOP("MythControls",
00498             "Page the program guide forward one day"), "End");
00499     REG_KEY("TV Frontend", ACTION_PAGELEFT, QT_TRANSLATE_NOOP("MythControls",
00500             "Page the program guide left"), ",,<");
00501     REG_KEY("TV Frontend", ACTION_PAGERIGHT, QT_TRANSLATE_NOOP("MythControls",
00502             "Page the program guide right"), ">,.");
00503     REG_KEY("TV Frontend", ACTION_TOGGLEFAV, QT_TRANSLATE_NOOP("MythControls",
00504             "Toggle the current channel as a favorite"), "?");
00505     REG_KEY("TV Frontend", ACTION_TOGGLEPGORDER, QT_TRANSLATE_NOOP("MythControls",
00506             "Reverse the channel order in the program guide"), "");
00507     REG_KEY("TV Frontend", ACTION_GUIDE, QT_TRANSLATE_NOOP("MythControls",
00508             "Show the Program Guide"), "S");
00509     REG_KEY("TV Frontend", ACTION_FINDER, QT_TRANSLATE_NOOP("MythControls",
00510             "Show the Program Finder"), "#");
00511     REG_KEY("TV Frontend", "NEXTFAV", QT_TRANSLATE_NOOP("MythControls",
00512             "Cycle through channel groups and all channels in the "
00513             "program guide."), "/");
00514     REG_KEY("TV Frontend", "CHANUPDATE", QT_TRANSLATE_NOOP("MythControls",
00515             "Switch channels without exiting guide in Live TV mode."), "X");
00516     REG_KEY("TV Frontend", ACTION_VOLUMEDOWN, QT_TRANSLATE_NOOP("MythControls",
00517             "Volume down"), "[,{,F10,Volume Down");
00518     REG_KEY("TV Frontend", ACTION_VOLUMEUP, QT_TRANSLATE_NOOP("MythControls",
00519             "Volume up"), "],},F11,Volume Up");
00520     REG_KEY("TV Frontend", ACTION_MUTEAUDIO, QT_TRANSLATE_NOOP("MythControls",
00521             "Mute"), "|,\\,F9,Volume Mute");
00522     REG_KEY("TV Frontend", "CYCLEAUDIOCHAN", QT_TRANSLATE_NOOP("MythControls",
00523             "Cycle audio channels"), "");
00524     REG_KEY("TV Frontend", "RANKINC", QT_TRANSLATE_NOOP("MythControls",
00525             "Increase program or channel rank"), "Right");
00526     REG_KEY("TV Frontend", "RANKDEC", QT_TRANSLATE_NOOP("MythControls",
00527             "Decrease program or channel rank"), "Left");
00528     REG_KEY("TV Frontend", "UPCOMING", QT_TRANSLATE_NOOP("MythControls",
00529             "List upcoming episodes"), "O");
00530     REG_KEY("TV Frontend", ACTION_VIEWSCHEDULED, QT_TRANSLATE_NOOP("MythControls",
00531             "List scheduled upcoming episodes"), "");
00532     REG_KEY("TV Frontend", "DETAILS", QT_TRANSLATE_NOOP("MythControls",
00533             "Show details"), "U");
00534     REG_KEY("TV Frontend", "VIEWCARD", QT_TRANSLATE_NOOP("MythControls",
00535             "Switch Capture Card view"), "Y");
00536     REG_KEY("TV Frontend", "VIEWINPUT", QT_TRANSLATE_NOOP("MythControls",
00537             "Switch Capture Card view"), "C");
00538     REG_KEY("TV Frontend", "CUSTOMEDIT", QT_TRANSLATE_NOOP("MythControls",
00539             "Edit Custom Record Rule"), "");
00540     REG_KEY("TV Frontend", "CHANGERECGROUP", QT_TRANSLATE_NOOP("MythControls",
00541             "Change Recording Group"), "");
00542     REG_KEY("TV Frontend", "CHANGEGROUPVIEW", QT_TRANSLATE_NOOP("MythControls",
00543             "Change Group View"), "");
00544 
00545     REG_KEY("TV Playback", "BACK", QT_TRANSLATE_NOOP("MythControls",
00546             "Exit or return to DVD menu"), "");
00547     REG_KEY("TV Playback", ACTION_CLEAROSD, QT_TRANSLATE_NOOP("MythControls",
00548             "Clear OSD"), "Backspace");
00549     REG_KEY("TV Playback", ACTION_PAUSE, QT_TRANSLATE_NOOP("MythControls",
00550             "Pause"), "P");
00551     REG_KEY("TV Playback", ACTION_SEEKFFWD, QT_TRANSLATE_NOOP("MythControls",
00552             "Fast Forward"), "Right");
00553     REG_KEY("TV Playback", ACTION_SEEKRWND, QT_TRANSLATE_NOOP("MythControls",
00554             "Rewind"), "Left");
00555     REG_KEY("TV Playback", ACTION_SEEKARB, QT_TRANSLATE_NOOP("MythControls",
00556             "Arbitrary Seek"), "*");
00557     REG_KEY("TV Playback", ACTION_SEEKABSOLUTE, QT_TRANSLATE_NOOP("MythControls",
00558             "Seek to a position in seconds"), "");
00559     REG_KEY("TV Playback", ACTION_CHANNELUP, QT_TRANSLATE_NOOP("MythControls",
00560             "Channel up"), "Up");
00561     REG_KEY("TV Playback", ACTION_CHANNELDOWN, QT_TRANSLATE_NOOP("MythControls",
00562             "Channel down"), "Down");
00563     REG_KEY("TV Playback", "NEXTFAV", QT_TRANSLATE_NOOP("MythControls",
00564             "Switch to the next favorite channel"), "/");
00565     REG_KEY("TV Playback", "PREVCHAN", QT_TRANSLATE_NOOP("MythControls",
00566             "Switch to the previous channel"), "H");
00567     REG_KEY("TV Playback", ACTION_JUMPFFWD, QT_TRANSLATE_NOOP("MythControls",
00568             "Jump ahead"), "PgDown");
00569     REG_KEY("TV Playback", ACTION_JUMPRWND, QT_TRANSLATE_NOOP("MythControls",
00570             "Jump back"), "PgUp");
00571     REG_KEY("TV Playback", "INFOWITHCUTLIST", QT_TRANSLATE_NOOP("MythControls",
00572             "Info utilizing cutlist"), "");
00573     REG_KEY("TV Playback", ACTION_JUMPBKMRK, QT_TRANSLATE_NOOP("MythControls",
00574             "Jump to bookmark"), "K");
00575     REG_KEY("TV Playback", "FFWDSTICKY", QT_TRANSLATE_NOOP("MythControls",
00576             "Fast Forward (Sticky) or Forward one frame while paused"), ">,.");
00577     REG_KEY("TV Playback", "RWNDSTICKY", QT_TRANSLATE_NOOP("MythControls",
00578             "Rewind (Sticky) or Rewind one frame while paused"), ",,<");
00579     REG_KEY("TV Playback", "NEXTSOURCE", QT_TRANSLATE_NOOP("MythControls",
00580             "Next Video Source"), "Y");
00581     REG_KEY("TV Playback", "PREVSOURCE", QT_TRANSLATE_NOOP("MythControls",
00582             "Previous Video Source"), "");
00583     REG_KEY("TV Playback", "NEXTINPUT", QT_TRANSLATE_NOOP("MythControls",
00584             "Next Input"), "C");
00585     REG_KEY("TV Playback", "NEXTCARD", QT_TRANSLATE_NOOP("MythControls",
00586             "Next Card"), "");
00587     REG_KEY("TV Playback", "SKIPCOMMERCIAL", QT_TRANSLATE_NOOP("MythControls",
00588             "Skip Commercial"), "Z,End");
00589     REG_KEY("TV Playback", "SKIPCOMMBACK", QT_TRANSLATE_NOOP("MythControls",
00590             "Skip Commercial (Reverse)"), "Q,Home");
00591     REG_KEY("TV Playback", ACTION_JUMPSTART, QT_TRANSLATE_NOOP("MythControls",
00592             "Jump to the start of the recording."), "Ctrl+B");
00593     REG_KEY("TV Playback", "TOGGLEBROWSE", QT_TRANSLATE_NOOP("MythControls",
00594             "Toggle channel browse mode"), "O");
00595     REG_KEY("TV Playback", ACTION_TOGGLERECORD, QT_TRANSLATE_NOOP("MythControls",
00596             "Toggle recording status of current program"), "R");
00597     REG_KEY("TV Playback", ACTION_TOGGLEFAV, QT_TRANSLATE_NOOP("MythControls",
00598             "Toggle the current channel as a favorite"), "?");
00599     REG_KEY("TV Playback", ACTION_VOLUMEDOWN, QT_TRANSLATE_NOOP("MythControls",
00600             "Volume down"), "[,{,F10,Volume Down");
00601     REG_KEY("TV Playback", ACTION_VOLUMEUP, QT_TRANSLATE_NOOP("MythControls",
00602             "Volume up"), "],},F11,Volume Up");
00603     REG_KEY("TV Playback", ACTION_MUTEAUDIO, QT_TRANSLATE_NOOP("MythControls",
00604             "Mute"), "|,\\,F9,Volume Mute");
00605     REG_KEY("TV Playback", ACTION_SETVOLUME, QT_TRANSLATE_NOOP("MythControls",
00606             "Set the volume"), "");
00607     REG_KEY("TV Playback", "CYCLEAUDIOCHAN", QT_TRANSLATE_NOOP("MythControls",
00608             "Cycle audio channels"), "");
00609     REG_KEY("TV Playback", ACTION_TOGGLEUPMIX, QT_TRANSLATE_NOOP("MythControls",
00610             "Toggle audio upmixer"), "Ctrl+U");
00611     REG_KEY("TV Playback", "TOGGLEPIPMODE", QT_TRANSLATE_NOOP("MythControls",
00612             "Toggle Picture-in-Picture view"), "V");
00613     REG_KEY("TV Playback", "TOGGLEPBPMODE", QT_TRANSLATE_NOOP("MythControls",
00614             "Toggle Picture-by-Picture view"), "Ctrl+V");
00615     REG_KEY("TV Playback", "CREATEPIPVIEW", QT_TRANSLATE_NOOP("MythControls",
00616             "Create Picture-in-Picture view"), "");
00617     REG_KEY("TV Playback", "CREATEPBPVIEW", QT_TRANSLATE_NOOP("MythControls",
00618             "Create Picture-by-Picture view"), "");
00619     REG_KEY("TV Playback", "NEXTPIPWINDOW", QT_TRANSLATE_NOOP("MythControls",
00620             "Toggle active PIP/PBP window"), "B");
00621     REG_KEY("TV Playback", "SWAPPIP", QT_TRANSLATE_NOOP("MythControls",
00622             "Swap PBP/PIP Windows"), "N");
00623     REG_KEY("TV Playback", "TOGGLEPIPSTATE", QT_TRANSLATE_NOOP("MythControls",
00624             "Change PxP view"), "");
00625     REG_KEY("TV Playback", "TOGGLEASPECT", QT_TRANSLATE_NOOP("MythControls",
00626             "Toggle the video aspect ratio"), "Ctrl+W");
00627     REG_KEY("TV Playback", "TOGGLEFILL", QT_TRANSLATE_NOOP("MythControls",
00628             "Next Preconfigured Zoom mode"), "W");
00629     REG_KEY("TV Playback", ACTION_TOGGLESUBS, QT_TRANSLATE_NOOP("MythControls",
00630             "Toggle any captions"), "T");
00631     REG_KEY("TV Playback", ACTION_ENABLESUBS, QT_TRANSLATE_NOOP("MythControls",
00632             "Enable any captions"), "");
00633     REG_KEY("TV Playback", ACTION_DISABLESUBS, QT_TRANSLATE_NOOP("MythControls",
00634             "Disable any captions"), "");
00635     REG_KEY("TV Playback", "TOGGLETTC", QT_TRANSLATE_NOOP("MythControls",
00636             "Toggle Teletext Captions"),"");
00637     REG_KEY("TV Playback", "TOGGLESUBTITLE", QT_TRANSLATE_NOOP("MythControls",
00638             "Toggle Subtitles"), "");
00639     REG_KEY("TV Playback", "TOGGLECC608", QT_TRANSLATE_NOOP("MythControls",
00640             "Toggle VBI CC"), "");
00641     REG_KEY("TV Playback", "TOGGLECC708", QT_TRANSLATE_NOOP("MythControls",
00642             "Toggle ATSC CC"), "");
00643     REG_KEY("TV Playback", "TOGGLETTM", QT_TRANSLATE_NOOP("MythControls",
00644             "Toggle Teletext Menu"), "");
00645     REG_KEY("TV Playback", ACTION_TOGGLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
00646             "Toggle External Subtitles"), "");
00647     REG_KEY("TV Playback", ACTION_ENABLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
00648             "Enable External Subtitles"), "");
00649     REG_KEY("TV Playback", ACTION_DISABLEEXTTEXT, QT_TRANSLATE_NOOP("MythControls",
00650             "Disable External Subtitles"), "");
00651     REG_KEY("TV Playback", "TOGGLERAWTEXT", QT_TRANSLATE_NOOP("MythControls",
00652             "Toggle Text Subtitles"), "");
00653 
00654     REG_KEY("TV Playback", "SELECTAUDIO_0", QT_TRANSLATE_NOOP("MythControls",
00655             "Play audio track 1"), "");
00656     REG_KEY("TV Playback", "SELECTAUDIO_1", QT_TRANSLATE_NOOP("MythControls",
00657             "Play audio track 2"), "");
00658     REG_KEY("TV Playback", "SELECTSUBTITLE_0",QT_TRANSLATE_NOOP("MythControls",
00659             "Display subtitle 1"), "");
00660     REG_KEY("TV Playback", "SELECTSUBTITLE_1",QT_TRANSLATE_NOOP("MythControls",
00661             "Display subtitle 2"), "");
00662     REG_KEY("TV Playback", "SELECTRAWTEXT_0",QT_TRANSLATE_NOOP("MythControls",
00663             "Display Text Subtitle 1"), "");
00664     REG_KEY("TV Playback", "SELECTCC608_0", QT_TRANSLATE_NOOP("MythControls",
00665             "Display VBI CC1"), "");
00666     REG_KEY("TV Playback", "SELECTCC608_1", QT_TRANSLATE_NOOP("MythControls",
00667             "Display VBI CC2"), "");
00668     REG_KEY("TV Playback", "SELECTCC608_2", QT_TRANSLATE_NOOP("MythControls",
00669             "Display VBI CC3"), "");
00670     REG_KEY("TV Playback", "SELECTCC608_3", QT_TRANSLATE_NOOP("MythControls",
00671             "Display VBI CC4"), "");
00672     REG_KEY("TV Playback", "SELECTCC708_0", QT_TRANSLATE_NOOP("MythControls",
00673             "Display ATSC CC1"), "");
00674     REG_KEY("TV Playback", "SELECTCC708_1", QT_TRANSLATE_NOOP("MythControls",
00675             "Display ATSC CC2"), "");
00676     REG_KEY("TV Playback", "SELECTCC708_2", QT_TRANSLATE_NOOP("MythControls",
00677             "Display ATSC CC3"), "");
00678     REG_KEY("TV Playback", "SELECTCC708_3", QT_TRANSLATE_NOOP("MythControls",
00679             "Display ATSC CC4"), "");
00680     REG_KEY("TV Playback", ACTION_ENABLEFORCEDSUBS, QT_TRANSLATE_NOOP("MythControls",
00681             "Enable Forced Subtitles"), "");
00682     REG_KEY("TV Playback", ACTION_DISABLEFORCEDSUBS, QT_TRANSLATE_NOOP("MythControls",
00683             "Disable Forced Subtitles"), "");
00684 
00685     REG_KEY("TV Playback", "NEXTAUDIO", QT_TRANSLATE_NOOP("MythControls",
00686             "Next audio track"), "+");
00687     REG_KEY("TV Playback", "PREVAUDIO", QT_TRANSLATE_NOOP("MythControls",
00688             "Previous audio track"), "-");
00689     REG_KEY("TV Playback", "NEXTSUBTITLE", QT_TRANSLATE_NOOP("MythControls",
00690             "Next subtitle track"), "");
00691     REG_KEY("TV Playback", "PREVSUBTITLE", QT_TRANSLATE_NOOP("MythControls",
00692             "Previous subtitle track"), "");
00693     REG_KEY("TV Playback", "NEXTRAWTEXT", QT_TRANSLATE_NOOP("MythControls",
00694             "Next Text track"), "");
00695     REG_KEY("TV Playback", "PREVRAWTEXT", QT_TRANSLATE_NOOP("MythControls",
00696             "Previous Text track"), "");
00697     REG_KEY("TV Playback", "NEXTCC608", QT_TRANSLATE_NOOP("MythControls",
00698             "Next VBI CC track"), "");
00699     REG_KEY("TV Playback", "PREVCC608", QT_TRANSLATE_NOOP("MythControls",
00700             "Previous VBI CC track"), "");
00701     REG_KEY("TV Playback", "NEXTCC708", QT_TRANSLATE_NOOP("MythControls",
00702             "Next ATSC CC track"), "");
00703     REG_KEY("TV Playback", "PREVCC708", QT_TRANSLATE_NOOP("MythControls",
00704             "Previous ATSC CC track"), "");
00705     REG_KEY("TV Playback", "NEXTCC", QT_TRANSLATE_NOOP("MythControls",
00706             "Next of any captions"), "");
00707 
00708     REG_KEY("TV Playback", "NEXTSCAN", QT_TRANSLATE_NOOP("MythControls",
00709             "Next video scan overidemode"), "");
00710     REG_KEY("TV Playback", "QUEUETRANSCODE", QT_TRANSLATE_NOOP("MythControls",
00711             "Queue the current recording for transcoding"), "X");
00712     REG_KEY("TV Playback", "SPEEDINC", QT_TRANSLATE_NOOP("MythControls",
00713             "Increase the playback speed"), "U");
00714     REG_KEY("TV Playback", "SPEEDDEC", QT_TRANSLATE_NOOP("MythControls",
00715             "Decrease the playback speed"), "J");
00716     REG_KEY("TV Playback", "ADJUSTSTRETCH", QT_TRANSLATE_NOOP("MythControls",
00717             "Turn on time stretch control"), "A");
00718     REG_KEY("TV Playback", "STRETCHINC", QT_TRANSLATE_NOOP("MythControls",
00719             "Increase time stretch speed"), "");
00720     REG_KEY("TV Playback", "STRETCHDEC", QT_TRANSLATE_NOOP("MythControls",
00721             "Decrease time stretch speed"), "");
00722     REG_KEY("TV Playback", "TOGGLESTRETCH", QT_TRANSLATE_NOOP("MythControls",
00723             "Toggle time stretch speed"), "");
00724     REG_KEY("TV Playback", ACTION_TOGGELAUDIOSYNC,
00725             QT_TRANSLATE_NOOP("MythControls",
00726             "Turn on audio sync adjustment controls"), "");
00727     REG_KEY("TV Playback", ACTION_SETAUDIOSYNC,
00728             QT_TRANSLATE_NOOP("MythControls",
00729             "Set the audio sync adjustment"), "");
00730     REG_KEY("TV Playback", "TOGGLEPICCONTROLS",
00731             QT_TRANSLATE_NOOP("MythControls", "Playback picture adjustments"),
00732              "F");
00733     REG_KEY("TV Playback", ACTION_TOGGLENIGHTMODE,
00734             QT_TRANSLATE_NOOP("MythControls", "Toggle night mode"), "Ctrl+F");
00735     REG_KEY("TV Playback", ACTION_SETBRIGHTNESS,
00736             QT_TRANSLATE_NOOP("MythControls", "Set the picture brightness"), "");
00737     REG_KEY("TV Playback", ACTION_SETCONTRAST,
00738             QT_TRANSLATE_NOOP("MythControls", "Set the picture contrast"), "");
00739     REG_KEY("TV Playback", ACTION_SETCOLOUR,
00740             QT_TRANSLATE_NOOP("MythControls", "Set the picture color"), "");
00741     REG_KEY("TV Playback", ACTION_SETHUE,
00742             QT_TRANSLATE_NOOP("MythControls", "Set the picture hue"), "");
00743     REG_KEY("TV Playback", ACTION_TOGGLESTUDIOLEVELS,
00744             QT_TRANSLATE_NOOP("MythControls", "Playback picture adjustments"),
00745              "");
00746     REG_KEY("TV Playback", ACTION_TOGGLECHANCONTROLS,
00747             QT_TRANSLATE_NOOP("MythControls", "Recording picture adjustments "
00748             "for this channel"), "Ctrl+G");
00749     REG_KEY("TV Playback", ACTION_TOGGLERECCONTROLS,
00750             QT_TRANSLATE_NOOP("MythControls", "Recording picture adjustments "
00751             "for this recorder"), "G");
00752     REG_KEY("TV Playback", "CYCLECOMMSKIPMODE",
00753             QT_TRANSLATE_NOOP("MythControls", "Cycle Commercial Skip mode"),
00754             "");
00755     REG_KEY("TV Playback", ACTION_GUIDE, QT_TRANSLATE_NOOP("MythControls",
00756             "Show the Program Guide"), "S");
00757     REG_KEY("TV Playback", ACTION_FINDER, QT_TRANSLATE_NOOP("MythControls",
00758             "Show the Program Finder"), "#");
00759     REG_KEY("TV Playback", ACTION_TOGGLESLEEP, QT_TRANSLATE_NOOP("MythControls",
00760             "Toggle the Sleep Timer"), "F8");
00761     REG_KEY("TV Playback", ACTION_PLAY, QT_TRANSLATE_NOOP("MythControls", "Play"),
00762             "Ctrl+P");
00763     REG_KEY("TV Playback", ACTION_JUMPPREV, QT_TRANSLATE_NOOP("MythControls",
00764             "Jump to previously played recording"), "");
00765     REG_KEY("TV Playback", ACTION_JUMPREC, QT_TRANSLATE_NOOP("MythControls",
00766             "Display menu of recorded programs to jump to"), "");
00767     REG_KEY("TV Playback", ACTION_VIEWSCHEDULED, QT_TRANSLATE_NOOP("MythControls",
00768             "Display scheduled recording list"), "");
00769     REG_KEY("TV Playback", ACTION_SIGNALMON, QT_TRANSLATE_NOOP("MythControls",
00770             "Monitor Signal Quality"), "Alt+F7");
00771     REG_KEY("TV Playback", ACTION_JUMPTODVDROOTMENU,
00772             QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Root Menu"), "");
00773     REG_KEY("TV Playback", ACTION_JUMPTOPOPUPMENU,
00774             QT_TRANSLATE_NOOP("MythControls", "Jump to the Popup Menu"), "");
00775     REG_KEY("TV Playback", ACTION_JUMPTODVDCHAPTERMENU,
00776             QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Chapter Menu"), "");
00777     REG_KEY("TV Playback", ACTION_JUMPTODVDTITLEMENU,
00778             QT_TRANSLATE_NOOP("MythControls", "Jump to the DVD Title Menu"), "");
00779     REG_KEY("TV Playback", ACTION_EXITSHOWNOPROMPTS,
00780             QT_TRANSLATE_NOOP("MythControls", "Exit Show without any prompts"),
00781             "");
00782     REG_KEY("TV Playback", ACTION_JUMPCHAPTER, QT_TRANSLATE_NOOP("MythControls",
00783             "Jump to a chapter"), "");
00784     REG_KEY("TV Playback", ACTION_SWITCHTITLE, QT_TRANSLATE_NOOP("MythControls",
00785             "Switch title"), "");
00786     REG_KEY("TV Playback", ACTION_SWITCHANGLE, QT_TRANSLATE_NOOP("MythControls",
00787             "Switch angle"), "");
00788 
00789     /* Interactive Television keys */
00790     REG_KEY("TV Playback", ACTION_MENURED,    QT_TRANSLATE_NOOP("MythControls",
00791             "Menu Red"),    "F2");
00792     REG_KEY("TV Playback", ACTION_MENUGREEN,  QT_TRANSLATE_NOOP("MythControls",
00793             "Menu Green"),  "F3");
00794     REG_KEY("TV Playback", ACTION_MENUYELLOW, QT_TRANSLATE_NOOP("MythControls",
00795             "Menu Yellow"), "F4");
00796     REG_KEY("TV Playback", ACTION_MENUBLUE,   QT_TRANSLATE_NOOP("MythControls",
00797             "Menu Blue"),   "F5");
00798     REG_KEY("TV Playback", ACTION_TEXTEXIT,   QT_TRANSLATE_NOOP("MythControls",
00799             "Menu Exit"),   "F6");
00800     REG_KEY("TV Playback", ACTION_MENUTEXT,   QT_TRANSLATE_NOOP("MythControls",
00801             "Menu Text"),   "F7");
00802     REG_KEY("TV Playback", ACTION_MENUEPG,    QT_TRANSLATE_NOOP("MythControls",
00803             "Menu EPG"),    "F12");
00804 
00805     /* Editing keys */
00806     REG_KEY("TV Editing", ACTION_CLEARMAP,    QT_TRANSLATE_NOOP("MythControls",
00807             "Clear editing cut points"), "C,Q,Home");
00808     REG_KEY("TV Editing", ACTION_INVERTMAP,   QT_TRANSLATE_NOOP("MythControls",
00809             "Invert Begin/End cut points"),"I");
00810     REG_KEY("TV Editing", ACTION_SAVEMAP,     QT_TRANSLATE_NOOP("MythControls",
00811             "Save cuts"),"");
00812     REG_KEY("TV Editing", ACTION_LOADCOMMSKIP,QT_TRANSLATE_NOOP("MythControls",
00813             "Load cuts from detected commercials"), "Z,End");
00814     REG_KEY("TV Editing", ACTION_NEXTCUT,     QT_TRANSLATE_NOOP("MythControls",
00815             "Jump to the next cut point"), "PgDown");
00816     REG_KEY("TV Editing", ACTION_PREVCUT,     QT_TRANSLATE_NOOP("MythControls",
00817             "Jump to the previous cut point"), "PgUp");
00818     REG_KEY("TV Editing", ACTION_BIGJUMPREW,  QT_TRANSLATE_NOOP("MythControls",
00819             "Jump back 10x the normal amount"), ",,<");
00820     REG_KEY("TV Editing", ACTION_BIGJUMPFWD,  QT_TRANSLATE_NOOP("MythControls",
00821             "Jump forward 10x the normal amount"), ">,.");
00822 
00823     /* Teletext keys */
00824     REG_KEY("Teletext Menu", ACTION_NEXTPAGE,    QT_TRANSLATE_NOOP("MythControls",
00825             "Next Page"),             "Down");
00826     REG_KEY("Teletext Menu", ACTION_PREVPAGE,    QT_TRANSLATE_NOOP("MythControls",
00827             "Previous Page"),         "Up");
00828     REG_KEY("Teletext Menu", ACTION_NEXTSUBPAGE, QT_TRANSLATE_NOOP("MythControls",
00829             "Next Subpage"),          "Right");
00830     REG_KEY("Teletext Menu", ACTION_PREVSUBPAGE, QT_TRANSLATE_NOOP("MythControls",
00831             "Previous Subpage"),      "Left");
00832     REG_KEY("Teletext Menu", ACTION_TOGGLETT,    QT_TRANSLATE_NOOP("MythControls",
00833             "Toggle Teletext"),       "T");
00834     REG_KEY("Teletext Menu", ACTION_MENURED,     QT_TRANSLATE_NOOP("MythControls",
00835             "Menu Red"),              "F2");
00836     REG_KEY("Teletext Menu", ACTION_MENUGREEN,   QT_TRANSLATE_NOOP("MythControls",
00837             "Menu Green"),            "F3");
00838     REG_KEY("Teletext Menu", ACTION_MENUYELLOW,  QT_TRANSLATE_NOOP("MythControls",
00839             "Menu Yellow"),           "F4");
00840     REG_KEY("Teletext Menu", ACTION_MENUBLUE,    QT_TRANSLATE_NOOP("MythControls",
00841             "Menu Blue"),             "F5");
00842     REG_KEY("Teletext Menu", ACTION_MENUWHITE,   QT_TRANSLATE_NOOP("MythControls",
00843             "Menu White"),            "F6");
00844     REG_KEY("Teletext Menu", ACTION_TOGGLEBACKGROUND,
00845             QT_TRANSLATE_NOOP("MythControls", "Toggle Background"), "F7");
00846     REG_KEY("Teletext Menu", ACTION_REVEAL,      QT_TRANSLATE_NOOP("MythControls",
00847             "Reveal hidden Text"),    "F8");
00848 
00849     /* Visualisations */
00850     REG_KEY("TV Playback", ACTION_TOGGLEVISUALISATION,
00851             QT_TRANSLATE_NOOP("MythControls", "Toggle audio visualisation"), "");
00852 
00853     /* OSD playback information screen */
00854     REG_KEY("TV Playback", ACTION_TOGGLEOSDDEBUG,
00855             QT_TRANSLATE_NOOP("MythControls", "Toggle OSD playback information"), "");
00856 
00857     /* 3D/Frame compatible/Stereoscopic TV */
00858     REG_KEY("TV Playback", ACTION_3DNONE,
00859             QT_TRANSLATE_NOOP("MythControls", "No 3D"), "");
00860     REG_KEY("TV Playback", ACTION_3DSIDEBYSIDE,
00861             QT_TRANSLATE_NOOP("MythControls", "3D Side by Side"), "");
00862     REG_KEY("TV Playback", ACTION_3DSIDEBYSIDEDISCARD,
00863             QT_TRANSLATE_NOOP("MythControls", "Discard 3D Side by Side"), "");
00864     REG_KEY("TV Playback", ACTION_3DTOPANDBOTTOM,
00865             QT_TRANSLATE_NOOP("MythControls", "3D Top and Bottom"), "");
00866     REG_KEY("TV Playback", ACTION_3DTOPANDBOTTOMDISCARD,
00867             QT_TRANSLATE_NOOP("MythControls", "Discard 3D Top and Bottom"), "");
00868 
00869 /*
00870   keys already used:
00871 
00872   Global:           I   M              0123456789
00873   Playback: ABCDEFGH JK  NOPQRSTUVWXYZ
00874   Frontend:   CD          OP R  U  XY  01 3   7 9
00875   Editing:    C E   I       Q        Z
00876   Teletext:                    T
00877 
00878   Playback: <>,.?/|[]{}\+-*#^
00879   Frontend: <>,.?/
00880   Editing:  <>,.
00881 
00882   Global:   PgDown, PgUp,  Right, Left, Home, End, Up, Down,
00883   Playback: PgDown, PgUp,  Right, Left, Home, End, Up, Down, Backspace,
00884   Frontend:                Right, Left, Home, End
00885   Editing:  PgDown, PgUp,               Home, End
00886   Teletext:                Right, Left,            Up, Down,
00887 
00888   Global:   Return, Enter, Space, Esc
00889 
00890   Global:   F1,
00891   Playback:                   F7,F8,F9,F10,F11
00892   Teletext     F2,F3,F4,F5,F6,F7,F8
00893   ITV          F2,F3,F4,F5,F6,F7,F12
00894 
00895   Playback: Ctrl-B,Ctrl-G,Ctrl-Y,Ctrl-U
00896 */
00897 }
00898 
00899 void TV::ReloadKeys(void)
00900 {
00901     MythMainWindow *mainWindow = GetMythMainWindow();
00902     mainWindow->ClearKeyContext("TV Frontend");
00903     mainWindow->ClearKeyContext("TV Playback");
00904     mainWindow->ClearKeyContext("TV Editing");
00905     mainWindow->ClearKeyContext("Teletext Menu");
00906     InitKeys();
00907 }
00908 
00912 TV::TV(void)
00913     : // Configuration variables from database
00914       baseFilters(""),
00915       db_channel_format("<num> <sign>"),
00916       db_idle_timeout(0),
00917       db_playback_exit_prompt(0),   db_autoexpire_default(0),
00918       db_auto_set_watched(false),   db_end_of_rec_exit_prompt(false),
00919       db_jump_prefer_osd(true),     db_use_gui_size_for_tv(false),
00920       db_start_in_guide(false),     db_toggle_bookmark(false),
00921       db_run_jobs_on_remote(false), db_continue_embedded(false),
00922       db_use_fixed_size(true),      db_browse_always(false),
00923       db_browse_all_tuners(false),
00924       db_use_channel_groups(false), db_remember_last_channel_group(false),
00925 
00926       tryUnflaggedSkip(false),
00927       smartForward(false),
00928       ff_rew_repos(1.0f), ff_rew_reverse(false),
00929       jumped_back(false),
00930       vbimode(VBIMode::None),
00931       // State variables
00932       switchToInputId(0),
00933       wantsToQuit(true),
00934       stretchAdjustment(false),
00935       audiosyncAdjustment(false),
00936       subtitleZoomAdjustment(false),
00937       editmode(false),     zoomMode(false),
00938       sigMonMode(false),
00939       endOfRecording(false),
00940       requestDelete(false),  allowRerecord(false),
00941       doSmartForward(false),
00942       queuedTranscode(false),
00943       adjustingPicture(kAdjustingPicture_None),
00944       adjustingPictureAttribute(kPictureAttribute_None),
00945       askAllowLock(QMutex::Recursive),
00946       // Channel Editing
00947       chanEditMapLock(QMutex::Recursive),
00948       ddMapSourceId(0), ddMapLoader(new DDLoader(this)),
00949       // Sleep Timer
00950       sleep_index(0), sleepTimerId(0), sleepDialogTimerId(0),
00951       // Idle Timer
00952       idleTimerId(0), idleDialogTimerId(0),
00953       // CC/Teletext input state variables
00954       ccInputMode(false),
00955       // Arbritary seek input state variables
00956       asInputMode(false),
00957       // Channel changing state variables
00958       queuedChanNum(""),
00959       lockTimerOn(false),
00960       // channel browsing
00961       browsehelper(NULL),
00962       // Program Info for currently playing video
00963       lastProgram(NULL),
00964       inPlaylist(false), underNetworkControl(false),
00965       // Jump to program stuff
00966       jumpToProgramPIPState(kPIPOff),
00967       jumpToProgram(false),
00968       // Video Player currently receiving UI input
00969       playerActive(-1),
00970       noHardwareDecoders(false),
00971       //Recorder switching info
00972       switchToRec(NULL),
00973       // LCD Info
00974       lcdTitle(""), lcdSubtitle(""), lcdCallsign(""),
00975       // Window info (GUI is optional, transcoding, preview img, etc)
00976       myWindow(NULL),               weDisabledGUI(false),
00977       disableDrawUnusedRects(false),
00978       isEmbedded(false),            ignoreKeyPresses(false),
00979       // Timers
00980       lcdTimerId(0),                lcdVolumeTimerId(0),
00981       networkControlTimerId(0),     jumpMenuTimerId(0),
00982       pipChangeTimerId(0),
00983       switchToInputTimerId(0),      ccInputTimerId(0),
00984       asInputTimerId(0),            queueInputTimerId(0),
00985       browseTimerId(0),             updateOSDPosTimerId(0),
00986       updateOSDDebugTimerId(0),
00987       endOfPlaybackTimerId(0),      embedCheckTimerId(0),
00988       endOfRecPromptTimerId(0),     videoExitDialogTimerId(0),
00989       pseudoChangeChanTimerId(0),   speedChangeTimerId(0),
00990       errorRecoveryTimerId(0),      exitPlayerTimerId(0)
00991 {
00992     LOG(VB_GENERAL, LOG_INFO, LOC + "Creating TV object");
00993     ctorTime.start();
00994 
00995     setObjectName("TV");
00996     keyRepeatTimer.start();
00997 
00998     sleep_times.push_back(SleepTimerInfo(QObject::tr("Off"),       0));
00999     sleep_times.push_back(SleepTimerInfo(QObject::tr("30m"),   30*60));
01000     sleep_times.push_back(SleepTimerInfo(QObject::tr("1h"),    60*60));
01001     sleep_times.push_back(SleepTimerInfo(QObject::tr("1h30m"), 90*60));
01002     sleep_times.push_back(SleepTimerInfo(QObject::tr("2h"),   120*60));
01003 
01004     playerLock.lockForWrite();
01005     player.push_back(new PlayerContext(kPlayerInUseID));
01006     playerActive = 0;
01007     playerLock.unlock();
01008 
01009     InitFromDB();
01010 
01011     LOG(VB_PLAYBACK, LOG_INFO, LOC + "Finished creating TV object");
01012 }
01013 
01014 void TV::InitFromDB(void)
01015 {
01016     QMap<QString,QString> kv;
01017     kv["LiveTVIdleTimeout"]        = "0";
01018     kv["BrowseMaxForward"]         = "240";
01019     kv["PlaybackExitPrompt"]       = "0";
01020     kv["AutomaticSetWatched"]      = "0";
01021     kv["EndOfRecordingExitPrompt"] = "0";
01022     kv["JumpToProgramOSD"]         = "1";
01023     kv["GuiSizeForTV"]             = "0";
01024     kv["WatchTVGuide"]             = "0";
01025     kv["AltClearSavedPosition"]    = "1";
01026     kv["JobsRunOnRecordHost"]      = "0";
01027     kv["ContinueEmbeddedTVPlay"]   = "0";
01028     kv["UseFixedWindowSize"]       = "1";
01029     kv["PersistentBrowseMode"]     = "0";
01030     kv["BrowseAllTuners"]          = "0";
01031     kv["ChannelOrdering"]          = "channum";
01032 
01033     kv["CustomFilters"]            = "";
01034     kv["ChannelFormat"]            = "<num> <sign>";
01035     kv["TimeFormat"]               = "h:mm AP";
01036     kv["ShortDateFormat"]          = "M/d";
01037 
01038     kv["TryUnflaggedSkip"]         = "0";
01039 
01040     kv["ChannelGroupDefault"]      = "-1";
01041     kv["BrowseChannelGroup"]       = "0";
01042     kv["SmartForward"]             = "0";
01043     kv["FFRewReposTime"]           = "100";
01044     kv["FFRewReverse"]             = "1";
01045 
01046     kv["BrowseChannelGroup"]       = "0";
01047     kv["ChannelGroupDefault"]      = "-1";
01048     kv["ChannelGroupRememberLast"] = "0";
01049 
01050     kv["VbiFormat"]                = "";
01051     kv["DecodeVBIFormat"]          = "";
01052 
01053     int ff_rew_def[8] = { 3, 5, 10, 20, 30, 60, 120, 180 };
01054     for (uint i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
01055         kv[QString("FFRewSpeed%1").arg(i)] = QString::number(ff_rew_def[i]);
01056 
01057     MythDB::getMythDB()->GetSettings(kv);
01058 
01059     QString db_time_format;
01060     QString db_short_date_format;
01061     QString db_channel_ordering;
01062     uint    db_browse_max_forward;
01063 
01064     // convert from minutes to ms.
01065     db_idle_timeout        = kv["LiveTVIdleTimeout"].toInt() * 60 * 1000;
01066     db_browse_max_forward  = kv["BrowseMaxForward"].toInt() * 60;
01067     db_playback_exit_prompt= kv["PlaybackExitPrompt"].toInt();
01068     db_auto_set_watched    = kv["AutomaticSetWatched"].toInt();
01069     db_end_of_rec_exit_prompt = kv["EndOfRecordingExitPrompt"].toInt();
01070     db_jump_prefer_osd     = kv["JumpToProgramOSD"].toInt();
01071     db_use_gui_size_for_tv = kv["GuiSizeForTV"].toInt();
01072     db_start_in_guide      = kv["WatchTVGuide"].toInt();
01073     db_toggle_bookmark     = kv["AltClearSavedPosition"].toInt();
01074     db_run_jobs_on_remote  = kv["JobsRunOnRecordHost"].toInt();
01075     db_continue_embedded   = kv["ContinueEmbeddedTVPlay"].toInt();
01076     db_use_fixed_size      = kv["UseFixedWindowSize"].toInt();
01077     db_browse_always       = kv["PersistentBrowseMode"].toInt();
01078     db_browse_all_tuners   = kv["BrowseAllTuners"].toInt();
01079     db_channel_ordering    = kv["ChannelOrdering"];
01080     baseFilters           += kv["CustomFilters"];
01081     db_channel_format      = kv["ChannelFormat"];
01082     db_time_format         = kv["TimeFormat"];
01083     db_short_date_format   = kv["ShortDateFormat"];
01084     tryUnflaggedSkip       = kv["TryUnflaggedSkip"].toInt();
01085     smartForward           = kv["SmartForward"].toInt();
01086     ff_rew_repos           = kv["FFRewReposTime"].toFloat() * 0.01f;
01087     ff_rew_reverse         = kv["FFRewReverse"].toInt();
01088 
01089     db_use_channel_groups  = kv["BrowseChannelGroup"].toInt();
01090     db_remember_last_channel_group = kv["ChannelGroupRememberLast"].toInt();
01091     channelGroupId         = kv["ChannelGroupDefault"].toInt();
01092 
01093     QString beVBI          = kv["VbiFormat"];
01094     QString feVBI          = kv["DecodeVBIFormat"];
01095 
01096     RecordingRule record;
01097     record.LoadTemplate("Default");
01098     db_autoexpire_default  = record.m_autoExpire;
01099 
01100     if (db_use_channel_groups)
01101     {
01102         db_channel_groups  = ChannelGroup::GetChannelGroups();
01103         if (channelGroupId > -1)
01104         {
01105             channelGroupChannelList = ChannelUtil::GetChannels(
01106                 0, true, "channum, callsign", channelGroupId);
01107             ChannelUtil::SortChannels(
01108                 channelGroupChannelList, "channum", true);
01109         }
01110     }
01111 
01112     for (uint i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
01113         ff_rew_speeds.push_back(kv[QString("FFRewSpeed%1").arg(i)].toInt());
01114 
01115     // process it..
01116     browsehelper = new TVBrowseHelper(
01117         this,
01118         db_time_format,          db_short_date_format,
01119         db_browse_max_forward,   db_browse_all_tuners,
01120         db_use_channel_groups,   db_channel_ordering);
01121 
01122     vbimode = VBIMode::Parse(!feVBI.isEmpty() ? feVBI : beVBI);
01123 
01124     gCoreContext->addListener(this);
01125 
01126     QMutexLocker lock(&initFromDBLock);
01127     initFromDBDone = true;
01128     initFromDBWait.wakeAll();
01129 }
01130 
01137 bool TV::Init(bool createWindow)
01138 {
01139     LOG(VB_PLAYBACK, LOG_INFO, LOC + "Init -- begin");
01140 
01141     if (createWindow)
01142     {
01143         bool fullscreen = !gCoreContext->GetNumSetting("GuiSizeForTV", 0);
01144         bool switchMode = gCoreContext->GetNumSetting("UseVideoModes", 0);
01145 
01146         saved_gui_bounds = QRect(GetMythMainWindow()->geometry().topLeft(),
01147                                  GetMythMainWindow()->size());
01148 
01149         // adjust for window manager wierdness.
01150         {
01151             int xbase, width, ybase, height;
01152             float wmult, hmult;
01153             GetMythUI()->GetScreenSettings(xbase, width, wmult,
01154                                            ybase, height, hmult);
01155             if ((abs(saved_gui_bounds.x()-xbase) < 3) &&
01156                 (abs(saved_gui_bounds.y()-ybase) < 3))
01157             {
01158                 saved_gui_bounds = QRect(QPoint(xbase, ybase),
01159                                          GetMythMainWindow()->size());
01160             }
01161         }
01162 
01163         // if width && height are zero users expect fullscreen playback
01164         if (!fullscreen)
01165         {
01166             int gui_width = 0, gui_height = 0;
01167             gCoreContext->GetResolutionSetting("Gui", gui_width, gui_height);
01168             fullscreen |= (0 == gui_width && 0 == gui_height);
01169         }
01170 
01171         player_bounds = saved_gui_bounds;
01172         if (fullscreen)
01173         {
01174             int xbase, width, ybase, height;
01175             GetMythUI()->GetScreenBounds(xbase, ybase, width, height);
01176             player_bounds = QRect(xbase, ybase, width, height);
01177         }
01178 
01179         // main window sizing
01180         int maxWidth = 1920, maxHeight = 1440;
01181         if (switchMode)
01182         {
01183             DisplayRes *display_res = DisplayRes::GetDisplayRes();
01184             if(display_res)
01185             {
01186                 // The very first Resize needs to be the maximum possible
01187                 // desired res, because X will mask off anything outside
01188                 // the initial dimensions
01189                 maxWidth = display_res->GetMaxWidth();
01190                 maxHeight = display_res->GetMaxHeight();
01191 
01192                 // bit of a hack, but it's ok if the window is too
01193                 // big in fullscreen mode
01194                 if (fullscreen)
01195                 {
01196                     player_bounds.setSize(QSize(maxWidth, maxHeight));
01197 
01198                     // resize possibly avoids a bug on some systems
01199                     GetMythMainWindow()->setGeometry(player_bounds);
01200                     GetMythMainWindow()->ResizePainterWindow(player_bounds.size());
01201                 }
01202             }
01203         }
01204 
01205         // player window sizing
01206         MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack();
01207 
01208         myWindow = new TvPlayWindow(mainStack, "Playback");
01209 
01210         if (myWindow->Create())
01211         {
01212             mainStack->AddScreen(myWindow, false);
01213             LOG(VB_GENERAL, LOG_INFO, LOC + "Created TvPlayWindow.");
01214         }
01215         else
01216         {
01217             delete myWindow;
01218             myWindow = NULL;
01219         }
01220 
01221         MythMainWindow *mainWindow = GetMythMainWindow();
01222         if (mainWindow->GetPaintWindow())
01223             mainWindow->GetPaintWindow()->update();
01224         mainWindow->installEventFilter(this);
01225         qApp->processEvents();
01226     }
01227 
01228     {
01229         QMutexLocker locker(&initFromDBLock);
01230         while (!initFromDBDone)
01231         {
01232             qApp->processEvents();
01233             initFromDBWait.wait(&initFromDBLock, 50);
01234         }
01235     }
01236 
01237     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
01238     mctx->ff_rew_state = 0;
01239     mctx->ff_rew_index = kInitFFRWSpeed;
01240     mctx->ff_rew_speed = 0;
01241     mctx->ts_normal    = 1.0f;
01242     ReturnPlayerLock(mctx);
01243 
01244     sleep_index = 0;
01245 
01246     SetUpdateOSDPosition(false);
01247 
01248     const PlayerContext *ctx = GetPlayerReadLock(0, __FILE__, __LINE__);
01249     ClearInputQueues(ctx, false);
01250     ReturnPlayerLock(ctx);
01251 
01252     switchToRec = NULL;
01253     SetExitPlayer(false, false);
01254 
01255     errorRecoveryTimerId = StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
01256     lcdTimerId           = StartTimer(1, __LINE__);
01257     speedChangeTimerId   = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
01258 
01259     LOG(VB_PLAYBACK, LOG_INFO, LOC + "Init -- end");
01260     return true;
01261 }
01262 
01263 TV::~TV(void)
01264 {
01265     LOG(VB_PLAYBACK, LOG_INFO, "TV::~TV() -- begin");
01266 
01267     if (browsehelper)
01268         browsehelper->Stop();
01269 
01270     gCoreContext->removeListener(this);
01271 
01272     if (GetMythMainWindow() && weDisabledGUI)
01273         GetMythMainWindow()->PopDrawDisabled();
01274 
01275     if (myWindow)
01276     {
01277         myWindow->Close();
01278         myWindow = NULL;
01279     }
01280 
01281     LOG(VB_PLAYBACK, LOG_INFO, "TV::~TV() -- lock");
01282 
01283     // restore window to gui size and position
01284     MythMainWindow* mwnd = GetMythMainWindow();
01285     mwnd->setGeometry(saved_gui_bounds);
01286     mwnd->setFixedSize(saved_gui_bounds.size());
01287     mwnd->ResizePainterWindow(saved_gui_bounds.size());
01288     mwnd->show();
01289     if (!db_use_gui_size_for_tv)
01290         mwnd->move(saved_gui_bounds.topLeft());
01291 
01292     if (lastProgram)
01293         delete lastProgram;
01294 
01295     if (LCD *lcd = LCD::Get())
01296     {
01297         lcd->setFunctionLEDs(FUNC_TV, false);
01298         lcd->setFunctionLEDs(FUNC_MOVIE, false);
01299         lcd->switchToTime();
01300     }
01301 
01302     if (ddMapLoader)
01303     {
01304         ddMapLoader->wait();
01305 
01306         if (ddMapSourceId)
01307         {
01308             ddMapLoader->SetParent(NULL);
01309             ddMapLoader->SetSourceID(ddMapSourceId);
01310             ddMapLoader->setAutoDelete(true);
01311             MThreadPool::globalInstance()->start(ddMapLoader, "DDLoadMapPost");
01312         }
01313         else
01314         {
01315             delete ddMapLoader;
01316         }
01317 
01318         ddMapSourceId = 0;
01319         ddMapLoader = NULL;
01320     }
01321 
01322     if (browsehelper)
01323     {
01324         delete browsehelper;
01325         browsehelper = NULL;
01326     }
01327 
01328     PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
01329     while (!player.empty())
01330     {
01331         delete player.back();
01332         player.pop_back();
01333     }
01334     ReturnPlayerLock(mctx);
01335 
01336     if (browsehelper)
01337     {
01338         delete browsehelper;
01339         browsehelper = NULL;
01340     }
01341 
01342     LOG(VB_PLAYBACK, LOG_INFO, "TV::~TV() -- end");
01343 }
01344 
01348 void TV::PlaybackLoop(void)
01349 {
01350     while (true)
01351     {
01352         qApp->processEvents();
01353 
01354         TVState state = GetState(0);
01355         if ((kState_Error == state) || (kState_None == state))
01356             return;
01357 
01358         if (kState_ChangingState == state)
01359             continue;
01360 
01361         int count = player.size();
01362         for (int i = 0; i < count; i++)
01363         {
01364             const PlayerContext *mctx = GetPlayerReadLock(i, __FILE__, __LINE__);
01365             if (mctx)
01366             {
01367                 mctx->LockDeletePlayer(__FILE__, __LINE__);
01368                 if (mctx->player && !mctx->player->IsErrored())
01369                 {
01370                     mctx->player->EventLoop();
01371                     mctx->player->VideoLoop();
01372                 }
01373                 mctx->UnlockDeletePlayer(__FILE__, __LINE__);
01374             }
01375             ReturnPlayerLock(mctx);
01376         }
01377     }
01378 }
01379 
01383 void TV::UpdateChannelList(int groupID)
01384 {
01385     if (!db_use_channel_groups)
01386         return;
01387 
01388     QMutexLocker locker(&channelGroupLock);
01389     if (groupID == channelGroupId)
01390         return;
01391 
01392     DBChanList list;
01393     if (groupID != -1)
01394     {
01395         list = ChannelUtil::GetChannels(
01396             0, true, "channum, callsign", groupID);
01397         ChannelUtil::SortChannels(list, "channum", true);
01398     }
01399 
01400     channelGroupId = groupID;
01401     channelGroupChannelList = list;
01402 
01403     if (db_remember_last_channel_group)
01404         gCoreContext->SaveSetting("ChannelGroupDefault", channelGroupId);
01405 }
01406 
01410 TVState TV::GetState(int player_idx) const
01411 {
01412     TVState ret = kState_ChangingState;
01413     const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
01414     ret = GetState(ctx);
01415     ReturnPlayerLock(ctx);
01416     return ret;
01417 }
01418 
01419 // XXX what about subtitlezoom?
01420 void TV::GetStatus(void)
01421 {
01422     QVariantMap status;
01423 
01424     const PlayerContext *ctx = GetPlayerReadLock(-1, __FILE__, __LINE__);
01425 
01426     status.insert("state", StateToString(GetState(ctx)));
01427     ctx->LockPlayingInfo(__FILE__, __LINE__);
01428     if (ctx->playingInfo)
01429     {
01430         status.insert("title", ctx->playingInfo->GetTitle());
01431         status.insert("subtitle", ctx->playingInfo->GetSubtitle());
01432         status.insert("starttime",
01433                            ctx->playingInfo->GetRecordingStartTime()
01434                            .toUTC().toString("yyyy-MM-ddThh:mm:ssZ"));
01435         status.insert("chanid",
01436                            QString::number(ctx->playingInfo->GetChanID()));
01437         status.insert("programid", ctx->playingInfo->GetProgramID());
01438     }
01439     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
01440     osdInfo info;
01441     ctx->CalcPlayerSliderPosition(info);
01442     ctx->LockDeletePlayer(__FILE__, __LINE__);
01443     if (ctx->player)
01444     {
01445         if (!info.text["totalchapters"].isEmpty())
01446         {
01447             QList<long long> chapters;
01448             ctx->player->GetChapterTimes(chapters);
01449             QVariantList var;
01450             foreach (long long chapter, chapters)
01451                 var << QVariant(chapter);
01452             status.insert("chaptertimes", var);
01453         }
01454 
01455         uint capmode = ctx->player->GetCaptionMode();
01456         QVariantMap tracks;
01457 
01458         QStringList list = ctx->player->GetTracks(kTrackTypeSubtitle);
01459         int currenttrack = -1;
01460         if (!list.isEmpty() && (kDisplayAVSubtitle == capmode))
01461             currenttrack = ctx->player->GetTrack(kTrackTypeSubtitle);
01462         for (int i = 0; i < list.size(); i++)
01463         {
01464             if (i == currenttrack)
01465                 status.insert("currentsubtitletrack", list[i]);
01466             tracks.insert("SELECTSUBTITLE_" + QString::number(i), list[i]);
01467         }
01468 
01469         list = ctx->player->GetTracks(kTrackTypeTeletextCaptions);
01470         currenttrack = -1;
01471         if (!list.isEmpty() && (kDisplayTeletextCaptions == capmode))
01472             currenttrack = ctx->player->GetTrack(kTrackTypeTeletextCaptions);
01473         for (int i = 0; i < list.size(); i++)
01474         {
01475             if (i == currenttrack)
01476                 status.insert("currentsubtitletrack", list[i]);
01477             tracks.insert("SELECTTTC_" + QString::number(i), list[i]);
01478         }
01479 
01480         list = ctx->player->GetTracks(kTrackTypeCC708);
01481         currenttrack = -1;
01482         if (!list.isEmpty() && (kDisplayCC708 == capmode))
01483             currenttrack = ctx->player->GetTrack(kTrackTypeCC708);
01484         for (int i = 0; i < list.size(); i++)
01485         {
01486             if (i == currenttrack)
01487                 status.insert("currentsubtitletrack", list[i]);
01488             tracks.insert("SELECTCC708_" + QString::number(i), list[i]);
01489         }
01490 
01491         list = ctx->player->GetTracks(kTrackTypeCC608);
01492         currenttrack = -1;
01493         if (!list.isEmpty() && (kDisplayCC608 == capmode))
01494             currenttrack = ctx->player->GetTrack(kTrackTypeCC608);
01495         for (int i = 0; i < list.size(); i++)
01496         {
01497             if (i == currenttrack)
01498                 status.insert("currentsubtitletrack", list[i]);
01499             tracks.insert("SELECTCC608_" + QString::number(i), list[i]);
01500         }
01501 
01502         list = ctx->player->GetTracks(kTrackTypeRawText);
01503         currenttrack = -1;
01504         if (!list.isEmpty() && (kDisplayRawTextSubtitle == capmode))
01505             currenttrack = ctx->player->GetTrack(kTrackTypeRawText);
01506         for (int i = 0; i < list.size(); i++)
01507         {
01508             if (i == currenttrack)
01509                 status.insert("currentsubtitletrack", list[i]);
01510             tracks.insert("SELECTRAWTEXT_" + QString::number(i), list[i]);
01511         }
01512 
01513         if (ctx->player->HasTextSubtitles())
01514         {
01515             if (kDisplayTextSubtitle == capmode)
01516                 status.insert("currentsubtitletrack", tr("External Subtitles"));
01517             tracks.insert(ACTION_ENABLEEXTTEXT, tr("External Subtitles"));
01518         }
01519 
01520         status.insert("totalsubtitletracks", tracks.size());
01521         if (!tracks.isEmpty())
01522             status.insert("subtitletracks", tracks);
01523 
01524         tracks.clear();
01525         list = ctx->player->GetTracks(kTrackTypeAudio);
01526         currenttrack = ctx->player->GetTrack(kTrackTypeAudio);
01527         for (int i = 0; i < list.size(); i++)
01528         {
01529             if (i == currenttrack)
01530                 status.insert("currentaudiotrack", list[i]);
01531             tracks.insert("SELECTAUDIO_" + QString::number(i), list[i]);
01532         }
01533 
01534         status.insert("totalaudiotracks", tracks.size());
01535         if (!tracks.isEmpty())
01536             status.insert("audiotracks", tracks);
01537 
01538         status.insert("playspeed", ctx->player->GetPlaySpeed());
01539         status.insert("audiosyncoffset", (long long)ctx->player->GetAudioTimecodeOffset());
01540         if (ctx->player->GetAudio()->ControlsVolume())
01541         {
01542             status.insert("volume", ctx->player->GetVolume());
01543             status.insert("mute",   ctx->player->GetMuteState());
01544         }
01545         if (ctx->player->GetVideoOutput())
01546         {
01547             VideoOutput *vo = ctx->player->GetVideoOutput();
01548             PictureAttributeSupported supp =
01549                     vo->GetSupportedPictureAttributes();
01550             if (supp & kPictureAttributeSupported_Brightness)
01551             {
01552                 status.insert("brightness",
01553                   vo->GetPictureAttribute(kPictureAttribute_Brightness));
01554             }
01555             if (supp & kPictureAttributeSupported_Brightness)
01556             {
01557                 status.insert("contrast",
01558                   vo->GetPictureAttribute(kPictureAttribute_Contrast));
01559             }
01560             if (supp & kPictureAttributeSupported_Brightness)
01561             {
01562                 status.insert("colour",
01563                   vo->GetPictureAttribute(kPictureAttribute_Colour));
01564             }
01565             if (supp & kPictureAttributeSupported_Brightness)
01566             {
01567                 status.insert("hue",
01568                   vo->GetPictureAttribute(kPictureAttribute_Hue));
01569             }
01570             if (supp & kPictureAttributeSupported_StudioLevels)
01571             {
01572                 status.insert("studiolevels",
01573                   vo->GetPictureAttribute(kPictureAttribute_StudioLevels));
01574             }
01575         }
01576     }
01577     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
01578 
01579     ReturnPlayerLock(ctx);
01580 
01581     QHashIterator<QString,QString> tit(info.text);
01582     while (tit.hasNext())
01583     {
01584         tit.next();
01585         status.insert(tit.key(), tit.value());
01586     }
01587 
01588     QHashIterator<QString,int> vit(info.values);
01589     while (vit.hasNext())
01590     {
01591         vit.next();
01592         status.insert(vit.key(), vit.value());
01593     }
01594 
01595     MythUIStateTracker::SetState(status);
01596 }
01597 
01601 TVState TV::GetState(const PlayerContext *actx) const
01602 {
01603     TVState ret = kState_ChangingState;
01604     if (!actx->InStateChange())
01605         ret = actx->GetState();
01606     return ret;
01607 }
01608 
01613 bool TV::LiveTV(bool showDialogs)
01614 {
01615     requestDelete = false;
01616     allowRerecord = false;
01617     jumpToProgram = false;
01618 
01619     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
01620     if (actx->GetState() == kState_None &&
01621         RequestNextRecorder(actx, showDialogs))
01622     {
01623         actx->SetInitialTVState(true);
01624         HandleStateChange(actx, actx);
01625         switchToRec = NULL;
01626 
01627         // Start Idle Timer
01628         if (db_idle_timeout > 0)
01629         {
01630             idleTimerId = StartTimer(db_idle_timeout, __LINE__);
01631             LOG(VB_GENERAL, LOG_INFO, QString("Using Idle Timer. %1 minutes")
01632                     .arg(db_idle_timeout*(1.0f/60000.0f)));
01633         }
01634 
01635         ReturnPlayerLock(actx);
01636         return true;
01637     }
01638     ReturnPlayerLock(actx);
01639     return false;
01640 }
01641 
01642 int TV::GetLastRecorderNum(int player_idx) const
01643 {
01644     const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
01645     int ret = ctx->GetCardID();
01646     ReturnPlayerLock(ctx);
01647     return ret;
01648 }
01649 
01650 bool TV::RequestNextRecorder(PlayerContext *ctx, bool showDialogs)
01651 {
01652     if (!ctx)
01653         return false;
01654 
01655     ctx->SetRecorder(NULL);
01656 
01657     RemoteEncoder *testrec = NULL;
01658     if (switchToRec)
01659     {
01660         // If this is set we, already got a new recorder in SwitchCards()
01661         testrec = switchToRec;
01662         switchToRec = NULL;
01663     }
01664     else
01665     {
01666         // When starting LiveTV we just get the next free recorder
01667         testrec = RemoteRequestNextFreeRecorder(-1);
01668     }
01669 
01670     if (!testrec)
01671         return false;
01672 
01673     if (!testrec->IsValidRecorder())
01674     {
01675         if (showDialogs)
01676             ShowNoRecorderDialog(ctx);
01677 
01678         delete testrec;
01679 
01680         return false;
01681     }
01682 
01683     ctx->SetRecorder(testrec);
01684 
01685     return true;
01686 }
01687 
01688 void TV::FinishRecording(int player_ctx)
01689 {
01690     PlayerContext *ctx = GetPlayerReadLock(player_ctx, __FILE__, __LINE__);
01691     if (StateIsRecording(GetState(ctx)) && ctx->recorder)
01692         ctx->recorder->FinishRecording();
01693     ReturnPlayerLock(ctx);
01694 }
01695 
01696 void TV::AskAllowRecording(PlayerContext *ctx,
01697                            const QStringList &msg, int timeuntil,
01698                            bool hasrec, bool haslater)
01699 {
01700 #if 0
01701     LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording");
01702 #endif
01703     if (!StateIsLiveTV(GetState(ctx)))
01704        return;
01705 
01706     ProgramInfo *info = new ProgramInfo(msg);
01707     if (!info->GetChanID())
01708     {
01709         delete info;
01710         return;
01711     }
01712 
01713     QMutexLocker locker(&askAllowLock);
01714     QString key = info->MakeUniqueKey();
01715     if (timeuntil > 0)
01716     {
01717         // add program to list
01718 #if 0
01719         LOG(VB_GENERAL, LOG_DEBUG, LOC + "AskAllowRecording -- " +
01720             QString("adding '%1'").arg(info->title));
01721 #endif
01722         QDateTime expiry = QDateTime::currentDateTime().addSecs(timeuntil);
01723         askAllowPrograms[key] = AskProgramInfo(expiry, hasrec, haslater, info);
01724     }
01725     else
01726     {
01727         // remove program from list
01728         LOG(VB_GENERAL, LOG_INFO, LOC + "AskAllowRecording -- " +
01729             QString("removing '%1'").arg(info->GetTitle()));
01730         QMap<QString,AskProgramInfo>::iterator it = askAllowPrograms.find(key);
01731         if (it != askAllowPrograms.end())
01732         {
01733             delete (*it).info;
01734             askAllowPrograms.erase(it);
01735         }
01736         delete info;
01737     }
01738 
01739     ShowOSDAskAllow(ctx);
01740 }
01741 
01742 void TV::ShowOSDAskAllow(PlayerContext *ctx)
01743 {
01744     QMutexLocker locker(&askAllowLock);
01745     if (!ctx->recorder)
01746         return;
01747 
01748     uint cardid = ctx->GetCardID();
01749 
01750     QString single_rec =
01751         tr("MythTV wants to record \"%1\" on %2 in %d seconds. "
01752            "Do you want to:");
01753 
01754     QString record_watch  = tr("Record and watch while it records");
01755     QString let_record1   = tr("Let it record and go back to the Main Menu");
01756     QString let_recordm   = tr("Let them record and go back to the Main Menu");
01757     QString record_later1 = tr("Record it later, I want to watch TV");
01758     QString record_laterm = tr("Record them later, I want to watch TV");
01759     QString do_not_record1= tr("Don't let it record, I want to watch TV");
01760     QString do_not_recordm= tr("Don't let them record, I want to watch TV");
01761 
01762     // eliminate timed out programs
01763     QDateTime timeNow = QDateTime::currentDateTime();
01764     QMap<QString,AskProgramInfo>::iterator it = askAllowPrograms.begin();
01765     QMap<QString,AskProgramInfo>::iterator next = it;
01766     while (it != askAllowPrograms.end())
01767     {
01768         next = it; ++next;
01769         if ((*it).expiry <= timeNow)
01770         {
01771 #if 0
01772             LOG(VB_GENERAL, LOG_DEBUG, LOC + "UpdateOSDAskAllowDialog -- " +
01773                 QString("removing '%1'").arg((*it).info->title));
01774 #endif
01775             delete (*it).info;
01776             askAllowPrograms.erase(it);
01777         }
01778         it = next;
01779     }
01780     int          timeuntil = 0;
01781     QString      message   = QString::null;
01782     uint conflict_count = askAllowPrograms.size();
01783 
01784     it = askAllowPrograms.begin();
01785     if ((1 == askAllowPrograms.size()) && ((*it).info->GetCardID() == cardid))
01786     {
01787         (*it).is_in_same_input_group = (*it).is_conflicting = true;
01788     }
01789     else if (!askAllowPrograms.empty())
01790     {
01791         // get the currently used input on our card
01792         bool busy_input_grps_loaded = false;
01793         vector<uint> busy_input_grps;
01794         TunedInputInfo busy_input;
01795         RemoteIsBusy(cardid, busy_input);
01796 
01797         // check if current input can conflict
01798         it = askAllowPrograms.begin();
01799         for (; it != askAllowPrograms.end(); ++it)
01800         {
01801             (*it).is_in_same_input_group =
01802                 (cardid == (*it).info->GetCardID());
01803 
01804             if ((*it).is_in_same_input_group)
01805                 continue;
01806 
01807             // is busy_input in same input group as recording
01808             if (!busy_input_grps_loaded)
01809             {
01810                 busy_input_grps = CardUtil::GetInputGroups(busy_input.inputid);
01811                 busy_input_grps_loaded = true;
01812             }
01813 
01814             vector<uint> input_grps =
01815                 CardUtil::GetInputGroups((*it).info->GetInputID());
01816 
01817             for (uint i = 0; i < input_grps.size(); i++)
01818             {
01819                 if (find(busy_input_grps.begin(), busy_input_grps.end(),
01820                          input_grps[i]) !=  busy_input_grps.end())
01821                 {
01822                     (*it).is_in_same_input_group = true;
01823                     break;
01824                 }
01825             }
01826         }
01827 
01828         // check if inputs that can conflict are ok
01829         conflict_count = 0;
01830         it = askAllowPrograms.begin();
01831         for (; it != askAllowPrograms.end(); ++it)
01832         {
01833             if (!(*it).is_in_same_input_group)
01834                 (*it).is_conflicting = false;
01835             else if ((cardid == (uint)(*it).info->GetCardID()))
01836                 (*it).is_conflicting = true;
01837             else if (!CardUtil::IsTunerShared(cardid, (*it).info->GetCardID()))
01838                 (*it).is_conflicting = true;
01839             else if ((busy_input.sourceid == (uint)(*it).info->GetSourceID()) &&
01840                      (busy_input.mplexid  == (uint)(*it).info->QueryMplexID()))
01841                 (*it).is_conflicting = false;
01842             else
01843                 (*it).is_conflicting = true;
01844 
01845             conflict_count += (*it).is_conflicting ? 1 : 0;
01846         }
01847     }
01848 
01849     it = askAllowPrograms.begin();
01850     for (; it != askAllowPrograms.end() && !(*it).is_conflicting; ++it);
01851 
01852     if (conflict_count == 0)
01853     {
01854         LOG(VB_GENERAL, LOG_INFO, LOC + "The scheduler wants to make "
01855                                         "a non-conflicting recording.");
01856         // TODO take down mplexid and inform user of problem
01857         // on channel changes.
01858     }
01859     else if (conflict_count == 1 && ((*it).info->GetCardID() == cardid))
01860     {
01861 #if 0
01862         LOG(VB_GENERAL, LOG_DEBUG, LOC + "UpdateOSDAskAllowDialog -- " +
01863             "kAskAllowOneRec");
01864 #endif
01865 
01866         it = askAllowPrograms.begin();
01867 
01868         QString channel = db_channel_format;
01869         channel
01870             .replace("<num>",  (*it).info->GetChanNum())
01871             .replace("<sign>", (*it).info->GetChannelSchedulingID())
01872             .replace("<name>", (*it).info->GetChannelName());
01873 
01874         message = single_rec.arg((*it).info->GetTitle()).arg(channel);
01875 
01876         OSD *osd = GetOSDLock(ctx);
01877         if (osd)
01878         {
01879             browsehelper->BrowseEnd(ctx, false);
01880             timeuntil = QDateTime::currentDateTime().secsTo((*it).expiry) * 1000;
01881             osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
01882             osd->DialogAddButton(record_watch, "DIALOG_ASKALLOW_WATCH_0",
01883                                  false, !((*it).has_rec));
01884             osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0");
01885             osd->DialogAddButton(((*it).has_later) ? record_later1 : do_not_record1,
01886                                  "DIALOG_ASKALLOW_CANCELRECORDING_0",
01887                                  false, ((*it).has_rec));
01888         }
01889         ReturnOSDLock(ctx, osd);
01890     }
01891     else
01892     {
01893         if (conflict_count > 1)
01894         {
01895             message = QObject::tr(
01896                 "MythTV wants to record these programs in %d seconds:");
01897             message += "\n";
01898         }
01899 
01900         bool has_rec = false;
01901         it = askAllowPrograms.begin();
01902         for (; it != askAllowPrograms.end(); ++it)
01903         {
01904             if (!(*it).is_conflicting)
01905                 continue;
01906 
01907             QString title = (*it).info->GetTitle();
01908             if ((title.length() < 10) && !(*it).info->GetSubtitle().isEmpty())
01909                 title += ": " + (*it).info->GetSubtitle();
01910             if (title.length() > 20)
01911                 title = title.left(17) + "...";
01912 
01913             QString channel = db_channel_format;
01914             channel
01915                 .replace("<num>",  (*it).info->GetChanNum())
01916                 .replace("<sign>", (*it).info->GetChannelSchedulingID())
01917                 .replace("<name>", (*it).info->GetChannelName());
01918 
01919             if (conflict_count > 1)
01920             {
01921                 message += QObject::tr("\"%1\" on %2").arg(title).arg(channel);
01922                 message += "\n";
01923             }
01924             else
01925             {
01926                 message = single_rec.arg((*it).info->GetTitle()).arg(channel);
01927                 has_rec = (*it).has_rec;
01928             }
01929         }
01930 
01931         if (conflict_count > 1)
01932         {
01933             message += "\n";
01934             message += QObject::tr("Do you want to:");
01935         }
01936 
01937         bool all_have_later = true;
01938         timeuntil = 9999999;
01939         it = askAllowPrograms.begin();
01940         for (; it != askAllowPrograms.end(); ++it)
01941         {
01942             if ((*it).is_conflicting)
01943             {
01944                 all_have_later &= (*it).has_later;
01945                 int tmp = QDateTime::currentDateTime().secsTo((*it).expiry);
01946                 tmp *= 1000;
01947                 timeuntil = min(timeuntil, max(tmp, 0));
01948             }
01949         }
01950         timeuntil = (9999999 == timeuntil) ? 0 : timeuntil;
01951 
01952         OSD *osd = GetOSDLock(ctx);
01953         if (osd && conflict_count > 1)
01954         {
01955             browsehelper->BrowseEnd(ctx, false);
01956             osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
01957             osd->DialogAddButton(let_recordm, "DIALOG_ASKALLOW_EXIT_0",
01958                                  false, true);
01959             osd->DialogAddButton((all_have_later) ? record_laterm : do_not_recordm,
01960                                  "DIALOG_ASKALLOW_CANCELCONFLICTING_0");
01961         }
01962         else if (osd)
01963         {
01964             browsehelper->BrowseEnd(ctx, false);
01965             osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
01966             osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0",
01967                                  false, !has_rec);
01968             osd->DialogAddButton((all_have_later) ? record_later1 : do_not_record1,
01969                                  "DIALOG_ASKALLOW_CANCELRECORDING_0",
01970                                  false, has_rec);
01971         }
01972         ReturnOSDLock(ctx, osd);
01973     }
01974 }
01975 
01976 void TV::HandleOSDAskAllow(PlayerContext *ctx, QString action)
01977 {
01978     if (!DialogIsVisible(ctx, OSD_DLG_ASKALLOW))
01979         return;
01980 
01981     if (!askAllowLock.tryLock())
01982     {
01983         LOG(VB_GENERAL, LOG_ERR, "allowrecordingbox : askAllowLock is locked");
01984         return;
01985     }
01986 
01987     if (action == "CANCELRECORDING")
01988     {
01989         if (ctx->recorder)
01990             ctx->recorder->CancelNextRecording(true);
01991     }
01992     else if (action == "CANCELCONFLICTING")
01993     {
01994         QMap<QString,AskProgramInfo>::iterator it =
01995             askAllowPrograms.begin();
01996         for (; it != askAllowPrograms.end(); ++it)
01997         {
01998             if ((*it).is_conflicting)
01999                 RemoteCancelNextRecording((*it).info->GetCardID(), true);
02000         }
02001     }
02002     else if (action == "WATCH")
02003     {
02004         if (ctx->recorder)
02005             ctx->recorder->CancelNextRecording(false);
02006     }
02007     else // if (action == "EXIT")
02008     {
02009         PrepareToExitPlayer(ctx, __LINE__);
02010         SetExitPlayer(true, true);
02011     }
02012 
02013     askAllowLock.unlock();
02014 }
02015 
02016 int TV::Playback(const ProgramInfo &rcinfo)
02017 {
02018     wantsToQuit   = false;
02019     jumpToProgram = false;
02020     allowRerecord = false;
02021     requestDelete = false;
02022 
02023     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02024     if (mctx->GetState() != kState_None)
02025     {
02026         ReturnPlayerLock(mctx);
02027         return 0;
02028     }
02029 
02030     mctx->SetPlayingInfo(&rcinfo);
02031     mctx->SetInitialTVState(false);
02032     HandleStateChange(mctx, mctx);
02033 
02034     ReturnPlayerLock(mctx);
02035 
02036     if (LCD *lcd = LCD::Get())
02037     {
02038         lcd->switchToChannel(rcinfo.GetChannelSchedulingID(),
02039                              rcinfo.GetTitle(), rcinfo.GetSubtitle());
02040         lcd->setFunctionLEDs((rcinfo.IsRecording())?FUNC_TV:FUNC_MOVIE, true);
02041     }
02042 
02043     return 1;
02044 }
02045 
02046 bool TV::StateIsRecording(TVState state)
02047 {
02048     return (state == kState_RecordingOnly ||
02049             state == kState_WatchingRecording);
02050 }
02051 
02052 bool TV::StateIsPlaying(TVState state)
02053 {
02054     return (state == kState_WatchingPreRecorded ||
02055             state == kState_WatchingRecording   ||
02056             state == kState_WatchingVideo       ||
02057             state == kState_WatchingDVD         ||
02058             state == kState_WatchingBD);
02059 }
02060 
02061 bool TV::StateIsLiveTV(TVState state)
02062 {
02063     return (state == kState_WatchingLiveTV);
02064 }
02065 
02066 TVState TV::RemoveRecording(TVState state)
02067 {
02068     if (StateIsRecording(state))
02069     {
02070         if (state == kState_RecordingOnly)
02071             return kState_None;
02072         return kState_WatchingPreRecorded;
02073     }
02074     return kState_Error;
02075 }
02076 
02077 #define TRANSITION(ASTATE,BSTATE) \
02078    ((ctxState == ASTATE) && (desiredNextState == BSTATE))
02079 
02080 #define SET_NEXT() do { nextState = desiredNextState; changed = true; } while(0)
02081 #define SET_LAST() do { nextState = ctxState; changed = true; } while(0)
02082 
02083 static QString tv_i18n(const QString &msg)
02084 {
02085     QByteArray msg_arr = msg.toLatin1();
02086     QString msg_i18n = QObject::tr(msg_arr.constData());
02087     QByteArray msg_i18n_arr = msg_i18n.toLatin1();
02088     return (msg_arr == msg_i18n_arr) ? msg_i18n : msg;
02089 }
02090 
02099 void TV::HandleStateChange(PlayerContext *mctx, PlayerContext *ctx)
02100 {
02101     LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("HandleStateChange(%1) -- begin")
02102             .arg(find_player_index(ctx)));
02103 
02104     if (ctx->IsErrored())
02105     {
02106         LOG(VB_GENERAL, LOG_ERR, LOC +
02107             "HandleStateChange(): Called after fatal error detected.");
02108         return;
02109     }
02110 
02111     bool changed = false;
02112 
02113     ctx->LockState();
02114     TVState nextState = ctx->GetState();
02115     if (ctx->nextState.empty())
02116     {
02117         LOG(VB_GENERAL, LOG_WARNING, LOC +
02118             "HandleStateChange() Warning, called with no state to change to.");
02119         ctx->UnlockState();
02120         return;
02121     }
02122 
02123     TVState ctxState = ctx->GetState();
02124     TVState desiredNextState = ctx->DequeueNextState();
02125 
02126     LOG(VB_GENERAL, LOG_INFO, LOC +
02127         QString("Attempting to change from %1 to %2")
02128             .arg(StateToString(nextState))
02129             .arg(StateToString(desiredNextState)));
02130 
02131     if (desiredNextState == kState_Error)
02132     {
02133         LOG(VB_GENERAL, LOG_ERR, LOC + "HandleStateChange(): "
02134                 "Attempting to set to an error state!");
02135         SetErrored(ctx);
02136         ctx->UnlockState();
02137         return;
02138     }
02139 
02140     bool ok = false;
02141     if (TRANSITION(kState_None, kState_WatchingLiveTV))
02142     {
02143         QString name = "";
02144 
02145         ctx->lastSignalUIInfo.clear();
02146 
02147         ctx->recorder->Setup();
02148 
02149         QDateTime timerOffTime = QDateTime::currentDateTime();
02150         lockTimerOn = false;
02151 
02152         SET_NEXT();
02153 
02154         uint chanid = gCoreContext->GetNumSetting("DefaultChanid", 0);
02155 
02156         if (chanid && !IsTunable(ctx, chanid))
02157             chanid = 0;
02158 
02159         QString channum = "";
02160 
02161         if (chanid)
02162         {
02163             QStringList reclist;
02164 
02165             MSqlQuery query(MSqlQuery::InitCon());
02166             query.prepare("SELECT channum FROM channel "
02167                           "WHERE chanid = :CHANID");
02168             query.bindValue(":CHANID", chanid);
02169             if (query.exec() && query.isActive() && query.size() > 0 && query.next())
02170                 channum = query.value(0).toString();
02171             else
02172                 channum = QString::number(chanid);
02173 
02174             bool getit = ctx->recorder->ShouldSwitchToAnotherCard(
02175                 QString::number(chanid));
02176 
02177             if (getit)
02178                 reclist = ChannelUtil::GetValidRecorderList(chanid, channum);
02179 
02180             if (reclist.size())
02181             {
02182                 RemoteEncoder *testrec = NULL;
02183                 vector<uint> excluded_cardids;
02184                 testrec = RemoteRequestFreeRecorderFromList(reclist,
02185                                                             excluded_cardids);
02186                 if (testrec && testrec->IsValidRecorder())
02187                 {
02188                     ctx->SetRecorder(testrec);
02189                     ctx->recorder->Setup();
02190                 }
02191             }
02192             else if (getit)
02193                 chanid = 0;
02194         }
02195 
02196         LOG(VB_GENERAL, LOG_NOTICE, LOC + "Spawning LiveTV Recorder -- begin");
02197 
02198         if (chanid && !channum.isEmpty())
02199             ctx->recorder->SpawnLiveTV(ctx->tvchain->GetID(), false, channum);
02200         else
02201             ctx->recorder->SpawnLiveTV(ctx->tvchain->GetID(), false, "");
02202 
02203         LOG(VB_GENERAL, LOG_NOTICE, LOC + "Spawning LiveTV Recorder -- end");
02204 
02205         if (!ctx->ReloadTVChain())
02206         {
02207             LOG(VB_GENERAL, LOG_ERR, LOC +
02208                     "HandleStateChange(): LiveTV not successfully started");
02209             RestoreScreenSaver(ctx);
02210             ctx->SetRecorder(NULL);
02211             SetErrored(ctx);
02212             SET_LAST();
02213         }
02214         else
02215         {
02216             ctx->LockPlayingInfo(__FILE__, __LINE__);
02217             QString playbackURL = ctx->playingInfo->GetPlaybackURL(true);
02218             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
02219 
02220             bool opennow = (ctx->tvchain->GetCardType(-1) != "DUMMY");
02221 
02222             LOG(VB_GENERAL, LOG_INFO, LOC +
02223                 QString("playbackURL(%1) cardtype(%2)")
02224                     .arg(playbackURL).arg(ctx->tvchain->GetCardType(-1)));
02225 
02226             ctx->SetRingBuffer(
02227                 RingBuffer::Create(
02228                     playbackURL, false, true,
02229                     opennow ? RingBuffer::kLiveTVOpenTimeout : -1));
02230 
02231             ctx->buffer->SetLiveMode(ctx->tvchain);
02232         }
02233 
02234 
02235         if (ctx->playingInfo && StartRecorder(ctx,-1))
02236         {
02237             ok = StartPlayer(mctx, ctx, desiredNextState);
02238         }
02239         if (!ok)
02240         {
02241             LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
02242             RestoreScreenSaver(ctx);
02243             ctx->SetRecorder(NULL);
02244             SetErrored(ctx);
02245             SET_LAST();
02246         }
02247         else if (!ctx->IsPIP())
02248         {
02249             if (!lastLockSeenTime.isValid() ||
02250                 (lastLockSeenTime < timerOffTime))
02251             {
02252                 lockTimer.start();
02253                 lockTimerOn = true;
02254             }
02255         }
02256 
02257         if (mctx != ctx)
02258             SetActive(ctx, find_player_index(ctx), false);
02259     }
02260     else if (TRANSITION(kState_WatchingLiveTV, kState_None))
02261     {
02262         SET_NEXT();
02263         RestoreScreenSaver(ctx);
02264         StopStuff(mctx, ctx, true, true, true);
02265 
02266         if ((mctx != ctx) && (GetPlayer(ctx,-1) == ctx))
02267             SetActive(mctx, 0, true);
02268     }
02269     else if (TRANSITION(kState_WatchingRecording, kState_WatchingPreRecorded))
02270     {
02271         SET_NEXT();
02272     }
02273     else if (TRANSITION(kState_None, kState_WatchingPreRecorded) ||
02274              TRANSITION(kState_None, kState_WatchingVideo) ||
02275              TRANSITION(kState_None, kState_WatchingDVD)   ||
02276              TRANSITION(kState_None, kState_WatchingBD)    ||
02277              TRANSITION(kState_None, kState_WatchingRecording))
02278     {
02279         ctx->LockPlayingInfo(__FILE__, __LINE__);
02280         QString playbackURL = ctx->playingInfo->GetPlaybackURL(true);
02281         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
02282 
02283         ctx->SetRingBuffer(RingBuffer::Create(playbackURL, false));
02284 
02285         if (ctx->buffer && ctx->buffer->IsOpen())
02286         {
02287             if (desiredNextState == kState_WatchingRecording)
02288             {
02289                 ctx->LockPlayingInfo(__FILE__, __LINE__);
02290                 RemoteEncoder *rec = RemoteGetExistingRecorder(
02291                     ctx->playingInfo);
02292                 ctx->UnlockPlayingInfo(__FILE__, __LINE__);
02293 
02294                 ctx->SetRecorder(rec);
02295 
02296                 if (!ctx->recorder ||
02297                     !ctx->recorder->IsValidRecorder())
02298                 {
02299                     LOG(VB_GENERAL, LOG_ERR, LOC +
02300                         "Couldn't find recorder for in-progress recording");
02301                     desiredNextState = kState_WatchingPreRecorded;
02302                     ctx->SetRecorder(NULL);
02303                 }
02304                 else
02305                 {
02306                     ctx->recorder->Setup();
02307                 }
02308             }
02309 
02310             ok = StartPlayer(mctx, ctx, desiredNextState);
02311 
02312             if (ok)
02313             {
02314                 SET_NEXT();
02315 
02316                 ctx->LockPlayingInfo(__FILE__, __LINE__);
02317                 if (ctx->playingInfo->IsRecording())
02318                 {
02319                     QString message = "COMMFLAG_REQUEST ";
02320                     message += ctx->playingInfo->MakeUniqueKey();
02321                     gCoreContext->SendMessage(message);
02322                 }
02323                 ctx->UnlockPlayingInfo(__FILE__, __LINE__);
02324             }
02325         }
02326 
02327         if (!ok)
02328         {
02329             SET_LAST();
02330             SetErrored(ctx);
02331         }
02332         else if (mctx != ctx)
02333         {
02334             SetActive(ctx, find_player_index(ctx), false);
02335         }
02336     }
02337     else if (TRANSITION(kState_WatchingPreRecorded, kState_None) ||
02338              TRANSITION(kState_WatchingVideo, kState_None)       ||
02339              TRANSITION(kState_WatchingDVD, kState_None)         ||
02340              TRANSITION(kState_WatchingBD, kState_None)          ||
02341              TRANSITION(kState_WatchingRecording, kState_None))
02342     {
02343         SET_NEXT();
02344 
02345         RestoreScreenSaver(ctx);
02346         StopStuff(mctx, ctx, true, true, false);
02347 
02348         if ((mctx != ctx) && (GetPlayer(ctx,-1) == ctx))
02349             SetActive(mctx, 0, true);
02350     }
02351     else if (TRANSITION(kState_None, kState_None))
02352     {
02353         SET_NEXT();
02354     }
02355 
02356     // Print state changed message...
02357     if (!changed)
02358     {
02359         LOG(VB_GENERAL, LOG_ERR, LOC +
02360             QString("Unknown state transition: %1 to %2")
02361                 .arg(StateToString(ctx->GetState()))
02362                 .arg(StateToString(desiredNextState)));
02363     }
02364     else if (ctx->GetState() != nextState)
02365     {
02366         LOG(VB_GENERAL, LOG_INFO, LOC + QString("Changing from %1 to %2")
02367                 .arg(StateToString(ctx->GetState()))
02368                 .arg(StateToString(nextState)));
02369     }
02370 
02371     // update internal state variable
02372     TVState lastState = ctx->GetState();
02373     ctx->playingState = nextState;
02374     ctx->UnlockState();
02375 
02376     if (mctx == ctx)
02377     {
02378         if (StateIsLiveTV(ctx->GetState()))
02379         {
02380             LOG(VB_GENERAL, LOG_INFO, LOC + "State is LiveTV & mctx == ctx");
02381             UpdateOSDInput(ctx);
02382             LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateOSDInput done");
02383             UpdateLCD();
02384             LOG(VB_GENERAL, LOG_INFO, LOC + "UpdateLCD done");
02385             ITVRestart(ctx, true);
02386             LOG(VB_GENERAL, LOG_INFO, LOC + "ITVRestart done");
02387         }
02388         else if (StateIsPlaying(ctx->GetState()) && lastState == kState_None)
02389         {
02390             ctx->LockPlayingInfo(__FILE__, __LINE__);
02391             int count = PlayGroup::GetCount();
02392             QString msg = tr("%1 Settings")
02393                     .arg(tv_i18n(ctx->playingInfo->GetPlaybackGroup()));
02394             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
02395             if (count > 0)
02396                 SetOSDMessage(ctx, msg);
02397             ITVRestart(ctx, false);
02398         }
02399 
02400         if (ctx->buffer && ctx->buffer->IsDVD())
02401         {
02402             UpdateLCD();
02403         }
02404 
02405         if (ctx->recorder)
02406             ctx->recorder->FrontendReady();
02407 
02408         QMutexLocker locker(&timerIdLock);
02409         if (endOfRecPromptTimerId)
02410             KillTimer(endOfRecPromptTimerId);
02411         endOfRecPromptTimerId = 0;
02412         if (db_end_of_rec_exit_prompt && !inPlaylist && !underNetworkControl)
02413         {
02414             endOfRecPromptTimerId =
02415                 StartTimer(kEndOfRecPromptCheckFrequency, __LINE__);
02416         }
02417 
02418         if (endOfPlaybackTimerId)
02419             KillTimer(endOfPlaybackTimerId);
02420         endOfPlaybackTimerId = 0;
02421 
02422         if (StateIsPlaying(ctx->GetState()))
02423         {
02424             endOfPlaybackTimerId =
02425                 StartTimer(kEndOfPlaybackFirstCheckTimer, __LINE__);
02426 
02427         }
02428 
02429     }
02430 
02431     if (TRANSITION(kState_None, kState_WatchingPreRecorded) ||
02432              TRANSITION(kState_None, kState_WatchingVideo) ||
02433              TRANSITION(kState_None, kState_WatchingDVD)   ||
02434              TRANSITION(kState_None, kState_WatchingBD)    ||
02435              TRANSITION(kState_None, kState_WatchingRecording) ||
02436              TRANSITION(kState_None, kState_WatchingLiveTV))
02437     {
02438         if (!ctx->IsPIP())
02439             GetMythUI()->DisableScreensaver();
02440         MythMainWindow *mainWindow = GetMythMainWindow();
02441         mainWindow->setBaseSize(player_bounds.size());
02442         mainWindow->setMinimumSize(
02443             (db_use_fixed_size) ? player_bounds.size() : QSize(16, 16));
02444         mainWindow->setMaximumSize(
02445             (db_use_fixed_size) ? player_bounds.size() :
02446             QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
02447         mainWindow->setGeometry(player_bounds);
02448         mainWindow->ResizePainterWindow(player_bounds.size());
02449         if (!weDisabledGUI)
02450         {
02451             weDisabledGUI = true;
02452             GetMythMainWindow()->PushDrawDisabled();
02453         }
02454         DrawUnusedRects();
02455         // we no longer need the contents of myWindow
02456         if (myWindow)
02457             myWindow->DeleteAllChildren();
02458 
02459         LOG(VB_GENERAL, LOG_INFO, LOC + "Main UI disabled.");
02460     }
02461 
02462     LOG(VB_PLAYBACK, LOG_INFO, LOC +
02463         QString("HandleStateChange(%1) -- end")
02464             .arg(find_player_index(ctx)));
02465 }
02466 #undef TRANSITION
02467 #undef SET_NEXT
02468 #undef SET_LAST
02469 
02475 bool TV::StartRecorder(PlayerContext *ctx, int maxWait)
02476 {
02477     RemoteEncoder *rec = ctx->recorder;
02478     maxWait = (maxWait <= 0) ? 40000 : maxWait;
02479     MythTimer t;
02480     t.start();
02481     bool recording = false, ok = true;
02482     if (!rec) {
02483         LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid Remote Encoder");
02484         SetErrored(ctx);
02485         return false;
02486     }
02487     while (!(recording = rec->IsRecording(&ok)) &&
02488            !exitPlayerTimerId && t.elapsed() < maxWait)
02489     {
02490         if (!ok)
02491         {
02492             LOG(VB_GENERAL, LOG_ERR, LOC + "StartRecorder() -- "
02493                     "lost contact with backend");
02494             SetErrored(ctx);
02495             return false;
02496         }
02497         usleep(5000);
02498     }
02499 
02500     if (!recording || exitPlayerTimerId)
02501     {
02502         if (!exitPlayerTimerId)
02503             LOG(VB_GENERAL, LOG_ERR, LOC + "StartRecorder() -- "
02504                     "timed out waiting for recorder to start");
02505         return false;
02506     }
02507 
02508     LOG(VB_PLAYBACK, LOG_INFO, LOC +
02509         QString("StartRecorder(): took %1 ms to start recorder.")
02510             .arg(t.elapsed()));
02511 
02512     return true;
02513 }
02514 
02528 void TV::StopStuff(PlayerContext *mctx, PlayerContext *ctx,
02529                    bool stopRingBuffer, bool stopPlayer, bool stopRecorder)
02530 {
02531     LOG(VB_PLAYBACK, LOG_INFO,
02532         LOC + QString("StopStuff() for player ctx %1 -- begin")
02533             .arg(find_player_index(ctx)));
02534 
02535     SetActive(mctx, 0, false);
02536 
02537     if (ctx->buffer)
02538         ctx->buffer->IgnoreWaitStates(true);
02539 
02540     ctx->LockDeletePlayer(__FILE__, __LINE__);
02541     if (stopPlayer)
02542         ctx->StopPlaying();
02543     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
02544 
02545     if (stopRingBuffer)
02546     {
02547         LOG(VB_PLAYBACK, LOG_INFO, LOC + "StopStuff(): stopping ring buffer");
02548         if (ctx->buffer)
02549         {
02550             ctx->buffer->StopReads();
02551             ctx->buffer->Pause();
02552             ctx->buffer->WaitForPause();
02553         }
02554     }
02555 
02556     if (stopPlayer)
02557     {
02558         LOG(VB_PLAYBACK, LOG_INFO, LOC + "StopStuff(): stopping player");
02559         if (ctx == mctx)
02560         {
02561             for (uint i = 1; mctx && (i < player.size()); i++)
02562                 StopStuff(mctx, GetPlayer(mctx,i), true, true, true);
02563         }
02564     }
02565 
02566     if (stopRecorder)
02567     {
02568         LOG(VB_PLAYBACK, LOG_INFO, LOC + "StopStuff(): stopping recorder");
02569         if (ctx->recorder)
02570             ctx->recorder->StopLiveTV();
02571     }
02572 
02573     LOG(VB_PLAYBACK, LOG_INFO, LOC + "StopStuff() -- end");
02574 }
02575 
02576 void TV::TeardownPlayer(PlayerContext *mctx, PlayerContext *ctx)
02577 {
02578     int ctx_index = find_player_index(ctx);
02579 
02580     QString loc = LOC + QString("TeardownPlayer() player ctx %1")
02581         .arg(ctx_index);
02582 
02583     if (!mctx || !ctx || ctx_index < 0)
02584     {
02585         LOG(VB_GENERAL, LOG_ERR, loc + "-- error");
02586         return;
02587     }
02588 
02589     LOG(VB_PLAYBACK, LOG_INFO, loc);
02590 
02591     if (mctx != ctx)
02592     {
02593         if (ctx->HasPlayer())
02594         {
02595             PIPRemovePlayer(mctx, ctx);
02596             ctx->SetPlayer(NULL);
02597         }
02598 
02599         player.erase(player.begin() + ctx_index);
02600         delete ctx;
02601         if (mctx->IsPBP())
02602             PBPRestartMainPlayer(mctx);
02603         SetActive(mctx, playerActive, false);
02604         return;
02605     }
02606 
02607     ctx->TeardownPlayer();
02608 }
02609 
02610 void TV::timerEvent(QTimerEvent *te)
02611 {
02612     const int timer_id = te->timerId();
02613 
02614     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02615     if (mctx->IsErrored())
02616     {
02617         ReturnPlayerLock(mctx);
02618         return;
02619     }
02620     ReturnPlayerLock(mctx);
02621 
02622     bool ignore = false;
02623     {
02624         QMutexLocker locker(&timerIdLock);
02625         ignore =
02626             (stateChangeTimerId.size() &&
02627              stateChangeTimerId.find(timer_id) == stateChangeTimerId.end());
02628     }
02629     if (ignore)
02630         return; // Always handle state changes first...
02631 
02632     bool handled = true;
02633     if (timer_id == lcdTimerId)
02634         HandleLCDTimerEvent();
02635     else if (timer_id == lcdVolumeTimerId)
02636         HandleLCDVolumeTimerEvent();
02637     else if (timer_id == sleepTimerId)
02638         ShowOSDSleep();
02639     else if (timer_id == sleepDialogTimerId)
02640         SleepDialogTimeout();
02641     else if (timer_id == idleTimerId)
02642         ShowOSDIdle();
02643     else if (timer_id == idleDialogTimerId)
02644         IdleDialogTimeout();
02645     else if (timer_id == endOfPlaybackTimerId)
02646         HandleEndOfPlaybackTimerEvent();
02647     else if (timer_id == embedCheckTimerId)
02648         HandleIsNearEndWhenEmbeddingTimerEvent();
02649     else if (timer_id == endOfRecPromptTimerId)
02650         HandleEndOfRecordingExitPromptTimerEvent();
02651     else if (timer_id == videoExitDialogTimerId)
02652         HandleVideoExitDialogTimerEvent();
02653     else if (timer_id == pseudoChangeChanTimerId)
02654         HandlePseudoLiveTVTimerEvent();
02655     else if (timer_id == speedChangeTimerId)
02656         HandleSpeedChangeTimerEvent();
02657     else if (timer_id == pipChangeTimerId)
02658         HandlePxPTimerEvent();
02659     else
02660         handled = false;
02661 
02662     if (handled)
02663         return;
02664 
02665     // Check if it matches a stateChangeTimerId
02666     PlayerContext *ctx = NULL;
02667     {
02668         QMutexLocker locker(&timerIdLock);
02669         TimerContextMap::iterator it = stateChangeTimerId.find(timer_id);
02670         if (it != stateChangeTimerId.end())
02671         {
02672             KillTimer(timer_id);
02673             ctx = *it;
02674             stateChangeTimerId.erase(it);
02675         }
02676     }
02677 
02678     if (ctx)
02679     {
02680         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02681         bool still_exists = find_player_index(ctx) >= 0;
02682 
02683         while (still_exists && !ctx->nextState.empty())
02684         {
02685             HandleStateChange(mctx, ctx);
02686             if ((kState_None  == ctx->GetState() ||
02687                  kState_Error == ctx->GetState()) &&
02688                 ((mctx != ctx) || jumpToProgram))
02689             {
02690                 ReturnPlayerLock(mctx);
02691                 mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
02692                 TeardownPlayer(mctx, ctx);
02693                 still_exists = false;
02694             }
02695         }
02696         ReturnPlayerLock(mctx);
02697         handled = true;
02698     }
02699 
02700     if (handled)
02701         return;
02702 
02703     // Check if it matches a signalMonitorTimerId
02704     ctx = NULL;
02705     {
02706         QMutexLocker locker(&timerIdLock);
02707         TimerContextMap::iterator it = signalMonitorTimerId.find(timer_id);
02708         if (it != signalMonitorTimerId.end())
02709         {
02710             KillTimer(timer_id);
02711             ctx = *it;
02712             signalMonitorTimerId.erase(it);
02713         }
02714     }
02715 
02716     if (ctx)
02717     {
02718         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02719         bool still_exists = find_player_index(ctx) >= 0;
02720 
02721         if (still_exists && !ctx->lastSignalMsg.empty())
02722         {   // set last signal msg, so we get some feedback...
02723             UpdateOSDSignal(ctx, ctx->lastSignalMsg);
02724             ctx->lastSignalMsg.clear();
02725         }
02726         UpdateOSDTimeoutMessage(ctx);
02727 
02728         ReturnPlayerLock(mctx);
02729         handled = true;
02730     }
02731 
02732     if (handled)
02733         return;
02734 
02735     // Check if it matches a tvchainUpdateTimerId
02736     ctx = NULL;
02737     {
02738         QMutexLocker locker(&timerIdLock);
02739         TimerContextMap::iterator it = tvchainUpdateTimerId.find(timer_id);
02740         if (it != tvchainUpdateTimerId.end())
02741         {
02742             KillTimer(timer_id);
02743             ctx = *it;
02744             tvchainUpdateTimerId.erase(it);
02745         }
02746     }
02747 
02748     if (ctx)
02749     {
02750         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02751         bool still_exists = find_player_index(ctx) >= 0;
02752 
02753         if (still_exists)
02754             ctx->UpdateTVChain();
02755 
02756         ReturnPlayerLock(mctx);
02757         handled = true;
02758     }
02759 
02760     if (handled)
02761         return;
02762 
02763     // Check if it matches networkControlTimerId
02764     QString netCmd = QString::null;
02765     {
02766         QMutexLocker locker(&timerIdLock);
02767         if (timer_id == networkControlTimerId)
02768         {
02769             if (networkControlCommands.size())
02770                 netCmd = networkControlCommands.dequeue();
02771             if (networkControlCommands.empty())
02772             {
02773                 KillTimer(networkControlTimerId);
02774                 networkControlTimerId = 0;
02775             }
02776         }
02777     }
02778 
02779     if (!netCmd.isEmpty())
02780     {
02781         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02782         ProcessNetworkControlCommand(actx, netCmd);
02783         ReturnPlayerLock(actx);
02784         handled = true;
02785     }
02786 
02787     if (handled)
02788         return;
02789 
02790     // Check if it matches exitPlayerTimerId
02791     if (timer_id == exitPlayerTimerId)
02792     {
02793         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02794 
02795         OSD *osd = GetOSDLock(mctx);
02796         if (osd)
02797         {
02798             osd->DialogQuit();
02799             osd->HideAll();
02800         }
02801         ReturnOSDLock(mctx, osd);
02802 
02803         if (jumpToProgram && lastProgram)
02804         {
02805             if (!lastProgram->IsFileReadable())
02806             {
02807                 SetOSDMessage(mctx, tr("Last Program: %1 Doesn't Exist")
02808                                         .arg(lastProgram->GetTitle()));
02809                 lastProgramStringList.clear();
02810                 SetLastProgram(NULL);
02811                 LOG(VB_PLAYBACK, LOG_ERR, LOC +
02812                     "Last Program File does not exist");
02813                 jumpToProgram = false;
02814             }
02815             else
02816                 ForceNextStateNone(mctx);
02817         }
02818         else
02819             ForceNextStateNone(mctx);
02820 
02821         ReturnPlayerLock(mctx);
02822 
02823         QMutexLocker locker(&timerIdLock);
02824         KillTimer(exitPlayerTimerId);
02825         exitPlayerTimerId = 0;
02826         handled = true;
02827     }
02828 
02829     if (handled)
02830         return;
02831 
02832     if (timer_id == jumpMenuTimerId)
02833     {
02834         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02835         if (actx)
02836             FillOSDMenuJumpRec(actx);
02837         ReturnPlayerLock(actx);
02838 
02839         QMutexLocker locker(&timerIdLock);
02840         KillTimer(jumpMenuTimerId);
02841         jumpMenuTimerId = 0;
02842         handled = true;
02843     }
02844 
02845     if (handled)
02846         return;
02847 
02848     if (timer_id == switchToInputTimerId)
02849     {
02850         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02851         if (switchToInputId)
02852         {
02853             uint tmp = switchToInputId;
02854             switchToInputId = 0;
02855             SwitchInputs(actx, tmp);
02856         }
02857         ReturnPlayerLock(actx);
02858 
02859         QMutexLocker locker(&timerIdLock);
02860         KillTimer(switchToInputTimerId);
02861         switchToInputTimerId = 0;
02862         handled = true;
02863     }
02864 
02865     if (handled)
02866         return;
02867 
02868     if (timer_id == ccInputTimerId)
02869     {
02870         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02871         // Clear closed caption input mode when timer expires
02872         if (ccInputMode)
02873         {
02874             ccInputMode = false;
02875             ClearInputQueues(actx, true);
02876         }
02877         ReturnPlayerLock(actx);
02878 
02879         QMutexLocker locker(&timerIdLock);
02880         KillTimer(ccInputTimerId);
02881         ccInputTimerId = 0;
02882         handled = true;
02883     }
02884 
02885     if (handled)
02886         return;
02887 
02888     if (timer_id == asInputTimerId)
02889     {
02890         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02891         // Clear closed caption input mode when timer expires
02892         if (asInputMode)
02893         {
02894             asInputMode = false;
02895             ClearInputQueues(actx, true);
02896         }
02897         ReturnPlayerLock(actx);
02898 
02899         QMutexLocker locker(&timerIdLock);
02900         KillTimer(asInputTimerId);
02901         asInputTimerId = 0;
02902         handled = true;
02903     }
02904 
02905     if (handled)
02906         return;
02907 
02908     if (timer_id == queueInputTimerId)
02909     {
02910         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02911         // Commit input when the OSD fades away
02912         if (HasQueuedChannel())
02913         {
02914             OSD *osd = GetOSDLock(actx);
02915             if (osd && !osd->IsWindowVisible("osd_input"))
02916                 CommitQueuedInput(actx);
02917             ReturnOSDLock(actx, osd);
02918         }
02919         ReturnPlayerLock(actx);
02920 
02921         QMutexLocker locker(&timerIdLock);
02922         if (!queuedChanID && queuedChanNum.isEmpty() && queueInputTimerId)
02923         {
02924             KillTimer(queueInputTimerId);
02925             queueInputTimerId = 0;
02926         }
02927         handled = true;
02928     }
02929 
02930     if (handled)
02931         return;
02932 
02933     if (timer_id == browseTimerId)
02934     {
02935         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02936         browsehelper->BrowseEnd(actx, false);
02937         ReturnPlayerLock(actx);
02938         handled = true;
02939     }
02940 
02941     if (handled)
02942         return;
02943 
02944     if (timer_id == updateOSDDebugTimerId)
02945     {
02946         bool update = false;
02947         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02948         OSD *osd = GetOSDLock(actx);
02949         if (osd && osd->IsWindowVisible("osd_debug") &&
02950             (StateIsLiveTV(actx->GetState()) ||
02951              StateIsPlaying(actx->GetState())))
02952         {
02953             update = true;
02954         }
02955         else
02956         {
02957             QMutexLocker locker(&timerIdLock);
02958             KillTimer(updateOSDDebugTimerId);
02959             updateOSDDebugTimerId = 0;
02960             actx->buffer->EnableBitrateMonitor(false);
02961             if (actx->player)
02962                 actx->player->EnableFrameRateMonitor(false);
02963         }
02964         ReturnOSDLock(actx, osd);
02965         if (update)
02966             UpdateOSDDebug(actx);
02967         ReturnPlayerLock(actx);
02968         handled = true;
02969     }
02970     if (timer_id == updateOSDPosTimerId)
02971     {
02972         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
02973         OSD *osd = GetOSDLock(actx);
02974         if (osd && osd->IsWindowVisible("osd_status") &&
02975             (StateIsLiveTV(actx->GetState()) ||
02976              StateIsPlaying(actx->GetState())))
02977         {
02978             osdInfo info;
02979             if (actx->CalcPlayerSliderPosition(info))
02980             {
02981                 osd->SetText("osd_status", info.text, kOSDTimeout_Ignore);
02982                 osd->SetValues("osd_status", info.values, kOSDTimeout_Ignore);
02983             }
02984         }
02985         else
02986             SetUpdateOSDPosition(false);
02987         ReturnOSDLock(actx, osd);
02988         ReturnPlayerLock(actx);
02989         handled = true;
02990     }
02991 
02992     if (handled)
02993         return;
02994 
02995     if (timer_id == errorRecoveryTimerId)
02996     {
02997         bool error = false;
02998         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
02999 
03000         if (mctx->IsPlayerErrored())
03001         {
03002             if (mctx->IsPlayerRecoverable())
03003                 RestartMainPlayer(mctx);
03004 
03005             if (mctx->IsPlayerDecoderErrored())
03006             {
03007                 LOG(VB_GENERAL, LOG_EMERG, LOC +
03008                     QString("Serious hardware decoder error detected. "
03009                             "Disabling hardware decoders."));
03010                 noHardwareDecoders = true;
03011                 for (uint i = 0; i < player.size(); i++)
03012                     player[i]->SetNoHardwareDecoders();
03013                 RestartMainPlayer(mctx);
03014             }
03015         }
03016 
03017         if (mctx->IsRecorderErrored() ||
03018             mctx->IsPlayerErrored() ||
03019             mctx->IsErrored())
03020         {
03021             SetExitPlayer(true, false);
03022             ForceNextStateNone(mctx);
03023             error = true;
03024         }
03025 
03026         for (uint i = 0; i < player.size(); i++)
03027         {
03028             PlayerContext *ctx = GetPlayer(mctx, i);
03029             if (error || ctx->IsErrored())
03030                 ForceNextStateNone(ctx);
03031         }
03032         ReturnPlayerLock(mctx);
03033 
03034         QMutexLocker locker(&timerIdLock);
03035         if (errorRecoveryTimerId)
03036             KillTimer(errorRecoveryTimerId);
03037         errorRecoveryTimerId =
03038             StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
03039     }
03040 }
03041 
03042 bool TV::HandlePxPTimerEvent(void)
03043 {
03044     QString cmd = QString::null;
03045 
03046     {
03047         QMutexLocker locker(&timerIdLock);
03048         if (changePxP.empty())
03049         {
03050             if (pipChangeTimerId)
03051                 KillTimer(pipChangeTimerId);
03052             pipChangeTimerId = 0;
03053             return true;
03054         }
03055         cmd = changePxP.dequeue();
03056     }
03057 
03058     PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
03059     PlayerContext *actx = GetPlayer(mctx, -1);
03060 
03061     if (cmd == "TOGGLEPIPMODE")
03062         PxPToggleView(actx, false);
03063     else if (cmd == "TOGGLEPBPMODE")
03064         PxPToggleView(actx, true);
03065     else if (cmd == "CREATEPIPVIEW")
03066         PxPCreateView(actx, false);
03067     else if (cmd == "CREATEPBPVIEW")
03068         PxPCreateView(actx, true);
03069     else if (cmd == "SWAPPIP")
03070     {
03071         if (mctx != actx)
03072             PxPSwap(mctx, actx);
03073         else if (mctx && player.size() == 2)
03074             PxPSwap(mctx, GetPlayer(mctx,1));
03075     }
03076     else if (cmd == "TOGGLEPIPSTATE")
03077         PxPToggleType(mctx, !mctx->IsPBP());
03078 
03079     ReturnPlayerLock(mctx);
03080 
03081     QMutexLocker locker(&timerIdLock);
03082 
03083     if (pipChangeTimerId)
03084         KillTimer(pipChangeTimerId);
03085 
03086     if (changePxP.empty())
03087         pipChangeTimerId = 0;
03088     else
03089         pipChangeTimerId = StartTimer(20, __LINE__);
03090 
03091     return true;
03092 }
03093 
03094 bool TV::HandleLCDTimerEvent(void)
03095 {
03096     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
03097     LCD *lcd = LCD::Get();
03098     if (lcd)
03099     {
03100         float progress = 0.0f;
03101         QString lcd_time_string;
03102         bool showProgress = true;
03103 
03104         if (StateIsLiveTV(GetState(actx)))
03105             ShowLCDChannelInfo(actx);
03106 
03107         if (actx->buffer && actx->buffer->IsDVD())
03108         {
03109             ShowLCDDVDInfo(actx);
03110             showProgress = !actx->buffer->IsInDiscMenuOrStillFrame();
03111         }
03112 
03113         if (showProgress)
03114         {
03115             osdInfo info;
03116             if (actx->CalcPlayerSliderPosition(info)) {
03117                 progress = info.values["position"] * 0.001f;
03118 
03119                 lcd_time_string = info.text["playedtime"] + " / " + info.text["totaltime"];
03120                 // if the string is longer than the LCD width, remove all spaces
03121                 if (lcd_time_string.length() > (int)lcd->getLCDWidth())
03122                     lcd_time_string.remove(' ');
03123             }
03124         }
03125         lcd->setChannelProgress(lcd_time_string, progress);
03126     }
03127     ReturnPlayerLock(actx);
03128 
03129     QMutexLocker locker(&timerIdLock);
03130     KillTimer(lcdTimerId);
03131     lcdTimerId = StartTimer(kLCDTimeout, __LINE__);
03132 
03133     return true;
03134 }
03135 
03136 void TV::HandleLCDVolumeTimerEvent()
03137 {
03138     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
03139     LCD *lcd = LCD::Get();
03140     if (lcd)
03141     {
03142         ShowLCDChannelInfo(actx);
03143         lcd->switchToChannel(lcdCallsign, lcdTitle, lcdSubtitle);
03144     }
03145     ReturnPlayerLock(actx);
03146 
03147     QMutexLocker locker(&timerIdLock);
03148     KillTimer(lcdVolumeTimerId);
03149 }
03150 
03151 int TV::StartTimer(int interval, int line)
03152 {
03153     int x = QObject::startTimer(interval);
03154     if (!x)
03155     {
03156         LOG(VB_GENERAL, LOG_ERR, LOC +
03157             QString("Failed to start timer on line %1 of %2")
03158                 .arg(line).arg(__FILE__));
03159     }
03160     return x;
03161 }
03162 
03163 void TV::KillTimer(int id)
03164 {
03165     QObject::killTimer(id);
03166 }
03167 
03168 void TV::ForceNextStateNone(PlayerContext *ctx)
03169 {
03170     ctx->ForceNextStateNone();
03171     ScheduleStateChange(ctx);
03172 }
03173 
03174 void TV::ScheduleStateChange(PlayerContext *ctx)
03175 {
03176     QMutexLocker locker(&timerIdLock);
03177     stateChangeTimerId[StartTimer(1, __LINE__)] = ctx;
03178 }
03179 
03180 void TV::SetErrored(PlayerContext *ctx)
03181 {
03182     if (!ctx)
03183         return;
03184     QMutexLocker locker(&timerIdLock);
03185     ctx->errored = true;
03186     KillTimer(errorRecoveryTimerId);
03187     errorRecoveryTimerId = StartTimer(1, __LINE__);
03188 }
03189 
03190 void TV::PrepToSwitchToRecordedProgram(PlayerContext *ctx,
03191                                        const ProgramInfo &p)
03192 {
03193     LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switching to program: %1")
03194             .arg(p.toString(ProgramInfo::kTitleSubtitle)));
03195     SetLastProgram(&p);
03196     PrepareToExitPlayer(ctx,__LINE__);
03197     jumpToProgram = true;
03198     SetExitPlayer(true, true);
03199 }
03200 
03201 void TV::PrepareToExitPlayer(PlayerContext *ctx, int line, BookmarkAction bookmark)
03202 {
03203     bool bm_basic =
03204         (bookmark == kBookmarkAlways ||
03205          (bookmark == kBookmarkAuto && db_playback_exit_prompt == 2));
03206     bool bookmark_it = bm_basic && IsBookmarkAllowed(ctx);
03207     ctx->LockDeletePlayer(__FILE__, line);
03208     if (ctx->player)
03209     {
03210         if (bookmark_it)
03211             SetBookmark(ctx,
03212                         (ctx->player->IsNearEnd() || getEndOfRecording())
03213                         && !StateIsRecording(GetState(ctx)));
03214         if (db_auto_set_watched)
03215             ctx->player->SetWatched();
03216     }
03217     ctx->UnlockDeletePlayer(__FILE__, line);
03218 }
03219 
03220 void TV::SetExitPlayer(bool set_it, bool wants_to)
03221 {
03222     QMutexLocker locker(&timerIdLock);
03223     if (set_it)
03224     {
03225         wantsToQuit = wants_to;
03226         if (!exitPlayerTimerId)
03227             exitPlayerTimerId = StartTimer(1, __LINE__);
03228     }
03229     else
03230     {
03231         if (exitPlayerTimerId)
03232             KillTimer(exitPlayerTimerId);
03233         exitPlayerTimerId = 0;
03234         wantsToQuit = wants_to;
03235     }
03236 }
03237 
03238 void TV::SetUpdateOSDPosition(bool set_it)
03239 {
03240     QMutexLocker locker(&timerIdLock);
03241     if (set_it)
03242     {
03243         if (!updateOSDPosTimerId)
03244             updateOSDPosTimerId = StartTimer(500, __LINE__);
03245     }
03246     else
03247     {
03248         if (updateOSDPosTimerId)
03249             KillTimer(updateOSDPosTimerId);
03250         updateOSDPosTimerId = 0;
03251     }
03252 }
03253 
03254 void TV::HandleEndOfPlaybackTimerEvent(void)
03255 {
03256     {
03257         QMutexLocker locker(&timerIdLock);
03258         if (endOfPlaybackTimerId)
03259             KillTimer(endOfPlaybackTimerId);
03260         endOfPlaybackTimerId = 0;
03261     }
03262 
03263     bool is_playing = false;
03264     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
03265     for (uint i = 0; mctx && (i < player.size()); i++)
03266     {
03267         PlayerContext *ctx = GetPlayer(mctx, i);
03268         if (!StateIsPlaying(ctx->GetState()))
03269             continue;
03270 
03271         if (ctx->IsPlayerPlaying())
03272         {
03273             is_playing = true;
03274             continue;
03275         }
03276 
03277         ForceNextStateNone(ctx);
03278         if (mctx == ctx)
03279         {
03280             endOfRecording = true;
03281             PrepareToExitPlayer(mctx, __LINE__);
03282             SetExitPlayer(true, true);
03283         }
03284     }
03285     ReturnPlayerLock(mctx);
03286 
03287     if (is_playing)
03288     {
03289         QMutexLocker locker(&timerIdLock);
03290         endOfPlaybackTimerId =
03291             StartTimer(kEndOfPlaybackCheckFrequency, __LINE__);
03292     }
03293 }
03294 
03295 void TV::HandleIsNearEndWhenEmbeddingTimerEvent(void)
03296 {
03297     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
03298     if (!StateIsLiveTV(GetState(actx)))
03299     {
03300         actx->LockDeletePlayer(__FILE__, __LINE__);
03301         bool toggle = actx->player && actx->player->IsEmbedding() &&
03302                       actx->player->IsNearEnd() && !actx->player->IsPaused();
03303         actx->UnlockDeletePlayer(__FILE__, __LINE__);
03304         if (toggle)
03305             DoTogglePause(actx, true);
03306     }
03307     ReturnPlayerLock(actx);
03308 }
03309 
03310 void TV::HandleEndOfRecordingExitPromptTimerEvent(void)
03311 {
03312     if (endOfRecording || inPlaylist || editmode || underNetworkControl ||
03313         exitPlayerTimerId)
03314     {
03315         return;
03316     }
03317 
03318     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
03319     OSD *osd = GetOSDLock(mctx);
03320     if (osd && osd->DialogVisible())
03321     {
03322         ReturnOSDLock(mctx, osd);
03323         ReturnPlayerLock(mctx);
03324         return;
03325     }
03326     ReturnOSDLock(mctx, osd);
03327 
03328     bool do_prompt = false;
03329     mctx->LockDeletePlayer(__FILE__, __LINE__);
03330     if (mctx->GetState() == kState_WatchingPreRecorded && mctx->player)
03331     {
03332         if (!mctx->player->IsNearEnd())
03333             jumped_back = false;
03334 
03335         do_prompt = mctx->player->IsNearEnd() && !jumped_back &&
03336             !mctx->player->IsEmbedding() && !mctx->player->IsPaused();
03337     }
03338     mctx->UnlockDeletePlayer(__FILE__, __LINE__);
03339 
03340     if (do_prompt)
03341         ShowOSDPromptDeleteRecording(mctx, tr("End Of Recording"));
03342 
03343     ReturnPlayerLock(mctx);
03344 }
03345 
03346 void TV::HandleVideoExitDialogTimerEvent(void)
03347 {
03348     {
03349         QMutexLocker locker(&timerIdLock);
03350         if (videoExitDialogTimerId)
03351             KillTimer(videoExitDialogTimerId);
03352         videoExitDialogTimerId = 0;
03353     }
03354 
03355     // disable dialog and exit playback after timeout
03356     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
03357     OSD *osd = GetOSDLock(mctx);
03358     if (!osd || !osd->DialogVisible(OSD_DLG_VIDEOEXIT))
03359     {
03360         ReturnOSDLock(mctx, osd);
03361         ReturnPlayerLock(mctx);
03362         return;
03363     }
03364     if (osd)
03365         osd->DialogQuit();
03366     ReturnOSDLock(mctx, osd);
03367     DoTogglePause(mctx, true);
03368     ClearOSD(mctx);
03369     PrepareToExitPlayer(mctx, __LINE__);
03370     ReturnPlayerLock(mctx);
03371 
03372     requestDelete = false;
03373     SetExitPlayer(true, true);
03374 }
03375 
03376 void TV::HandlePseudoLiveTVTimerEvent(void)
03377 {
03378     {
03379         QMutexLocker locker(&timerIdLock);
03380         KillTimer(pseudoChangeChanTimerId);
03381         pseudoChangeChanTimerId = 0;
03382     }
03383 
03384     bool restartTimer = false;
03385     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
03386     for (uint i = 0; mctx && (i < player.size()); i++)
03387     {
03388         PlayerContext *ctx = GetPlayer(mctx, i);
03389         if (kPseudoChangeChannel != ctx->pseudoLiveTVState)
03390             continue;
03391 
03392         if (ctx->InStateChange())
03393         {
03394             restartTimer = true;
03395             continue;
03396         }
03397 
03398         LOG(VB_CHANNEL, LOG_INFO,
03399             QString("REC_PROGRAM -- channel change %1").arg(i));
03400 
03401         uint        chanid  = ctx->pseudoLiveTVRec->GetChanID();
03402         QString     channum = ctx->pseudoLiveTVRec->GetChanNum();
03403         StringDeque tmp     = ctx->prevChan;
03404 
03405         ctx->prevChan.clear();
03406         ChangeChannel(ctx, chanid, channum);
03407         ctx->prevChan = tmp;
03408         ctx->pseudoLiveTVState = kPseudoRecording;
03409     }
03410     ReturnPlayerLock(mctx);
03411 
03412     if (restartTimer)
03413     {
03414         QMutexLocker locker(&timerIdLock);
03415         if (!pseudoChangeChanTimerId)
03416             pseudoChangeChanTimerId = StartTimer(25, __LINE__);
03417     }
03418 }
03419 
03420 void TV::SetSpeedChangeTimer(uint when, int line)
03421 {
03422     QMutexLocker locker(&timerIdLock);
03423     if (speedChangeTimerId)
03424         KillTimer(speedChangeTimerId);
03425     speedChangeTimerId = StartTimer(when, line);
03426 }
03427 
03428 void TV::HandleSpeedChangeTimerEvent(void)
03429 {
03430     {
03431         QMutexLocker locker(&timerIdLock);
03432         if (speedChangeTimerId)
03433             KillTimer(speedChangeTimerId);
03434         speedChangeTimerId = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
03435     }
03436 
03437     bool update_msg = false;
03438     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
03439     for (uint i = 0; actx && (i < player.size()); i++)
03440     {
03441         PlayerContext *ctx = GetPlayer(actx, i);
03442         update_msg |= ctx->HandlePlayerSpeedChangeFFRew() && (ctx == actx);
03443     }
03444     ReturnPlayerLock(actx);
03445 
03446     actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
03447     for (uint i = 0; actx && (i < player.size()); i++)
03448     {
03449         PlayerContext *ctx = GetPlayer(actx, i);
03450         update_msg |= ctx->HandlePlayerSpeedChangeEOF() && (ctx == actx);
03451     }
03452 
03453     if (update_msg)
03454     {
03455         UpdateOSDSeekMessage(actx, actx->GetPlayMessage(), kOSDTimeout_Med);
03456     }
03457     ReturnPlayerLock(actx);
03458 }
03459 
03461 bool TV::eventFilter(QObject *o, QEvent *e)
03462 {
03463     // We want to intercept all resize events sent to the main window
03464     if ((e->type() == QEvent::Resize))
03465         return (GetMythMainWindow()!= o) ? false : event(e);
03466 
03467     // Intercept keypress events unless they need to be handled by a main UI
03468     // screen (e.g. GuideGrid, ProgramFinder)
03469     if (QEvent::KeyPress == e->type())
03470         return ignoreKeyPresses ? false : event(e);
03471 
03472     if (e->type() == MythEvent::MythEventMessage ||
03473         e->type() == MythEvent::MythUserMessage  ||
03474         e->type() == MythEvent::kUpdateTvProgressEventType)
03475     {
03476         customEvent(e);
03477         return true;
03478     }
03479 
03480     switch (e->type())
03481     {
03482         case QEvent::Paint:
03483         case QEvent::UpdateRequest:
03484         case QEvent::Enter:
03485         {
03486             event(e);
03487             return false;
03488         }
03489         default:
03490             return false;
03491     }
03492 }
03493 
03495 bool TV::event(QEvent *e)
03496 {
03497     if (QEvent::Resize == e->type())
03498     {
03499         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
03500         mctx->LockDeletePlayer(__FILE__, __LINE__);
03501         if (mctx->player)
03502             mctx->player->WindowResized(((const QResizeEvent*) e)->size());
03503         mctx->UnlockDeletePlayer(__FILE__, __LINE__);
03504         ReturnPlayerLock(mctx);
03505         return true;
03506     }
03507 
03508     if (QEvent::KeyPress == e->type())
03509     {
03510         bool handled = false;
03511         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
03512         if (actx->HasPlayer())
03513             handled = ProcessKeypress(actx, (QKeyEvent *)e);
03514         ReturnPlayerLock(actx);
03515         if (handled)
03516             return true;
03517     }
03518 
03519     switch (e->type())
03520     {
03521         case QEvent::Paint:
03522         case QEvent::UpdateRequest:
03523         case QEvent::Enter:
03524             DrawUnusedRects();
03525             return true;
03526         default:
03527             break;
03528     }
03529 
03530     return QObject::event(e);
03531 }
03532 
03533 bool TV::HandleTrackAction(PlayerContext *ctx, const QString &action)
03534 {
03535     ctx->LockDeletePlayer(__FILE__, __LINE__);
03536     if (!ctx->player)
03537     {
03538         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
03539         return false;
03540     }
03541 
03542     bool handled = true;
03543 
03544     if (action == ACTION_TOGGLEEXTTEXT)
03545         ctx->player->ToggleCaptions(kTrackTypeTextSubtitle);
03546     else if (ACTION_ENABLEEXTTEXT == action)
03547         ctx->player->EnableCaptions(kDisplayTextSubtitle);
03548     else if (ACTION_DISABLEEXTTEXT == action)
03549         ctx->player->DisableCaptions(kDisplayTextSubtitle);
03550     else if (ACTION_ENABLEFORCEDSUBS == action)
03551         ctx->player->SetAllowForcedSubtitles(true);
03552     else if (ACTION_DISABLEFORCEDSUBS == action)
03553         ctx->player->SetAllowForcedSubtitles(false);
03554     else if (action == ACTION_ENABLESUBS)
03555         ctx->player->SetCaptionsEnabled(true, true);
03556     else if (action == ACTION_DISABLESUBS)
03557         ctx->player->SetCaptionsEnabled(false, true);
03558     else if (action == ACTION_TOGGLESUBS && !browsehelper->IsBrowsing())
03559     {
03560         if (ccInputMode)
03561         {
03562             bool valid = false;
03563             int page = GetQueuedInputAsInt(&valid, 16);
03564             if (vbimode == VBIMode::PAL_TT && valid)
03565                 ctx->player->SetTeletextPage(page);
03566             else if (vbimode == VBIMode::NTSC_CC)
03567                 ctx->player->SetTrack(kTrackTypeCC608,
03568                                    max(min(page - 1, 1), 0));
03569 
03570             ClearInputQueues(ctx, true);
03571 
03572             QMutexLocker locker(&timerIdLock);
03573             ccInputMode = false;
03574             if (ccInputTimerId)
03575             {
03576                 KillTimer(ccInputTimerId);
03577                 ccInputTimerId = 0;
03578             }
03579         }
03580         else if (ctx->player->GetCaptionMode() & kDisplayNUVTeletextCaptions)
03581         {
03582             ClearInputQueues(ctx, false);
03583             AddKeyToInputQueue(ctx, 0);
03584 
03585             QMutexLocker locker(&timerIdLock);
03586             ccInputMode        = true;
03587             asInputMode        = false;
03588             ccInputTimerId = StartTimer(kInputModeTimeout, __LINE__);
03589             if (asInputTimerId)
03590             {
03591                 KillTimer(asInputTimerId);
03592                 asInputTimerId = 0;
03593             }
03594         }
03595         else
03596         {
03597             ctx->player->ToggleCaptions();
03598         }
03599     }
03600     else if (action.left(6) == "TOGGLE")
03601     {
03602         int type = to_track_type(action.mid(6));
03603         if (type == kTrackTypeTeletextMenu)
03604             ctx->player->EnableTeletext();
03605         else if (type >= kTrackTypeSubtitle)
03606             ctx->player->ToggleCaptions(type);
03607         else
03608             handled = false;
03609     }
03610     else if (action.left(6) == "SELECT")
03611     {
03612         int type = to_track_type(action.mid(6));
03613         int num = action.section("_", -1).toInt();
03614         if (type >= kTrackTypeAudio)
03615             ctx->player->SetTrack(type, num);
03616         else
03617             handled = false;
03618     }
03619     else if (action.left(4) == "NEXT" || action.left(4) == "PREV")
03620     {
03621         int dir = (action.left(4) == "NEXT") ? +1 : -1;
03622         int type = to_track_type(action.mid(4));
03623         if (type >= kTrackTypeAudio)
03624             ctx->player->ChangeTrack(type, dir);
03625         else if (action.right(2) == "CC")
03626             ctx->player->ChangeCaptionTrack(dir);
03627         else
03628             handled = false;
03629     }
03630     else
03631         handled = false;
03632 
03633     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
03634 
03635     return handled;
03636 }
03637 
03638 static bool has_action(QString action, const QStringList &actions)
03639 {
03640     QStringList::const_iterator it;
03641     for (it = actions.begin(); it != actions.end(); ++it)
03642     {
03643         if (action == *it)
03644             return true;
03645     }
03646     return false;
03647 }
03648 
03649 bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e)
03650 {
03651     bool ignoreKeys = actx->IsPlayerChangingBuffers();
03652 #if DEBUG_ACTIONS
03653     LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ProcessKeypress() ignoreKeys: %1")
03654             .arg(ignoreKeys));
03655 #endif // DEBUG_ACTIONS
03656 
03657     if (idleTimerId)
03658     {
03659         KillTimer(idleTimerId);
03660         idleTimerId = StartTimer(db_idle_timeout, __LINE__);
03661     }
03662 
03663     QStringList actions;
03664     bool handled = false;
03665 
03666     if (ignoreKeys)
03667     {
03668         handled = GetMythMainWindow()->TranslateKeyPress(
03669                   "TV Playback", e, actions);
03670 
03671         if (handled || actions.isEmpty())
03672             return true;
03673 
03674         bool esc   = has_action("ESCAPE", actions) ||
03675                      has_action("BACK", actions);
03676         bool pause = has_action(ACTION_PAUSE, actions);
03677         bool play  = has_action(ACTION_PLAY,  actions);
03678 
03679         if ((!esc || browsehelper->IsBrowsing()) && !pause && !play)
03680             return false;
03681     }
03682 
03683     OSD *osd = GetOSDLock(actx);
03684     if (osd && osd->DialogVisible())
03685     {
03686         osd->DialogHandleKeypress(e);
03687         handled = true;
03688     }
03689     ReturnOSDLock(actx, osd);
03690 
03691     if (editmode && !handled)
03692     {
03693         handled |= GetMythMainWindow()->TranslateKeyPress(
03694                    "TV Editing", e, actions);
03695 
03696         if (!handled)
03697         {
03698             if (has_action("MENU", actions))
03699             {
03700                 ShowOSDCutpoint(actx, "EDIT_CUT_POINTS");
03701                 handled = true;
03702             }
03703             if (has_action("ESCAPE", actions))
03704             {
03705                 if (!actx->player->IsCutListSaved())
03706                     ShowOSDCutpoint(actx, "EXIT_EDIT_MODE");
03707                 else
03708                 {
03709                     actx->LockDeletePlayer(__FILE__, __LINE__);
03710                     if (actx->player)
03711                         actx->player->DisableEdit(0);
03712                     actx->UnlockDeletePlayer(__FILE__, __LINE__);
03713                 }
03714                 handled = true;
03715             }
03716             else
03717             {
03718                 actx->LockDeletePlayer(__FILE__, __LINE__);
03719                 int64_t current_frame = actx->player->GetFramesPlayed();
03720                 actx->UnlockDeletePlayer(__FILE__, __LINE__);
03721                 if ((has_action(ACTION_SELECT, actions)) &&
03722                     (actx->player->IsInDelete(current_frame)) &&
03723                     (!(actx->player->HasTemporaryMark())))
03724                 {
03725                     ShowOSDCutpoint(actx, "EDIT_CUT_REGION");
03726                     handled = true;
03727                 }
03728                 else
03729                     handled |= actx->player->HandleProgramEditorActions(
03730                                                       actions, current_frame);
03731             }
03732         }
03733         if (handled)
03734             editmode = actx->player->GetEditMode();
03735     }
03736 
03737     if (handled)
03738         return true;
03739 
03740     // If text is already queued up, be more lax on what is ok.
03741     // This allows hex teletext entry and minor channel entry.
03742     const QString txt = e->text();
03743     if (HasQueuedInput() && (1 == txt.length()))
03744     {
03745         bool ok = false;
03746         txt.toInt(&ok, 16);
03747         if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
03748         {
03749             AddKeyToInputQueue(actx, txt.at(0).toLatin1());
03750             return true;
03751         }
03752     }
03753 
03754     // Teletext menu
03755     actx->LockDeletePlayer(__FILE__, __LINE__);
03756     if (actx->player && (actx->player->GetCaptionMode() == kDisplayTeletextMenu))
03757     {
03758         QStringList tt_actions;
03759         handled = GetMythMainWindow()->TranslateKeyPress(
03760                   "Teletext Menu", e, tt_actions);
03761 
03762         if (!handled && !tt_actions.isEmpty())
03763         {
03764             for (int i = 0; i < tt_actions.size(); i++)
03765             {
03766                 if (actx->player->HandleTeletextAction(tt_actions[i]))
03767                 {
03768                     actx->UnlockDeletePlayer(__FILE__, __LINE__);
03769                     return true;
03770                 }
03771             }
03772         }
03773     }
03774 
03775     // Interactive television
03776     if (actx->player && actx->player->GetInteractiveTV())
03777     {
03778         QStringList itv_actions;
03779         handled = GetMythMainWindow()->TranslateKeyPress(
03780                   "TV Playback", e, itv_actions);
03781 
03782         if (!handled && !itv_actions.isEmpty())
03783         {
03784             for (int i = 0; i < itv_actions.size(); i++)
03785             {
03786                 if (actx->player->ITVHandleAction(itv_actions[i]))
03787                 {
03788                     actx->UnlockDeletePlayer(__FILE__, __LINE__);
03789                     return true;
03790                 }
03791             }
03792         }
03793     }
03794     actx->UnlockDeletePlayer(__FILE__, __LINE__);
03795 
03796     handled = GetMythMainWindow()->TranslateKeyPress(
03797               "TV Playback", e, actions);
03798 
03799     if (handled || actions.isEmpty())
03800         return true;
03801 
03802     handled = false;
03803 
03804     bool isDVD = actx->buffer && actx->buffer->IsDVD();
03805     bool isMenuOrStill = actx->buffer->IsInDiscMenuOrStillFrame();
03806 
03807     handled = handled || BrowseHandleAction(actx, actions);
03808     handled = handled || ManualZoomHandleAction(actx, actions);
03809     handled = handled || PictureAttributeHandleAction(actx, actions);
03810     handled = handled || TimeStretchHandleAction(actx, actions);
03811     handled = handled || AudioSyncHandleAction(actx, actions);
03812     handled = handled || SubtitleZoomHandleAction(actx, actions);
03813     handled = handled || DiscMenuHandleAction(actx, actions);
03814     handled = handled || ActiveHandleAction(
03815         actx, actions, isDVD, isMenuOrStill);
03816     handled = handled || ToggleHandleAction(actx, actions, isDVD);
03817     handled = handled || PxPHandleAction(actx, actions);
03818     handled = handled || FFRewHandleAction(actx, actions);
03819     handled = handled || ActivePostQHandleAction(actx, actions);
03820 
03821 #if DEBUG_ACTIONS
03822     for (uint i = 0; i < actions.size(); ++i)
03823         LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("handled(%1) actions[%2](%3)")
03824                 .arg(handled).arg(i).arg(actions[i]));
03825 #endif // DEBUG_ACTIONS
03826 
03827     if (handled)
03828         return true;
03829 
03830     if (!handled)
03831     {
03832         for (int i = 0; i < actions.size() && !handled; i++)
03833         {
03834             QString action = actions[i];
03835             bool ok = false;
03836             int val = action.toInt(&ok);
03837 
03838             if (ok)
03839             {
03840                 AddKeyToInputQueue(actx, '0' + val);
03841                 handled = true;
03842             }
03843         }
03844     }
03845 
03846     return true;
03847 }
03848 
03849 bool TV::BrowseHandleAction(PlayerContext *ctx, const QStringList &actions)
03850 {
03851     if (!browsehelper->IsBrowsing())
03852         return false;
03853 
03854     bool handled = true;
03855 
03856     if (has_action(ACTION_UP, actions) || has_action(ACTION_CHANNELUP, actions))
03857         browsehelper->BrowseDispInfo(ctx, BROWSE_UP);
03858     else if (has_action(ACTION_DOWN, actions) || has_action(ACTION_CHANNELDOWN, actions))
03859         browsehelper->BrowseDispInfo(ctx, BROWSE_DOWN);
03860     else if (has_action(ACTION_LEFT, actions))
03861         browsehelper->BrowseDispInfo(ctx, BROWSE_LEFT);
03862     else if (has_action(ACTION_RIGHT, actions))
03863         browsehelper->BrowseDispInfo(ctx, BROWSE_RIGHT);
03864     else if (has_action("NEXTFAV", actions))
03865         browsehelper->BrowseDispInfo(ctx, BROWSE_FAVORITE);
03866     else if (has_action(ACTION_SELECT, actions))
03867     {
03868         browsehelper->BrowseEnd(ctx, true);
03869     }
03870     else if (has_action(ACTION_CLEAROSD, actions) ||
03871              has_action("ESCAPE",       actions) ||
03872              has_action("BACK",         actions) ||
03873              has_action("TOGGLEBROWSE", actions))
03874     {
03875         browsehelper->BrowseEnd(ctx, false);
03876     }
03877     else if (has_action(ACTION_TOGGLERECORD, actions))
03878         ToggleRecord(ctx);
03879     else
03880     {
03881         handled = false;
03882         QStringList::const_iterator it = actions.begin();
03883         for (; it != actions.end(); ++it)
03884         {
03885             if ((*it).length() == 1 && (*it)[0].isDigit())
03886             {
03887                 AddKeyToInputQueue(ctx, (*it)[0].toLatin1());
03888                 handled = true;
03889             }
03890         }
03891     }
03892 
03893     // only pass-through actions listed below
03894     return handled ||
03895         !(has_action(ACTION_VOLUMEDOWN, actions) ||
03896           has_action(ACTION_VOLUMEUP,   actions) ||
03897           has_action("STRETCHINC",      actions) ||
03898           has_action("STRETCHDEC",      actions) ||
03899           has_action(ACTION_MUTEAUDIO,  actions) ||
03900           has_action("CYCLEAUDIOCHAN",  actions) ||
03901           has_action("TOGGLEASPECT",    actions) ||
03902           has_action("TOGGLEPIPMODE",   actions) ||
03903           has_action("TOGGLEPIPSTATE",  actions) ||
03904           has_action("NEXTPIPWINDOW",   actions) ||
03905           has_action("CREATEPIPVIEW",   actions) ||
03906           has_action("CREATEPBPVIEW",   actions) ||
03907           has_action("SWAPPIP",         actions));
03908 }
03909 
03910 bool TV::ManualZoomHandleAction(PlayerContext *actx, const QStringList &actions)
03911 {
03912     if (!zoomMode)
03913         return false;
03914 
03915     actx->LockDeletePlayer(__FILE__, __LINE__);
03916     if (!actx->player)
03917     {
03918         actx->UnlockDeletePlayer(__FILE__, __LINE__);
03919         return false;
03920     }
03921 
03922     bool end_manual_zoom = false;
03923     bool handled = true;
03924     if (has_action(ACTION_UP, actions) ||
03925         has_action(ACTION_CHANNELUP, actions))
03926     {
03927         actx->player->Zoom(kZoomUp);
03928     }
03929     else if (has_action(ACTION_DOWN, actions) ||
03930              has_action(ACTION_CHANNELDOWN, actions))
03931     {
03932         actx->player->Zoom(kZoomDown);
03933     }
03934     else if (has_action(ACTION_LEFT, actions))
03935         actx->player->Zoom(kZoomLeft);
03936     else if (has_action(ACTION_RIGHT, actions))
03937         actx->player->Zoom(kZoomRight);
03938     else if (has_action(ACTION_VOLUMEUP, actions))
03939         actx->player->Zoom(kZoomAspectUp);
03940     else if (has_action(ACTION_VOLUMEDOWN, actions))
03941         actx->player->Zoom(kZoomAspectDown);
03942     else if (has_action("ESCAPE", actions) ||
03943              has_action("BACK", actions))
03944     {
03945         actx->player->Zoom(kZoomHome);
03946         end_manual_zoom = true;
03947     }
03948     else if (has_action(ACTION_SELECT, actions))
03949         SetManualZoom(actx, false, tr("Zoom Committed"));
03950     else if (has_action(ACTION_JUMPFFWD, actions))
03951         actx->player->Zoom(kZoomIn);
03952     else if (has_action(ACTION_JUMPRWND, actions))
03953         actx->player->Zoom(kZoomOut);
03954     else
03955     {
03956         // only pass-through actions listed below
03957         handled = !(has_action("STRETCHINC",     actions) ||
03958                     has_action("STRETCHDEC",     actions) ||
03959                     has_action(ACTION_MUTEAUDIO, actions) ||
03960                     has_action("CYCLEAUDIOCHAN", actions) ||
03961                     has_action(ACTION_PAUSE,     actions) ||
03962                     has_action(ACTION_CLEAROSD,  actions));
03963     }
03964     actx->UnlockDeletePlayer(__FILE__, __LINE__);
03965 
03966     if (end_manual_zoom)
03967         SetManualZoom(actx, false, tr("Zoom Ignored"));
03968 
03969     return handled;
03970 }
03971 
03972 bool TV::PictureAttributeHandleAction(PlayerContext *ctx,
03973                                       const QStringList &actions)
03974 {
03975     if (!adjustingPicture)
03976         return false;
03977 
03978     bool handled = true;
03979     if (has_action(ACTION_LEFT, actions))
03980     {
03981         DoChangePictureAttribute(ctx, adjustingPicture,
03982                                  adjustingPictureAttribute, false);
03983     }
03984     else if (has_action(ACTION_RIGHT, actions))
03985     {
03986         DoChangePictureAttribute(ctx, adjustingPicture,
03987                                  adjustingPictureAttribute, true);
03988     }
03989     else
03990         handled = false;
03991 
03992     return handled;
03993 }
03994 
03995 bool TV::TimeStretchHandleAction(PlayerContext *ctx,
03996                                  const QStringList &actions)
03997 {
03998     if (!stretchAdjustment)
03999         return false;
04000 
04001     bool handled = true;
04002 
04003     if (has_action(ACTION_LEFT, actions))
04004         ChangeTimeStretch(ctx, -1);
04005     else if (has_action(ACTION_RIGHT, actions))
04006         ChangeTimeStretch(ctx, 1);
04007     else if (has_action(ACTION_DOWN, actions))
04008         ChangeTimeStretch(ctx, -5);
04009     else if (has_action(ACTION_UP, actions))
04010         ChangeTimeStretch(ctx, 5);
04011     else if (has_action("ADJUSTSTRETCH", actions))
04012         ClearOSD(ctx);
04013     else
04014         handled = false;
04015 
04016     return handled;
04017 }
04018 
04019 bool TV::AudioSyncHandleAction(PlayerContext *ctx,
04020                                const QStringList &actions)
04021 {
04022     if (!audiosyncAdjustment)
04023         return false;
04024 
04025     bool handled = true;
04026 
04027     if (has_action(ACTION_LEFT, actions))
04028         ChangeAudioSync(ctx, -1);
04029     else if (has_action(ACTION_RIGHT, actions))
04030         ChangeAudioSync(ctx, 1);
04031     else if (has_action(ACTION_UP, actions))
04032         ChangeAudioSync(ctx, -10);
04033     else if (has_action(ACTION_DOWN, actions))
04034         ChangeAudioSync(ctx, 10);
04035     else if (has_action(ACTION_TOGGELAUDIOSYNC, actions))
04036         ClearOSD(ctx);
04037     else
04038         handled = false;
04039 
04040     return handled;
04041 }
04042 
04043 bool TV::SubtitleZoomHandleAction(PlayerContext *ctx,
04044                                   const QStringList &actions)
04045 {
04046     if (!subtitleZoomAdjustment)
04047         return false;
04048 
04049     bool handled = true;
04050 
04051     if (has_action(ACTION_LEFT, actions))
04052         ChangeSubtitleZoom(ctx, -1);
04053     else if (has_action(ACTION_RIGHT, actions))
04054         ChangeSubtitleZoom(ctx, 1);
04055     else if (has_action(ACTION_UP, actions))
04056         ChangeSubtitleZoom(ctx, -10);
04057     else if (has_action(ACTION_DOWN, actions))
04058         ChangeSubtitleZoom(ctx, 10);
04059     else if (has_action(ACTION_TOGGLESUBTITLEZOOM, actions))
04060         ClearOSD(ctx);
04061     else
04062         handled = false;
04063 
04064     return handled;
04065 }
04066 
04067 bool TV::DiscMenuHandleAction(PlayerContext *ctx, const QStringList &actions)
04068 {
04069     int64_t pts = 0;
04070     VideoOutput *output = ctx->player->GetVideoOutput();
04071     if (output)
04072     {
04073         VideoFrame *frame = output->GetLastShownFrame();
04074         if (frame)
04075         {
04076             // convert timecode (msec) to pts (90kHz)
04077             pts = (int64_t)(frame->timecode  * 90);
04078         }
04079     }
04080     return ctx->buffer->HandleAction(actions, pts);
04081 }
04082 
04083 bool TV::Handle3D(PlayerContext *ctx, const QString &action)
04084 {
04085     ctx->LockDeletePlayer(__FILE__, __LINE__);
04086     if (ctx->player && ctx->player->GetVideoOutput() &&
04087         ctx->player->GetVideoOutput()->StereoscopicModesAllowed())
04088     {
04089         StereoscopicMode mode = kStereoscopicModeNone;
04090         if (ACTION_3DSIDEBYSIDE == action)
04091             mode = kStereoscopicModeSideBySide;
04092         else if (ACTION_3DSIDEBYSIDEDISCARD == action)
04093             mode = kStereoscopicModeSideBySideDiscard;
04094         else if (ACTION_3DTOPANDBOTTOM == action)
04095             mode = kStereoscopicModeTopAndBottom;
04096         else if (ACTION_3DTOPANDBOTTOMDISCARD == action)
04097             mode = kStereoscopicModeTopAndBottomDiscard;
04098         ctx->player->GetVideoOutput()->SetStereoscopicMode(mode);
04099         SetOSDMessage(ctx, StereoscopictoString(mode));
04100     }
04101     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04102     return true;
04103 }
04104 
04105 bool TV::ActiveHandleAction(PlayerContext *ctx,
04106                             const QStringList &actions,
04107                             bool isDVD, bool isDVDStill)
04108 {
04109     bool handled = true;
04110 
04111     if (has_action("SKIPCOMMERCIAL", actions) && !isDVD)
04112         DoSkipCommercials(ctx, 1);
04113     else if (has_action("SKIPCOMMBACK", actions) && !isDVD)
04114         DoSkipCommercials(ctx, -1);
04115     else if (has_action("QUEUETRANSCODE", actions) && !isDVD)
04116         DoQueueTranscode(ctx, "Default");
04117     else if (has_action("QUEUETRANSCODE_AUTO", actions) && !isDVD)
04118         DoQueueTranscode(ctx, "Autodetect");
04119     else if (has_action("QUEUETRANSCODE_HIGH", actions)  && !isDVD)
04120         DoQueueTranscode(ctx, "High Quality");
04121     else if (has_action("QUEUETRANSCODE_MEDIUM", actions) && !isDVD)
04122         DoQueueTranscode(ctx, "Medium Quality");
04123     else if (has_action("QUEUETRANSCODE_LOW", actions) && !isDVD)
04124         DoQueueTranscode(ctx, "Low Quality");
04125     else if (has_action(ACTION_PLAY, actions))
04126         DoPlay(ctx);
04127     else if (has_action(ACTION_PAUSE, actions))
04128         DoTogglePause(ctx, true);
04129     else if (has_action("SPEEDINC", actions) && !isDVDStill)
04130         ChangeSpeed(ctx, 1);
04131     else if (has_action("SPEEDDEC", actions) && !isDVDStill)
04132         ChangeSpeed(ctx, -1);
04133     else if (has_action("ADJUSTSTRETCH", actions))
04134         ChangeTimeStretch(ctx, 0);   // just display
04135     else if (has_action("CYCLECOMMSKIPMODE",actions) && !isDVD)
04136         SetAutoCommercialSkip(ctx, kCommSkipIncr);
04137     else if (has_action("NEXTSCAN", actions))
04138     {
04139         QString msg = QString::null;
04140         ctx->LockDeletePlayer(__FILE__, __LINE__);
04141         if (ctx->player)
04142         {
04143             ctx->player->NextScanType();
04144             msg = toString(ctx->player->GetScanType());
04145         }
04146         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04147 
04148         if (!msg.isEmpty())
04149             SetOSDMessage(ctx, msg);
04150     }
04151     else if (has_action(ACTION_SEEKARB, actions) && !isDVD)
04152     {
04153         if (asInputMode)
04154         {
04155             ClearInputQueues(ctx, true);
04156             SetOSDText(ctx, "osd_input", "osd_number_entry", tr("Seek:"),
04157                        kOSDTimeout_Med);
04158 
04159             QMutexLocker locker(&timerIdLock);
04160             asInputMode = false;
04161             if (asInputTimerId)
04162             {
04163                 KillTimer(asInputTimerId);
04164                 asInputTimerId = 0;
04165             }
04166         }
04167         else
04168         {
04169             ClearInputQueues(ctx, false);
04170             AddKeyToInputQueue(ctx, 0);
04171 
04172             QMutexLocker locker(&timerIdLock);
04173             asInputMode        = true;
04174             ccInputMode        = false;
04175             asInputTimerId = StartTimer(kInputModeTimeout, __LINE__);
04176             if (ccInputTimerId)
04177             {
04178                 KillTimer(ccInputTimerId);
04179                 ccInputTimerId = 0;
04180             }
04181         }
04182     }
04183     else if (has_action(ACTION_JUMPRWND, actions))
04184         DoJumpRWND(ctx);
04185     else if (has_action(ACTION_JUMPFFWD, actions))
04186         DoJumpFFWD(ctx);
04187     else if (has_action(ACTION_JUMPBKMRK, actions))
04188     {
04189         ctx->LockDeletePlayer(__FILE__, __LINE__);
04190         uint64_t bookmark  = ctx->player->GetBookmark();
04191         float     rate     = ctx->player->GetFrameRate();
04192         float seekloc = ctx->player->TranslatePositionAbsToRel(bookmark) / rate;
04193         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04194 
04195         if (bookmark > rate)
04196             DoSeek(ctx, seekloc, tr("Jump to Bookmark"),
04197                    /*timeIsOffset*/false,
04198                    /*honorCutlist*/true);
04199     }
04200     else if (has_action(ACTION_JUMPSTART,actions))
04201     {
04202         DoSeek(ctx, 0, tr("Jump to Beginning"),
04203                /*timeIsOffset*/false,
04204                /*honorCutlist*/true);
04205     }
04206     else if (has_action(ACTION_CLEAROSD, actions))
04207     {
04208         ClearOSD(ctx);
04209     }
04210     else if (has_action(ACTION_VIEWSCHEDULED, actions))
04211         EditSchedule(ctx, kViewSchedule);
04212     else if (HandleJumpToProgramAction(ctx, actions))
04213     {
04214     }
04215     else if (has_action(ACTION_SIGNALMON, actions))
04216     {
04217         if ((GetState(ctx) == kState_WatchingLiveTV) && ctx->recorder)
04218         {
04219             QString input = ctx->recorder->GetInput();
04220             uint timeout  = ctx->recorder->GetSignalLockTimeout(input);
04221 
04222             if (timeout == 0xffffffff)
04223             {
04224                 SetOSDMessage(ctx, "No Signal Monitor");
04225                 return false;
04226             }
04227 
04228             int rate   = sigMonMode ? 0 : 100;
04229             int notify = sigMonMode ? 0 : 1;
04230 
04231             PauseLiveTV(ctx);
04232             ctx->recorder->SetSignalMonitoringRate(rate, notify);
04233             UnpauseLiveTV(ctx);
04234 
04235             lockTimerOn = false;
04236             sigMonMode  = !sigMonMode;
04237         }
04238     }
04239     else if (has_action(ACTION_SCREENSHOT, actions))
04240     {
04241         ctx->LockDeletePlayer(__FILE__, __LINE__);
04242         if (ctx->player && ctx->player->GetScreenShot())
04243         {
04244             // VideoOutput has saved screenshot
04245         }
04246         else
04247         {
04248             GetMythMainWindow()->ScreenShot();
04249         }
04250         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04251     }
04252     else if (has_action(ACTION_STOP, actions))
04253     {
04254         PrepareToExitPlayer(ctx, __LINE__);
04255         SetExitPlayer(true, true);
04256     }
04257     else if (has_action(ACTION_EXITSHOWNOPROMPTS, actions))
04258     {
04259         requestDelete = false;
04260         PrepareToExitPlayer(ctx, __LINE__);
04261         SetExitPlayer(true, true);
04262     }
04263     else if (has_action("ESCAPE", actions) ||
04264              has_action("BACK", actions))
04265     {
04266         if (StateIsLiveTV(ctx->GetState()) &&
04267             (ctx->lastSignalMsgTime.elapsed() <
04268              (int)PlayerContext::kSMExitTimeout))
04269         {
04270             ClearOSD(ctx);
04271         }
04272         else
04273         {
04274             OSD *osd = GetOSDLock(ctx);
04275             if (osd && osd->IsVisible())
04276             {
04277                 ClearOSD(ctx);
04278                 ReturnOSDLock(ctx, osd);
04279                 return handled;
04280             }
04281             ReturnOSDLock(ctx, osd);
04282         }
04283 
04284         NormalSpeed(ctx);
04285 
04286         StopFFRew(ctx);
04287 
04288         bool do_exit = false;
04289 
04290         if (StateIsLiveTV(GetState(ctx)))
04291         {
04292             if (ctx->HasPlayer() && (12 & db_playback_exit_prompt))
04293             {
04294                 ShowOSDStopWatchingRecording(ctx);
04295                 return handled;
04296             }
04297             else
04298             {
04299                 do_exit = true;
04300             }
04301         }
04302         else
04303         {
04304             if (ctx->HasPlayer() && (5 & db_playback_exit_prompt) &&
04305                 !underNetworkControl && !isDVDStill)
04306             {
04307                 ShowOSDStopWatchingRecording(ctx);
04308                 return handled;
04309             }
04310             PrepareToExitPlayer(ctx, __LINE__);
04311             requestDelete = false;
04312             do_exit = true;
04313         }
04314 
04315         if (do_exit)
04316         {
04317             PlayerContext *mctx = GetPlayer(ctx, 0);
04318             if (mctx != ctx)
04319             { // A PIP is active, just tear it down..
04320                 PxPTeardownView(ctx);
04321                 return handled;
04322             }
04323             else
04324             {
04325                 // If it's a DVD, and we're not trying to execute a
04326                 // jumppoint, and it's not in a menu, then first try
04327                 // jumping to the title or root menu.
04328                 if (isDVD &&
04329                     !GetMythMainWindow()->IsExitingToMain() &&
04330                     has_action("BACK", actions) &&
04331                     !ctx->buffer->DVD()->IsInMenu() &&
04332                     (ctx->player->GoToMenu("title") ||
04333                      ctx->player->GoToMenu("root"))
04334                     )
04335                 {
04336                     return handled;
04337                 }
04338                 SetExitPlayer(true, true);
04339             }
04340         }
04341 
04342         SetActive(ctx, 0, false);
04343     }
04344     else if (has_action(ACTION_ENABLEUPMIX, actions))
04345         EnableUpmix(ctx, true);
04346     else if (has_action(ACTION_DISABLEUPMIX, actions))
04347         EnableUpmix(ctx, false);
04348     else if (has_action(ACTION_VOLUMEDOWN, actions))
04349         ChangeVolume(ctx, false);
04350     else if (has_action(ACTION_VOLUMEUP, actions))
04351         ChangeVolume(ctx, true);
04352     else if (has_action("CYCLEAUDIOCHAN", actions))
04353         ToggleMute(ctx, true);
04354     else if (has_action(ACTION_MUTEAUDIO, actions))
04355         ToggleMute(ctx);
04356     else if (has_action("STRETCHINC", actions))
04357         ChangeTimeStretch(ctx, 1);
04358     else if (has_action("STRETCHDEC", actions))
04359         ChangeTimeStretch(ctx, -1);
04360     else if (has_action("MENU", actions))
04361         ShowOSDMenu(ctx);
04362     else if (has_action("INFO", actions) ||
04363              has_action("INFOWITHCUTLIST", actions))
04364     {
04365         if (HasQueuedInput())
04366         {
04367             DoArbSeek(ctx, ARBSEEK_SET,
04368                       has_action("INFOWITHCUTLIST", actions));
04369         }
04370         else
04371             ToggleOSD(ctx, true);
04372     }
04373     else if (has_action(ACTION_TOGGLEOSDDEBUG, actions))
04374         ToggleOSDDebug(ctx);
04375     else if (!isDVDStill && SeekHandleAction(ctx, actions, isDVD))
04376     {
04377     }
04378     else
04379     {
04380         handled = false;
04381         QStringList::const_iterator it = actions.begin();
04382         for (; it != actions.end() && !handled; ++it)
04383             handled = HandleTrackAction(ctx, *it);
04384     }
04385 
04386     return handled;
04387 }
04388 
04389 bool TV::FFRewHandleAction(PlayerContext *ctx, const QStringList &actions)
04390 {
04391     bool handled = false;
04392 
04393     if (ctx->ff_rew_state)
04394     {
04395         for (int i = 0; i < actions.size() && !handled; i++)
04396         {
04397             QString action = actions[i];
04398             bool ok = false;
04399             int val = action.toInt(&ok);
04400 
04401             if (ok && val < (int)ff_rew_speeds.size())
04402             {
04403                 SetFFRew(ctx, val);
04404                 handled = true;
04405             }
04406         }
04407 
04408         if (!handled)
04409         {
04410             DoPlayerSeek(ctx, StopFFRew(ctx));
04411             UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Med);
04412             handled = true;
04413         }
04414     }
04415 
04416     if (ctx->ff_rew_speed)
04417     {
04418         NormalSpeed(ctx);
04419         UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Med);
04420         handled = true;
04421     }
04422 
04423     return handled;
04424 }
04425 
04426 bool TV::ToggleHandleAction(PlayerContext *ctx,
04427                             const QStringList &actions, bool isDVD)
04428 {
04429     bool handled = true;
04430     bool islivetv = StateIsLiveTV(GetState(ctx));
04431 
04432     if (has_action("TOGGLEASPECT", actions))
04433         ToggleAspectOverride(ctx);
04434     else if (has_action("TOGGLEFILL", actions))
04435         ToggleAdjustFill(ctx);
04436     else if (has_action(ACTION_TOGGELAUDIOSYNC, actions))
04437         ChangeAudioSync(ctx, 0);   // just display
04438     else if (has_action(ACTION_TOGGLESUBTITLEZOOM, actions))
04439         ChangeSubtitleZoom(ctx, 0);   // just display
04440     else if (has_action(ACTION_TOGGLEVISUALISATION, actions))
04441         EnableVisualisation(ctx, false, true /*toggle*/);
04442     else if (has_action(ACTION_ENABLEVISUALISATION, actions))
04443         EnableVisualisation(ctx, true);
04444     else if (has_action(ACTION_DISABLEVISUALISATION, actions))
04445         EnableVisualisation(ctx, false);
04446     else if (has_action("TOGGLEPICCONTROLS", actions))
04447         DoTogglePictureAttribute(ctx, kAdjustingPicture_Playback);
04448     else if (has_action(ACTION_TOGGLESTUDIOLEVELS, actions))
04449         DoToggleStudioLevels(ctx);
04450     else if (has_action(ACTION_TOGGLENIGHTMODE, actions))
04451         DoToggleNightMode(ctx);
04452     else if (has_action("TOGGLESTRETCH", actions))
04453         ToggleTimeStretch(ctx);
04454     else if (has_action(ACTION_TOGGLEUPMIX, actions))
04455         EnableUpmix(ctx, false, true);
04456     else if (has_action(ACTION_TOGGLESLEEP, actions))
04457         ToggleSleepTimer(ctx);
04458     else if (has_action(ACTION_TOGGLERECORD, actions) && islivetv)
04459         ToggleRecord(ctx);
04460     else if (has_action(ACTION_TOGGLEFAV, actions) && islivetv)
04461         ToggleChannelFavorite(ctx);
04462     else if (has_action(ACTION_TOGGLECHANCONTROLS, actions) && islivetv)
04463         DoTogglePictureAttribute(ctx, kAdjustingPicture_Channel);
04464     else if (has_action(ACTION_TOGGLERECCONTROLS, actions) && islivetv)
04465         DoTogglePictureAttribute(ctx, kAdjustingPicture_Recording);
04466     else if (has_action(ACTION_TOGGLEINPUTS, actions) &&
04467              islivetv && !ctx->pseudoLiveTVState)
04468     {
04469         ToggleInputs(ctx);
04470     }
04471     else if (has_action("TOGGLEBROWSE", actions))
04472     {
04473         if (islivetv)
04474             browsehelper->BrowseStart(ctx);
04475         else if (!isDVD)
04476             ShowOSDMenu(ctx);
04477         else
04478             handled = false;
04479     }
04480     else if (has_action("EDIT", actions))
04481     {
04482         if (islivetv)
04483             StartChannelEditMode(ctx);
04484         else if (!isDVD)
04485             StartProgramEditMode(ctx);
04486     }
04487     else
04488         handled = false;
04489 
04490     return handled;
04491 }
04492 
04493 void TV::EnableVisualisation(const PlayerContext *ctx, bool enable,
04494                             bool toggle, const QString &action)
04495 {
04496     QString visualiser = QString("");
04497     if (action.startsWith("VISUALISER"))
04498         visualiser = action.mid(11);
04499 
04500     ctx->LockDeletePlayer(__FILE__, __LINE__);
04501     if (ctx->player && ctx->player->CanVisualise())
04502     {
04503         bool want = enable || !visualiser.isEmpty();
04504         if (toggle && visualiser.isEmpty())
04505             want = !ctx->player->IsVisualising();
04506         bool on = ctx->player->EnableVisualisation(want, visualiser);
04507         SetOSDMessage(ctx, on ? ctx->player->GetVisualiserName() :
04508                                 tr("Visualisation Off"));
04509     }
04510     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04511 }
04512 
04513 bool TV::PxPHandleAction(PlayerContext *ctx, const QStringList &actions)
04514 {
04515     if (!IsPIPSupported(ctx) && !IsPBPSupported(ctx))
04516         return false;
04517 
04518     bool handled = true;
04519     {
04520         QMutexLocker locker(&timerIdLock);
04521 
04522         if (has_action("TOGGLEPIPMODE", actions))
04523             changePxP.enqueue("TOGGLEPIPMODE");
04524         else if (has_action("TOGGLEPBPMODE", actions))
04525             changePxP.enqueue("TOGGLEPBPMODE");
04526         else if (has_action("CREATEPIPVIEW", actions))
04527             changePxP.enqueue("CREATEPIPVIEW");
04528         else if (has_action("CREATEPBPVIEW", actions))
04529             changePxP.enqueue("CREATEPBPVIEW");
04530         else if (has_action("SWAPPIP", actions))
04531             changePxP.enqueue("SWAPPIP");
04532         else if (has_action("TOGGLEPIPSTATE", actions))
04533             changePxP.enqueue("TOGGLEPIPSTATE");
04534         else
04535             handled = false;
04536 
04537         if (!changePxP.empty() && !pipChangeTimerId)
04538             pipChangeTimerId = StartTimer(1, __LINE__);
04539     }
04540 
04541     if (has_action("NEXTPIPWINDOW", actions))
04542     {
04543         SetActive(ctx, -1, true);
04544         handled = true;
04545     }
04546 
04547     return handled;
04548 }
04549 
04550 void TV::SetBookmark(PlayerContext *ctx, bool clear)
04551 {
04552     ctx->LockDeletePlayer(__FILE__, __LINE__);
04553     if (ctx->player)
04554     {
04555         if (clear)
04556         {
04557             ctx->player->SetBookmark(true);
04558             SetOSDMessage(ctx, QObject::tr("Bookmark Cleared"));
04559         }
04560         else if (IsBookmarkAllowed(ctx))
04561         {
04562             ctx->player->SetBookmark();
04563             osdInfo info;
04564             ctx->CalcPlayerSliderPosition(info);
04565             info.text["title"] = QObject::tr("Position");
04566             UpdateOSDStatus(ctx, info, kOSDFunctionalType_Default,
04567                             kOSDTimeout_Med);
04568             SetOSDMessage(ctx, QObject::tr("Bookmark Saved"));
04569         }
04570     }
04571     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04572 }
04573 
04574 bool TV::ActivePostQHandleAction(PlayerContext *ctx, const QStringList &actions)
04575 {
04576     bool handled = true;
04577     TVState state = GetState(ctx);
04578     bool islivetv = StateIsLiveTV(state);
04579     bool isdvd  = state == kState_WatchingDVD;
04580     bool isdisc = isdvd || state == kState_WatchingBD;
04581 
04582     if (has_action(ACTION_SELECT, actions))
04583     {
04584         if (!islivetv || !CommitQueuedInput(ctx))
04585         {
04586             ctx->LockDeletePlayer(__FILE__, __LINE__);
04587             SetBookmark(ctx, db_toggle_bookmark && ctx->player->GetBookmark());
04588             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04589         }
04590     }
04591     else if (has_action("NEXTFAV", actions) && islivetv)
04592         ChangeChannel(ctx, CHANNEL_DIRECTION_FAVORITE);
04593     else if (has_action("NEXTSOURCE", actions) && islivetv)
04594         SwitchSource(ctx, kNextSource);
04595     else if (has_action("PREVSOURCE", actions) && islivetv)
04596         SwitchSource(ctx, kPreviousSource);
04597     else if (has_action("NEXTINPUT", actions) && islivetv)
04598         ToggleInputs(ctx);
04599     else if (has_action("NEXTCARD", actions) && islivetv)
04600         SwitchCards(ctx);
04601     else if (has_action(ACTION_GUIDE, actions))
04602         EditSchedule(ctx, kScheduleProgramGuide);
04603     else if (has_action("PREVCHAN", actions) && islivetv)
04604         PopPreviousChannel(ctx, false);
04605     else if (has_action(ACTION_CHANNELUP, actions))
04606     {
04607         if (islivetv)
04608         {
04609             if (db_browse_always)
04610                 browsehelper->BrowseDispInfo(ctx, BROWSE_UP);
04611             else
04612                 ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
04613         }
04614         else
04615             DoJumpRWND(ctx);
04616     }
04617     else if (has_action(ACTION_CHANNELDOWN, actions))
04618     {
04619         if (islivetv)
04620         {
04621             if (db_browse_always)
04622                 browsehelper->BrowseDispInfo(ctx, BROWSE_DOWN);
04623             else
04624                 ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
04625         }
04626         else
04627             DoJumpFFWD(ctx);
04628     }
04629     else if (has_action("DELETE", actions) && !islivetv)
04630     {
04631         NormalSpeed(ctx);
04632         StopFFRew(ctx);
04633         SetBookmark(ctx);
04634         ShowOSDPromptDeleteRecording(ctx, tr("Are you sure you want to delete:"));
04635     }
04636     else if (has_action(ACTION_JUMPTODVDROOTMENU, actions) && isdisc)
04637     {
04638         ctx->LockDeletePlayer(__FILE__, __LINE__);
04639         if (ctx->player)
04640             ctx->player->GoToMenu("root");
04641         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04642     }
04643     else if (has_action(ACTION_JUMPTOPOPUPMENU, actions) && isdisc)
04644     {
04645         ctx->LockDeletePlayer(__FILE__, __LINE__);
04646         if (ctx->player)
04647             ctx->player->GoToMenu("popup");
04648         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04649     }
04650     else if (has_action(ACTION_FINDER, actions))
04651         EditSchedule(ctx, kScheduleProgramFinder);
04652     else
04653         handled = false;
04654 
04655     return handled;
04656 }
04657 
04658 
04659 void TV::ProcessNetworkControlCommand(PlayerContext *ctx,
04660                                       const QString &command)
04661 {
04662     bool ignoreKeys = ctx->IsPlayerChangingBuffers();
04663 #ifdef DEBUG_ACTIONS
04664     LOG(VB_GENERAL, LOG_DEBUG, LOC + "ProcessNetworkControlCommand(" +
04665             QString("%1) ignoreKeys: %2").arg(command).arg(ignoreKeys));
04666 #endif
04667 
04668     if (ignoreKeys)
04669     {
04670         LOG(VB_GENERAL, LOG_WARNING, LOC +
04671                 "Ignoring network control command"
04672                 "\n\t\t\tbecause ignoreKeys is set");
04673         return;
04674     }
04675 
04676     QStringList tokens = command.split(" ", QString::SkipEmptyParts);
04677     if (tokens.size() < 2)
04678     {
04679         LOG(VB_GENERAL, LOG_ERR, LOC + "Not enough tokens"
04680                 "in network control command" + "\n\t\t\t" +
04681                 QString("'%1'").arg(command));
04682         return;
04683     }
04684 
04685     OSD *osd = GetOSDLock(ctx);
04686     bool dlg = false;
04687     if (osd)
04688         dlg = osd->DialogVisible();
04689     ReturnOSDLock(ctx, osd);
04690 
04691     if (dlg)
04692     {
04693         LOG(VB_GENERAL, LOG_WARNING, LOC +
04694             "Ignoring network control command\n\t\t\t" +
04695             QString("because dialog is waiting for a response"));
04696         return;
04697     }
04698 
04699     if (tokens[1] != "QUERY")
04700         ClearOSD(ctx);
04701 
04702     if (tokens.size() == 3 && tokens[1] == "CHANID")
04703     {
04704         queuedChanID = tokens[2].toUInt();
04705         queuedChanNum = QString::null;
04706         CommitQueuedInput(ctx);
04707     }
04708     else if (tokens.size() == 3 && tokens[1] == "CHANNEL")
04709     {
04710         if (StateIsLiveTV(GetState(ctx)))
04711         {
04712             if (tokens[2] == "UP")
04713                 ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
04714             else if (tokens[2] == "DOWN")
04715                 ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
04716             else if (tokens[2].contains(QRegExp("^[-\\.\\d_#]+$")))
04717                 ChangeChannel(ctx, 0, tokens[2]);
04718         }
04719     }
04720     else if (tokens.size() == 3 && tokens[1] == "SPEED")
04721     {
04722         bool paused = ContextIsPaused(ctx, __FILE__, __LINE__);
04723 
04724         if (tokens[2] == "0x")
04725         {
04726             NormalSpeed(ctx);
04727             StopFFRew(ctx);
04728             if (!paused)
04729                 DoTogglePause(ctx, true);
04730         }
04731         else
04732         {
04733             float tmpSpeed = 1.0f;
04734             bool ok = false;
04735 
04736             if (tokens[2].contains(QRegExp("^\\-*\\d+x$")))
04737             {
04738                 QString speed = tokens[2].left(tokens[2].length()-1);
04739                 tmpSpeed = speed.toFloat(&ok);
04740             }
04741             else if (tokens[2].contains(QRegExp("^\\-*\\d*\\.\\d+x$")))
04742             {
04743                 QString speed = tokens[2].left(tokens[2].length() - 1);
04744                 tmpSpeed = speed.toFloat(&ok);
04745             }
04746             else
04747             {
04748                 QRegExp re = QRegExp("^(\\-*\\d+)\\/(\\d+)x$");
04749                 if (tokens[2].contains(re))
04750                 {
04751                     QStringList matches = re.capturedTexts();
04752 
04753                     int numerator, denominator;
04754                     numerator = matches[1].toInt(&ok);
04755                     denominator = matches[2].toInt(&ok);
04756 
04757                     if (ok && denominator != 0)
04758                         tmpSpeed = static_cast<float>(numerator) /
04759                                    static_cast<float>(denominator);
04760                     else
04761                         ok = false;
04762                 }
04763             }
04764 
04765             if (ok)
04766             {
04767                 float searchSpeed = fabs(tmpSpeed);
04768                 unsigned int index;
04769 
04770                 if (paused)
04771                     DoTogglePause(ctx, true);
04772 
04773                 if (tmpSpeed == 0.0f)
04774                 {
04775                     NormalSpeed(ctx);
04776                     StopFFRew(ctx);
04777 
04778                     if (!paused)
04779                         DoTogglePause(ctx, true);
04780                 }
04781                 else if (tmpSpeed == 1.0f)
04782                 {
04783                     StopFFRew(ctx);
04784                     ctx->ts_normal = 1.0f;
04785                     ChangeTimeStretch(ctx, 0, false);
04786 
04787                     ReturnPlayerLock(ctx);
04788                     return;
04789                 }
04790 
04791                 NormalSpeed(ctx);
04792 
04793                 for (index = 0; index < ff_rew_speeds.size(); index++)
04794                     if (float(ff_rew_speeds[index]) == searchSpeed)
04795                         break;
04796 
04797                 if ((index < ff_rew_speeds.size()) &&
04798                     (float(ff_rew_speeds[index]) == searchSpeed))
04799                 {
04800                     if (tmpSpeed < 0)
04801                         ctx->ff_rew_state = -1;
04802                     else if (tmpSpeed > 1)
04803                         ctx->ff_rew_state = 1;
04804                     else
04805                         StopFFRew(ctx);
04806 
04807                     if (ctx->ff_rew_state)
04808                         SetFFRew(ctx, index);
04809                 }
04810                 else if (0.48 <= tmpSpeed && tmpSpeed <= 2.0) {
04811                     StopFFRew(ctx);
04812 
04813                     ctx->ts_normal = tmpSpeed;   // alter speed before display
04814                     ChangeTimeStretch(ctx, 0, false);
04815                 }
04816                 else
04817                 {
04818                     LOG(VB_GENERAL, LOG_WARNING,
04819                         QString("Couldn't find %1 speed. Setting Speed to 1x")
04820                             .arg(searchSpeed));
04821 
04822                     ctx->ff_rew_state = 0;
04823                     SetFFRew(ctx, kInitFFRWSpeed);
04824                 }
04825             }
04826             else
04827             {
04828                 LOG(VB_GENERAL, LOG_ERR,
04829                     QString("Found an unknown speed of %1").arg(tokens[2]));
04830             }
04831         }
04832     }
04833     else if (tokens.size() == 2 && tokens[1] == "STOP")
04834     {
04835         SetBookmark(ctx);
04836         ctx->LockDeletePlayer(__FILE__, __LINE__);
04837         if (ctx->player && db_auto_set_watched)
04838             ctx->player->SetWatched();
04839         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04840         SetExitPlayer(true, true);
04841     }
04842     else if (tokens.size() >= 3 && tokens[1] == "SEEK" && ctx->HasPlayer())
04843     {
04844         if (ctx->buffer && ctx->buffer->IsInDiscMenuOrStillFrame())
04845             return;
04846 
04847         ctx->LockDeletePlayer(__FILE__, __LINE__);
04848         long long fplay = 0;
04849         if (ctx->player && (tokens[2] == "BEGINNING" || tokens[2] == "POSITION"))
04850         {
04851             fplay = ctx->player->GetFramesPlayed();
04852         }
04853         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04854 
04855         if (tokens[2] == "BEGINNING")
04856             DoSeek(ctx, 0, tr("Jump to Beginning"),
04857                    /*timeIsOffset*/false,
04858                    /*honorCutlist*/true);
04859         else if (tokens[2] == "FORWARD")
04860             DoSeek(ctx, ctx->fftime, tr("Skip Ahead"),
04861                    /*timeIsOffset*/true,
04862                    /*honorCutlist*/true);
04863         else if (tokens[2] == "BACKWARD")
04864             DoSeek(ctx, -ctx->rewtime, tr("Skip Back"),
04865                    /*timeIsOffset*/true,
04866                    /*honorCutlist*/true);
04867         else if ((tokens[2] == "POSITION" ||
04868                   tokens[2] == "POSITIONWITHCUTLIST") &&
04869                  (tokens.size() == 4) &&
04870                  (tokens[3].contains(QRegExp("^\\d+$"))))
04871         {
04872             DoSeekAbsolute(ctx, tokens[3].toInt(),
04873                            tokens[2] == "POSITIONWITHCUTLIST");
04874         }
04875     }
04876     else if (tokens.size() >= 3 && tokens[1] == "VOLUME")
04877     {
04878         QRegExp re = QRegExp("(\\d+)%");
04879         if (tokens[2].contains(re))
04880         {
04881             QStringList matches = re.capturedTexts();
04882 
04883             LOG(VB_GENERAL, LOG_INFO, QString("Set Volume to %1%")
04884                     .arg(matches[1]));
04885 
04886             bool ok = false;
04887 
04888             int vol = matches[1].toInt(&ok);
04889 
04890             if (!ok)
04891                 return;
04892 
04893             if ( 0 <= vol && vol <= 100)
04894             {
04895                 ctx->LockDeletePlayer(__FILE__, __LINE__);
04896                 if (!ctx->player)
04897                 {
04898                     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04899                     return;
04900                 }
04901 
04902                 vol -= ctx->player->GetVolume();
04903                 vol = ctx->player->AdjustVolume(vol);
04904                 ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04905 
04906                 if (!browsehelper->IsBrowsing() && !editmode)
04907                 {
04908                     UpdateOSDStatus(ctx, tr("Adjust Volume"), tr("Volume"),
04909                                     QString::number(vol),
04910                                     kOSDFunctionalType_PictureAdjust, "%", vol * 10,
04911                                     kOSDTimeout_Med);
04912                     SetUpdateOSDPosition(false);
04913                 }
04914             }
04915         }
04916     }
04917     else if (tokens.size() >= 3 && tokens[1] == "QUERY")
04918     {
04919         if (tokens[2] == "POSITION")
04920         {
04921             QString speedStr;
04922             if (ContextIsPaused(ctx, __FILE__, __LINE__))
04923             {
04924                 speedStr = "pause";
04925             }
04926             else if (ctx->ff_rew_state)
04927             {
04928                 speedStr = QString("%1x").arg(ctx->ff_rew_speed);
04929             }
04930             else
04931             {
04932                 QRegExp re = QRegExp("Play (.*)x");
04933                 if (QString(ctx->GetPlayMessage()).contains(re))
04934                 {
04935                     QStringList matches = re.capturedTexts();
04936                     speedStr = QString("%1x").arg(matches[1]);
04937                 }
04938                 else
04939                 {
04940                     speedStr = "1x";
04941                 }
04942             }
04943 
04944             osdInfo info;
04945             ctx->CalcPlayerSliderPosition(info, true);
04946 
04947             QDateTime respDate = mythCurrentDateTime();
04948             QString infoStr = "";
04949 
04950             ctx->LockDeletePlayer(__FILE__, __LINE__);
04951             long long fplay = 0;
04952             float     rate  = 30.0f;
04953             if (ctx->player)
04954             {
04955                 fplay = ctx->player->GetFramesPlayed();
04956                 rate  = ctx->player->GetFrameRate();
04957             }
04958             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
04959 
04960             ctx->LockPlayingInfo(__FILE__, __LINE__);
04961             if (ctx->GetState() == kState_WatchingLiveTV)
04962             {
04963                 infoStr = "LiveTV";
04964                 if (ctx->playingInfo)
04965                     respDate = ctx->playingInfo->GetScheduledStartTime();
04966             }
04967             else
04968             {
04969                 if (ctx->buffer->IsDVD())
04970                     infoStr = "DVD";
04971                 else if (ctx->playingInfo->IsRecording())
04972                     infoStr = "Recorded";
04973                 else
04974                     infoStr = "Video";
04975 
04976                 if (ctx->playingInfo)
04977                     respDate = ctx->playingInfo->GetRecordingStartTime();
04978             }
04979 
04980             if ((infoStr == "Recorded") || (infoStr == "LiveTV"))
04981             {
04982                 infoStr += QString(" %1 %2 %3 %4 %5 %6 %7")
04983                     .arg(info.text["description"])
04984                     .arg(speedStr)
04985                     .arg(ctx->playingInfo != NULL ?
04986                          ctx->playingInfo->GetChanID() : 0)
04987                     .arg(respDate.toString(Qt::ISODate))
04988                     .arg(fplay)
04989                     .arg(ctx->buffer->GetFilename())
04990                     .arg(rate);
04991             }
04992             else
04993             {
04994                 QString position = info.text["description"].section(" ",0,0);
04995                 infoStr += QString(" %1 %2 %3 %4 %5")
04996                     .arg(position)
04997                     .arg(speedStr)
04998                     .arg(ctx->buffer->GetFilename())
04999                     .arg(fplay)
05000                     .arg(rate);
05001             }
05002 
05003             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
05004 
05005             QString message = QString("NETWORK_CONTROL ANSWER %1")
05006                                 .arg(infoStr);
05007             MythEvent me(message);
05008             gCoreContext->dispatch(me);
05009         }
05010         else if (tokens[2] == "VOLUME")
05011         {
05012             QString infoStr = QString("%1%").arg(ctx->player->GetVolume());
05013 
05014             QString message = QString("NETWORK_CONTROL ANSWER %1")
05015                 .arg(infoStr);
05016             MythEvent me(message);
05017             gCoreContext->dispatch(me);
05018         }
05019     }
05020 }
05021 
05026 bool TV::CreatePBP(PlayerContext *ctx, const ProgramInfo *info)
05027 {
05028     LOG(VB_PLAYBACK, LOG_INFO, LOC + "CreatePBP() -- begin");
05029 
05030     if (player.size() > 1)
05031     {
05032         LOG(VB_GENERAL, LOG_ERR, LOC + "CreatePBP() -- end : "
05033                 "only allowed when player.size() == 1");
05034         return false;
05035     }
05036 
05037     PlayerContext *mctx = GetPlayer(ctx, 0);
05038     if (!IsPBPSupported(mctx))
05039     {
05040         LOG(VB_GENERAL, LOG_ERR, LOC + "CreatePBP() -- end : "
05041                 "PBP not supported by video method.");
05042         return false;
05043     }
05044 
05045     if (!mctx->player)
05046         return false;
05047     mctx->LockDeletePlayer(__FILE__, __LINE__);
05048     long long mctx_frame = mctx->player->GetFramesPlayed();
05049     mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05050 
05051     // This is safe because we are already holding lock for a ctx
05052     player.push_back(new PlayerContext(kPBPPlayerInUseID));
05053     PlayerContext *pbpctx = player.back();
05054     if (noHardwareDecoders)
05055         pbpctx->SetNoHardwareDecoders();
05056     pbpctx->SetPIPState(kPBPRight);
05057 
05058     if (info)
05059     {
05060         pbpctx->SetPlayingInfo(info);
05061         pbpctx->SetInitialTVState(false);
05062         ScheduleStateChange(pbpctx);
05063     }
05064     else if (RequestNextRecorder(pbpctx, false))
05065     {
05066         pbpctx->SetInitialTVState(true);
05067         ScheduleStateChange(pbpctx);
05068     }
05069     else
05070     {
05071         delete player.back();
05072         player.pop_back();
05073         return false;
05074     }
05075 
05076     mctx->PIPTeardown();
05077     mctx->SetPIPState(kPBPLeft);
05078     mctx->buffer->Seek(0, SEEK_SET);
05079 
05080     if (StateIsLiveTV(mctx->GetState()))
05081         mctx->buffer->Unpause();
05082 
05083     bool ok = mctx->CreatePlayer(
05084         this, GetMythMainWindow(), mctx->GetState(), false);
05085 
05086     if (ok)
05087     {
05088         ScheduleStateChange(mctx);
05089         mctx->LockDeletePlayer(__FILE__, __LINE__);
05090         if (mctx->player)
05091             mctx->player->JumpToFrame(mctx_frame);
05092         mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05093         SetSpeedChangeTimer(25, __LINE__);
05094     }
05095     else
05096     {
05097         LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to restart new main context");
05098         // Make putative PBP context the main context
05099         swap(player[0],player[1]);
05100         player[0]->SetPIPState(kPIPOff);
05101         // End the old main context..
05102         ForceNextStateNone(mctx);
05103     }
05104 
05105     LOG(VB_PLAYBACK, LOG_INFO, LOC +
05106         QString("CreatePBP() -- end : %1").arg(ok));
05107     return ok;
05108 }
05109 
05114 bool TV::CreatePIP(PlayerContext *ctx, const ProgramInfo *info)
05115 {
05116     PlayerContext *mctx = GetPlayer(ctx, 0);
05117     if (!mctx)
05118         return false;
05119 
05120     LOG(VB_PLAYBACK, LOG_INFO, LOC + "CreatePIP -- begin");
05121 
05122     if (mctx->IsPBP())
05123     {
05124         LOG(VB_GENERAL, LOG_ERR, LOC +
05125             "CreatePIP called, but we're in PBP mode already, ignoring.");
05126         return false;
05127     }
05128 
05129     if (!IsPIPSupported(mctx))
05130     {
05131         LOG(VB_GENERAL, LOG_ERR, LOC + "PiP not supported by video method.");
05132         return false;
05133     }
05134 
05135     PlayerContext *pipctx = new PlayerContext(kPIPPlayerInUseID);
05136     if (noHardwareDecoders)
05137         pipctx->SetNoHardwareDecoders();
05138     pipctx->SetNullVideo(true);
05139     pipctx->SetPIPState(kPIPonTV);
05140     if (info)
05141     {
05142         pipctx->SetPlayingInfo(info);
05143         pipctx->SetInitialTVState(false);
05144         ScheduleStateChange(pipctx);
05145     }
05146     else if (RequestNextRecorder(pipctx, false))
05147     {
05148         pipctx->SetInitialTVState(true);
05149         ScheduleStateChange(pipctx);
05150     }
05151     else
05152     {
05153         delete pipctx;
05154         return false;
05155     }
05156 
05157     // this is safe because we are already holding lock for ctx
05158     player.push_back(pipctx);
05159 
05160     return true;
05161 }
05162 
05163 int TV::find_player_index(const PlayerContext *ctx) const
05164 {
05165     for (uint i = 0; i < player.size(); i++)
05166         if (GetPlayer(ctx, i) == ctx)
05167             return i;
05168     return -1;
05169 }
05170 
05171 bool TV::StartPlayer(PlayerContext *mctx, PlayerContext *ctx,
05172                      TVState desiredState)
05173 {
05174     bool wantPiP = ctx->IsPIP();
05175 
05176     LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("StartPlayer(%1, %2, %3) -- begin")
05177             .arg(find_player_index(ctx)).arg(StateToString(desiredState))
05178             .arg((wantPiP) ? "PiP" : "main"));
05179 
05180     LOG(VB_PLAYBACK, LOG_INFO, LOC +
05181             QString("Elapsed time since TV constructor was called: %1 ms")
05182             .arg(ctorTime.elapsed()));
05183 
05184     if (wantPiP)
05185     {
05186         if (mctx->HasPlayer() && ctx->StartPIPPlayer(this, desiredState) &&
05187             ctx->HasPlayer() && PIPAddPlayer(mctx, ctx))
05188         {
05189             ScheduleStateChange(ctx);
05190             LOG(VB_GENERAL, LOG_INFO, "StartPlayer PiP -- end : ok");
05191             return true;
05192         }
05193 
05194         ForceNextStateNone(ctx);
05195         LOG(VB_GENERAL, LOG_INFO, "StartPlayer PiP -- end : !ok");
05196         return false;
05197     }
05198 
05199     bool ok = false;
05200     if (ctx->IsNullVideoDesired())
05201     {
05202         ok = ctx->CreatePlayer(this, NULL, desiredState, false);
05203         ScheduleStateChange(ctx);
05204         if (ok)
05205             ok = PIPAddPlayer(mctx, ctx);
05206     }
05207     else
05208     {
05209         ok = ctx->CreatePlayer(this, GetMythMainWindow(), desiredState, false);
05210         ScheduleStateChange(ctx);
05211     }
05212 
05213     if (ok)
05214     {
05215         LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created player."));
05216         SetSpeedChangeTimer(25, __LINE__);
05217     }
05218 
05219     LOG(VB_PLAYBACK, LOG_INFO, LOC +
05220         QString("StartPlayer(%1, %2, %3) -- end %4")
05221             .arg(find_player_index(ctx)).arg(StateToString(desiredState))
05222             .arg((wantPiP) ? "PiP" : "main").arg((ok) ? "ok" : "error"));
05223 
05224     return ok;
05225 }
05226 
05228 bool TV::PIPAddPlayer(PlayerContext *mctx, PlayerContext *pipctx)
05229 {
05230     if (!mctx || !pipctx)
05231         return false;
05232 
05233     if (!mctx->IsPlayerPlaying())
05234         return false;
05235 
05236     bool ok = false, addCondition = false;
05237     pipctx->LockDeletePlayer(__FILE__, __LINE__);
05238     if (pipctx->player)
05239     {
05240         bool is_using_null = pipctx->player->UsingNullVideo();
05241         pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
05242 
05243         if (is_using_null)
05244         {
05245             addCondition = true;
05246             multi_lock(&mctx->deletePlayerLock, &pipctx->deletePlayerLock, NULL);
05247             if (mctx->player && pipctx->player)
05248             {
05249                 PIPLocation loc = mctx->player->GetNextPIPLocation();
05250                 if (loc != kPIP_END)
05251                     ok = mctx->player->AddPIPPlayer(pipctx->player, loc, 4000);
05252             }
05253             mctx->deletePlayerLock.unlock();
05254             pipctx->deletePlayerLock.unlock();
05255         }
05256         else if (pipctx->IsPIP())
05257         {
05258             ok = ResizePIPWindow(pipctx);
05259         }
05260     }
05261     else
05262         pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
05263 
05264     LOG(VB_GENERAL, LOG_ERR,
05265         QString("AddPIPPlayer null: %1 IsPIP: %2 addCond: %3 ok: %4")
05266             .arg(pipctx->player->UsingNullVideo())
05267             .arg(pipctx->IsPIP()).arg(addCondition).arg(ok));
05268 
05269     return ok;
05270 }
05271 
05273 bool TV::PIPRemovePlayer(PlayerContext *mctx, PlayerContext *pipctx)
05274 {
05275     if (!mctx || !pipctx)
05276         return false;
05277 
05278     bool ok = false;
05279     multi_lock(&mctx->deletePlayerLock, &pipctx->deletePlayerLock, NULL);
05280     if (mctx->player && pipctx->player)
05281         ok = mctx->player->RemovePIPPlayer(pipctx->player, 4000);
05282     mctx->deletePlayerLock.unlock();
05283     pipctx->deletePlayerLock.unlock();
05284 
05285     LOG(VB_GENERAL, LOG_INFO, QString("PIPRemovePlayer ok: %1").arg(ok));
05286 
05287     return ok;
05288 }
05289 
05291 void TV::PxPToggleView(PlayerContext *actx, bool wantPBP)
05292 {
05293     if (wantPBP && !IsPBPSupported(actx))
05294     {
05295         LOG(VB_GENERAL, LOG_WARNING, LOC +
05296             "PxPToggleView() -- end: PBP not supported by video method.");
05297         return;
05298     }
05299 
05300     if (player.size() <= 1)
05301         PxPCreateView(actx, wantPBP);
05302     else
05303         PxPTeardownView(actx);
05304 }
05305 
05307 void TV::PxPCreateView(PlayerContext *actx, bool wantPBP)
05308 {
05309     if (!actx)
05310         return;
05311 
05312     QString err_msg = QString::null;
05313     if ((player.size() > kMaxPBPCount) && (wantPBP || actx->IsPBP()))
05314     {
05315         err_msg = tr("Sorry, PBP only supports %n video stream(s)", "",
05316                      kMaxPBPCount);
05317     }
05318 
05319     if ((player.size() > kMaxPIPCount) &&
05320         (!wantPBP || GetPlayer(actx,1)->IsPIP()))
05321     {
05322         err_msg = tr("Sorry, PIP only supports %n video stream(s)", "",
05323                      kMaxPIPCount);
05324     }
05325 
05326     if ((player.size() > 1) && (wantPBP ^ actx->IsPBP()))
05327         err_msg = tr("Sorry, cannot mix PBP and PIP views");
05328 
05329     if (!err_msg.isEmpty())
05330     {
05331         LOG(VB_GENERAL, LOG_ERR, LOC + err_msg);
05332         SetOSDMessage(actx, err_msg);
05333         return;
05334     }
05335 
05336     bool ok = false;
05337     if (wantPBP)
05338         ok = CreatePBP(actx, NULL);
05339     else
05340         ok = CreatePIP(actx, NULL);
05341     actx = GetPlayer(actx, -1); // CreatePBP/PIP mess with ctx's
05342 
05343     QString msg = (ok) ?
05344         ((wantPBP) ? tr("Creating PBP")      : tr("Creating PIP")) :
05345         ((wantPBP) ? tr("Cannot create PBP") : tr("Cannot create PIP"));
05346 
05347     SetOSDMessage(actx, msg);
05348 }
05349 
05351 void TV::PxPTeardownView(PlayerContext *actx)
05352 {
05353     LOG(VB_GENERAL, LOG_INFO, "PxPTeardownView()");
05354 
05355     QString msg;
05356     PlayerContext *mctx = GetPlayer(actx, 0);
05357     PlayerContext *dctx = NULL;
05358     dctx = (mctx != actx)       ? actx               : dctx;
05359     dctx = (2 == player.size()) ? GetPlayer(actx, 1) : dctx;
05360 
05361     SetActive(actx, 0, false);
05362 
05363     PlayerContext *ctx1 = GetPlayer(actx, 1);
05364     msg = (ctx1->IsPIP()) ? tr("Stopping PIP") : tr("Stopping PBP");
05365     if (dctx)
05366     {
05367         ForceNextStateNone(dctx);
05368     }
05369     else
05370     {
05371         if (player.size() > 2)
05372         {
05373             msg = (ctx1->IsPIP()) ?
05374                 tr("Stopping all PIPs") : tr("Stopping all PBPs");
05375         }
05376 
05377         for (uint i = player.size() - 1; i > 0; i--)
05378             ForceNextStateNone(GetPlayer(actx,i));
05379     }
05380 
05381     SetOSDMessage(mctx, msg);
05382 }
05383 
05387 void TV::PxPToggleType(PlayerContext *mctx, bool wantPBP)
05388 {
05389     const QString before = (mctx->IsPBP()) ? "PBP" : "PIP";
05390     const QString after  = (wantPBP)       ? "PBP" : "PIP";
05391 
05392     // TODO renderer may change depending on display profile
05393     //      check for support in new renderer
05394     if (wantPBP && !IsPBPSupported(mctx))
05395     {
05396         LOG(VB_GENERAL, LOG_WARNING, LOC +
05397             "PxPToggleType() -- end: PBP not supported by video method.");
05398         return;
05399     }
05400 
05401 
05402     LOG(VB_PLAYBACK, LOG_INFO, LOC +
05403         QString("PxPToggleType() converting from %1 to %2 -- begin")
05404             .arg(before).arg(after));
05405 
05406     if (mctx->IsPBP() == wantPBP)
05407     {
05408         LOG(VB_GENERAL, LOG_WARNING, LOC +
05409             "PxPToggleType() -- end: already in desired mode");
05410         return;
05411     }
05412 
05413     uint max_cnt = min(kMaxPBPCount, kMaxPIPCount+1);
05414     if (player.size() > max_cnt)
05415     {
05416         LOG(VB_GENERAL, LOG_ERR, LOC +
05417             QString("PxPToggleType() -- end: # player contexts must be %1 or "
05418                     "less, but it is currently %1")
05419                 .arg(max_cnt).arg(player.size()));
05420 
05421         QString err_msg = tr("Too many views to switch");
05422 
05423         PlayerContext *actx = GetPlayer(mctx, -1);
05424         SetOSDMessage(actx, err_msg);
05425         return;
05426     }
05427 
05428     for (uint i = 0; i < player.size(); i++)
05429     {
05430         PlayerContext *ctx = GetPlayer(mctx, i);
05431         if (!ctx->IsPlayerPlaying())
05432         {
05433             LOG(VB_GENERAL, LOG_ERR, LOC + "PxPToggleType() -- end: " +
05434                     QString("player #%1 is not active, exiting without "
05435                             "doing anything to avoid danger").arg(i));
05436             return;
05437         }
05438     }
05439 
05440     MuteState mctx_mute = kMuteOff;
05441     mctx->LockDeletePlayer(__FILE__, __LINE__);
05442     if (mctx->player)
05443         mctx_mute = mctx->player->GetMuteState();
05444     mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05445 
05446     vector<long long> pos = TeardownAllPlayers(mctx);
05447 
05448     if (wantPBP)
05449     {
05450         GetPlayer(mctx, 0)->SetPIPState(kPBPLeft);
05451         GetPlayer(mctx, 1)->SetPIPState(kPBPRight);
05452     }
05453     else
05454     {
05455         GetPlayer(mctx, 0)->SetPIPState(kPIPOff);
05456         for (uint i = 1; i < player.size(); i++)
05457         {
05458             GetPlayer(mctx, i)->SetPIPState(kPIPonTV);
05459             GetPlayer(mctx, i)->SetNullVideo(true);
05460         }
05461     }
05462 
05463     RestartAllPlayers(mctx, pos, mctx_mute);
05464 
05465     LOG(VB_PLAYBACK, LOG_INFO, LOC +
05466         QString("PxPToggleType() converting from %1 to %2 -- end")
05467             .arg(before).arg(after));
05468 }
05469 
05473 bool TV::ResizePIPWindow(PlayerContext *ctx)
05474 {
05475     LOG(VB_PLAYBACK, LOG_INFO, LOC + "ResizePIPWindow -- begin");
05476     PlayerContext *mctx = GetPlayer(ctx, 0);
05477     if (mctx->HasPlayer() && ctx->HasPlayer())
05478     {
05479         QRect rect;
05480 
05481         multi_lock(&mctx->deletePlayerLock, &ctx->deletePlayerLock, (QMutex*)NULL);
05482         if (mctx->player && ctx->player)
05483         {
05484             PIPLocation loc = mctx->player->GetNextPIPLocation();
05485             LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("ResizePIPWindow -- loc %1")
05486                     .arg(loc));
05487             if (loc != kPIP_END)
05488             {
05489                 rect = mctx->player->GetVideoOutput()->GetPIPRect(
05490                     loc, ctx->player, false);
05491             }
05492         }
05493         mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05494         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05495 
05496         if (rect.isValid())
05497         {
05498             ctx->ResizePIPWindow(rect);
05499             LOG(VB_PLAYBACK, LOG_INFO, LOC + "ResizePIPWindow -- end : ok");
05500             return true;
05501         }
05502     }
05503     LOG(VB_PLAYBACK, LOG_ERR, LOC + "ResizePIPWindow -- end : !ok");
05504     return false;
05505 }
05506 
05507 bool TV::IsPBPSupported(const PlayerContext *ctx) const
05508 {
05509     const PlayerContext *mctx = NULL;
05510     if (ctx)
05511         mctx = GetPlayer(ctx, 0);
05512     else
05513         mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
05514 
05515     bool yes = mctx->IsPBPSupported();
05516 
05517     if (!ctx)
05518         ReturnPlayerLock(mctx);
05519 
05520     return yes;
05521 }
05522 
05523 bool TV::IsPIPSupported(const PlayerContext *ctx) const
05524 {
05525     const PlayerContext *mctx = NULL;
05526     if (ctx)
05527         mctx = GetPlayer(ctx, 0);
05528     else
05529         mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
05530 
05531     bool yes = mctx->IsPIPSupported();
05532 
05533     if (!ctx)
05534         ReturnPlayerLock(mctx);
05535 
05536     return yes;
05537 }
05538 
05542 vector<long long> TV::TeardownAllPlayers(PlayerContext *lctx)
05543 {
05544     vector<long long> pos;
05545     for (uint i = 0; i < player.size(); i++)
05546     {
05547         const PlayerContext *ctx = GetPlayer(lctx, i);
05548         ctx->LockDeletePlayer(__FILE__, __LINE__);
05549         pos.push_back((ctx->player) ? ctx->player->GetFramesPlayed() : 0);
05550         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05551     }
05552 
05553     for (uint i = 0; i < player.size(); i++)
05554     {
05555         PlayerContext *ctx = GetPlayer(lctx, i);
05556         ctx->PIPTeardown();
05557     }
05558 
05559     return pos;
05560 }
05561 
05566 void TV::PBPRestartMainPlayer(PlayerContext *mctx)
05567 {
05568     LOG(VB_PLAYBACK, LOG_INFO, LOC  + "PBPRestartMainPlayer -- begin");
05569 
05570     if (!mctx->IsPlayerPlaying() ||
05571         mctx->GetPIPState() != kPBPLeft || exitPlayerTimerId)
05572     {
05573         LOG(VB_PLAYBACK, LOG_ERR, LOC +
05574             "PBPRestartMainPlayer -- end !ok !valid");
05575         return;
05576     }
05577 
05578     mctx->LockDeletePlayer(__FILE__, __LINE__);
05579     long long mctx_frame = (mctx->player) ? mctx->player->GetFramesPlayed() : 0;
05580     mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05581 
05582     mctx->PIPTeardown();
05583     mctx->SetPIPState(kPIPOff);
05584     mctx->buffer->Seek(0, SEEK_SET);
05585 
05586     if (mctx->CreatePlayer(this, GetMythMainWindow(), mctx->GetState(), false))
05587     {
05588         ScheduleStateChange(mctx);
05589         mctx->LockDeletePlayer(__FILE__, __LINE__);
05590         if (mctx->player)
05591             mctx->player->JumpToFrame(mctx_frame);
05592         mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05593         SetSpeedChangeTimer(25, __LINE__);
05594         LOG(VB_PLAYBACK, LOG_INFO, LOC + "PBPRestartMainPlayer -- end ok");
05595         return;
05596     }
05597 
05598     ForceNextStateNone(mctx);
05599     LOG(VB_PLAYBACK, LOG_ERR, LOC +
05600             "PBPRestartMainPlayer -- end !ok Player did not restart");
05601 }
05602 
05606 void TV::RestartAllPlayers(PlayerContext *lctx,
05607                         const vector<long long> &pos,
05608                         MuteState mctx_mute)
05609 {
05610     QString loc = LOC + QString("RestartAllPlayers(): ");
05611 
05612     PlayerContext *mctx = GetPlayer(lctx, 0);
05613 
05614     if (!mctx)
05615         return;
05616 
05617     mctx->buffer->Seek(0, SEEK_SET);
05618 
05619     if (StateIsLiveTV(mctx->GetState()))
05620         mctx->buffer->Unpause();
05621 
05622     bool ok = StartPlayer(mctx, mctx, mctx->GetState());
05623 
05624     if (ok)
05625     {
05626         mctx->LockDeletePlayer(__FILE__, __LINE__);
05627         if (mctx->player)
05628             mctx->player->JumpToFrame(pos[0]);
05629         mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05630     }
05631     else
05632     {
05633         LOG(VB_GENERAL, LOG_ERR, loc +
05634                 "Failed to restart new main context (was pip context)");
05635         ForceNextStateNone(mctx);
05636         return;
05637     }
05638 
05639     for (uint i = 1; i < player.size(); i++)
05640     {
05641         PlayerContext *pipctx = GetPlayer(lctx, i);
05642 
05643         pipctx->buffer->Seek(0, SEEK_SET);
05644 
05645         if (StateIsLiveTV(pipctx->GetState()))
05646             pipctx->buffer->Unpause();
05647 
05648         ok = StartPlayer(mctx, pipctx, pipctx->GetState());
05649 
05650         if (ok)
05651         {
05652             pipctx->LockDeletePlayer(__FILE__, __LINE__);
05653             if (pipctx->player)
05654             {
05655                 pipctx->player->SetMuted(true);
05656                 pipctx->player->JumpToFrame(pos[i]);
05657             }
05658             pipctx->UnlockDeletePlayer(__FILE__, __LINE__);
05659         }
05660         else
05661         { // TODO print OSD informing user of Swap failure ?
05662             LOG(VB_GENERAL, LOG_ERR, loc +
05663                 "Failed to restart new pip context (was main context)");
05664             ForceNextStateNone(pipctx);
05665         }
05666     }
05667 
05668     // If old main player had a kMuteAll | kMuteOff setting,
05669     // apply old main player's mute setting to new main player.
05670     mctx->LockDeletePlayer(__FILE__, __LINE__);
05671     if (mctx->player && ((kMuteAll == mctx_mute) || (kMuteOff == mctx_mute)))
05672         mctx->player->SetMuteState(mctx_mute);
05673     mctx->UnlockDeletePlayer(__FILE__, __LINE__);
05674 }
05675 
05676 void TV::PxPSwap(PlayerContext *mctx, PlayerContext *pipctx)
05677 {
05678     if (!mctx || !pipctx)
05679         return;
05680 
05681     LOG(VB_PLAYBACK, LOG_INFO, LOC + "PxPSwap -- begin");
05682     if (mctx == pipctx)
05683     {
05684         LOG(VB_GENERAL, LOG_ERR, LOC + "PxPSwap -- need two contexts");
05685         return;
05686     }
05687 
05688     lockTimerOn = false;
05689 
05690     multi_lock(&mctx->deletePlayerLock, &pipctx->deletePlayerLock, NULL);
05691     if (!mctx->player   || !mctx->player->IsPlaying() ||
05692         !pipctx->player || !pipctx->player->IsPlaying())
05693     {
05694         mctx->deletePlayerLock.unlock();
05695         pipctx->deletePlayerLock.unlock();
05696         LOG(VB_GENERAL, LOG_ERR, LOC + "PxPSwap -- a player is not playing");
05697         return;
05698     }
05699 
05700     MuteState mctx_mute = mctx->player->GetMuteState();
05701     mctx->deletePlayerLock.unlock();
05702     pipctx->deletePlayerLock.unlock();
05703 
05704     int ctx_index = find_player_index(pipctx);
05705 
05706     vector<long long> pos = TeardownAllPlayers(mctx);
05707 
05708     swap(player[0],           player[ctx_index]);
05709     swap(pos[0],              pos[ctx_index]);
05710     swap(player[0]->pipState, player[ctx_index]->pipState);
05711     playerActive = (ctx_index == playerActive) ?
05712         0 : ((ctx_index == 0) ? ctx_index : playerActive);
05713 
05714     RestartAllPlayers(mctx, pos, mctx_mute);
05715 
05716     SetActive(mctx, playerActive, false);
05717 
05718     LOG(VB_PLAYBACK, LOG_INFO, LOC + "PxPSwap -- end");
05719 }
05720 
05721 void TV::RestartMainPlayer(PlayerContext *mctx)
05722 {
05723     if (!mctx)
05724         return;
05725 
05726     LOG(VB_PLAYBACK, LOG_INFO, LOC + "Restart main player -- begin");
05727     lockTimerOn = false;
05728 
05729     mctx->LockDeletePlayer(__FILE__, __LINE__);
05730     if (!mctx->player)
05731     {
05732         mctx->deletePlayerLock.unlock();
05733         return;
05734     }
05735 
05736     MuteState mctx_mute = mctx->player->GetMuteState();
05737 
05738     // HACK - FIXME
05739     // workaround muted audio when Player is re-created
05740     mctx_mute = kMuteOff;
05741     // FIXME - end
05742     mctx->deletePlayerLock.unlock();
05743 
05744     vector<long long> pos = TeardownAllPlayers(mctx);
05745     RestartAllPlayers(mctx, pos, mctx_mute);
05746     SetActive(mctx, playerActive, false);
05747 
05748     LOG(VB_PLAYBACK, LOG_INFO, LOC + "Restart main player -- end");
05749 }
05750 
05751 void TV::DoPlay(PlayerContext *ctx)
05752 {
05753     ctx->LockDeletePlayer(__FILE__, __LINE__);
05754     if (!ctx->player)
05755     {
05756         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05757         return;
05758     }
05759 
05760     float time = 0.0;
05761 
05762     if (ctx->ff_rew_state || (ctx->ff_rew_speed != 0) ||
05763         ctx->player->IsPaused())
05764     {
05765         if (ctx->ff_rew_state)
05766             time = StopFFRew(ctx);
05767         else if (ctx->player->IsPaused())
05768             SendMythSystemPlayEvent("PLAY_UNPAUSED", ctx->playingInfo);
05769 
05770         ctx->player->Play(ctx->ts_normal, true);
05771         ctx->ff_rew_speed = 0;
05772     }
05773     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05774 
05775     DoPlayerSeek(ctx, time);
05776     UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Med);
05777 
05778     GetMythUI()->DisableScreensaver();
05779 
05780     SetSpeedChangeTimer(0, __LINE__);
05781 }
05782 
05783 float TV::DoTogglePauseStart(PlayerContext *ctx)
05784 {
05785     if (!ctx)
05786         return 0.0f;
05787 
05788     if (ctx->buffer && ctx->buffer->IsInDiscMenuOrStillFrame())
05789         return 0.0f;
05790 
05791     ctx->ff_rew_speed = 0;
05792     float time = 0.0f;
05793 
05794     ctx->LockDeletePlayer(__FILE__, __LINE__);
05795     if (!ctx->player)
05796     {
05797         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05798         return 0.0f;
05799     }
05800     if (ctx->player->IsPaused())
05801     {
05802         ctx->player->Play(ctx->ts_normal, true);
05803     }
05804     else
05805     {
05806         if (ctx->ff_rew_state)
05807             time = StopFFRew(ctx);
05808         ctx->player->Pause();
05809     }
05810     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05811     return time;
05812 }
05813 
05814 void TV::DoTogglePauseFinish(PlayerContext *ctx, float time, bool showOSD)
05815 {
05816     if (!ctx || !ctx->HasPlayer())
05817         return;
05818 
05819     if (ctx->buffer && ctx->buffer->IsInDiscMenuOrStillFrame())
05820         return;
05821 
05822     if (ContextIsPaused(ctx, __FILE__, __LINE__))
05823     {
05824         if (ctx->buffer)
05825             ctx->buffer->WaitForPause();
05826 
05827         DoPlayerSeek(ctx, time);
05828 
05829         if (showOSD && ctx == player[0])
05830             UpdateOSDSeekMessage(ctx, tr("Paused"), kOSDTimeout_None);
05831         else if (showOSD)
05832             UpdateOSDSeekMessage(ctx, tr("Aux Paused"), kOSDTimeout_None);
05833 
05834         RestoreScreenSaver(ctx);
05835     }
05836     else
05837     {
05838         DoPlayerSeek(ctx, time);
05839         if (showOSD)
05840             UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Med);
05841         GetMythUI()->DisableScreensaver();
05842     }
05843 
05844     SetSpeedChangeTimer(0, __LINE__);
05845 }
05846 
05847 void TV::DoTogglePause(PlayerContext *ctx, bool showOSD)
05848 {
05849     bool ignore = false;
05850     bool paused = false;
05851     ctx->LockDeletePlayer(__FILE__, __LINE__);
05852     if (ctx->player)
05853     {
05854         ignore = ctx->player->GetEditMode();
05855         paused = ctx->player->IsPaused();
05856     }
05857     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05858 
05859     if (paused)
05860         SendMythSystemPlayEvent("PLAY_UNPAUSED", ctx->playingInfo);
05861     else
05862         SendMythSystemPlayEvent("PLAY_PAUSED", ctx->playingInfo);
05863 
05864     if (!ignore)
05865         DoTogglePauseFinish(ctx, DoTogglePauseStart(ctx), showOSD);
05866 }
05867 
05868 bool TV::DoPlayerSeek(PlayerContext *ctx, float time)
05869 {
05870     if (time > -0.001f && time < +0.001f)
05871         return false;
05872 
05873     LOG(VB_PLAYBACK, LOG_INFO, LOC +
05874         QString("DoPlayerSeek (%1 seconds)").arg(time));
05875 
05876     ctx->LockDeletePlayer(__FILE__, __LINE__);
05877     if (!ctx->player)
05878     {
05879         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05880         return false;
05881     }
05882 
05883     if (!ctx->buffer->IsSeekingAllowed())
05884     {
05885         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05886         return false;
05887     }
05888 
05889     if (ctx == GetPlayer(ctx, 0))
05890         PauseAudioUntilBuffered(ctx);
05891 
05892     bool res = false;
05893 
05894     if (time > 0.0f)
05895         res = ctx->player->FastForward(time);
05896     else if (time < 0.0)
05897         res = ctx->player->Rewind(-time);
05898     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05899 
05900     return res;
05901 }
05902 
05903 bool TV::SeekHandleAction(PlayerContext *actx, const QStringList &actions,
05904                           const bool isDVD)
05905 {
05906     const int kRewind = 4, kForward = 8, kSticky = 16, kSlippery = 32,
05907               kRelative = 64, kAbsolute = 128, kIgnoreCutlist = 256,
05908               kWhenceMask = 3;
05909     int flags = 0;
05910     if (has_action(ACTION_SEEKFFWD, actions))
05911         flags = ARBSEEK_FORWARD | kForward | kSlippery | kRelative;
05912     else if (has_action("FFWDSTICKY", actions))
05913         flags = ARBSEEK_END     | kForward | kSticky   | kAbsolute;
05914     else if (has_action(ACTION_RIGHT, actions))
05915         flags = ARBSEEK_FORWARD | kForward | kSticky   | kRelative;
05916     else if (has_action(ACTION_SEEKRWND, actions))
05917         flags = ARBSEEK_REWIND  | kRewind  | kSlippery | kRelative;
05918     else if (has_action("RWNDSTICKY", actions))
05919         flags = ARBSEEK_SET     | kRewind  | kSticky   | kAbsolute;
05920     else if (has_action(ACTION_LEFT, actions))
05921         flags = ARBSEEK_REWIND  | kRewind  | kSticky   | kRelative;
05922     else
05923         return false;
05924 
05925     int direction = (flags & kRewind) ? -1 : 1;
05926     if (HasQueuedInput())
05927     {
05928         DoArbSeek(actx, static_cast<ArbSeekWhence>(flags & kWhenceMask),
05929                   !(flags & kIgnoreCutlist));
05930     }
05931     else if (ContextIsPaused(actx, __FILE__, __LINE__))
05932     {
05933         if (!isDVD)
05934         {
05935             float rate = 30.0f;
05936             actx->LockDeletePlayer(__FILE__, __LINE__);
05937             if (actx->player)
05938                 rate = actx->player->GetFrameRate();
05939             actx->UnlockDeletePlayer(__FILE__, __LINE__);
05940             float time = (flags & kAbsolute) ?  direction :
05941                              direction * (1.001 / rate);
05942             QString message = (flags & kRewind) ? QString(tr("Rewind")) :
05943                                                  QString(tr("Forward"));
05944             DoSeek(actx, time, message,
05945                    /*timeIsOffset*/true,
05946                    /*honorCutlist*/!(flags & kIgnoreCutlist));
05947         }
05948     }
05949     else if (flags & kSticky)
05950     {
05951         ChangeFFRew(actx, direction);
05952     }
05953     else if (flags & kRewind)
05954     {
05955             if (smartForward)
05956                 doSmartForward = true;
05957             DoSeek(actx, -actx->rewtime, tr("Skip Back"),
05958                    /*timeIsOffset*/true,
05959                    /*honorCutlist*/!(flags & kIgnoreCutlist));
05960     }
05961     else
05962     {
05963         if (smartForward & doSmartForward)
05964             DoSeek(actx, actx->rewtime, tr("Skip Ahead"),
05965                    /*timeIsOffset*/true,
05966                    /*honorCutlist*/!(flags & kIgnoreCutlist));
05967         else
05968             DoSeek(actx, actx->fftime, tr("Skip Ahead"),
05969                    /*timeIsOffset*/true,
05970                    /*honorCutlist*/!(flags & kIgnoreCutlist));
05971     }
05972     return true;
05973 }
05974 
05975 void TV::DoSeek(PlayerContext *ctx, float time, const QString &mesg,
05976                 bool timeIsOffset, bool honorCutlist)
05977 {
05978     bool limitkeys = false;
05979 
05980     ctx->LockDeletePlayer(__FILE__, __LINE__);
05981     if (ctx->player && ctx->player->GetLimitKeyRepeat())
05982         limitkeys = true;
05983     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
05984 
05985     if (!limitkeys || (keyRepeatTimer.elapsed() > (int)kKeyRepeatTimeout))
05986     {
05987         keyRepeatTimer.start();
05988         NormalSpeed(ctx);
05989         time += StopFFRew(ctx);
05990         float framerate = ctx->player->GetFrameRate();
05991         uint64_t currentFrameAbs = ctx->player->GetFramesPlayed();
05992         uint64_t currentFrameRel = honorCutlist ?
05993             ctx->player->TranslatePositionAbsToRel(currentFrameAbs) :
05994             currentFrameAbs;
05995         int64_t desiredFrameRel = (timeIsOffset ? currentFrameRel : 0) +
05996             time * framerate + 0.5;
05997         if (desiredFrameRel < 0)
05998             desiredFrameRel = 0;
05999         uint64_t desiredFrameAbs = honorCutlist ?
06000             ctx->player->TranslatePositionRelToAbs(desiredFrameRel) :
06001             desiredFrameRel;
06002         time = ((int64_t)desiredFrameAbs - (int64_t)currentFrameAbs) /
06003             framerate;
06004         DoPlayerSeek(ctx, time);
06005         UpdateOSDSeekMessage(ctx, mesg, kOSDTimeout_Med);
06006     }
06007 }
06008 
06009 void TV::DoSeekAbsolute(PlayerContext *ctx, long long seconds,
06010                         bool honorCutlist)
06011 {
06012     ctx->LockDeletePlayer(__FILE__, __LINE__);
06013     if (!ctx->player)
06014     {
06015         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06016         return;
06017     }
06018     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06019     DoSeek(ctx, seconds, tr("Jump To"),
06020            /*timeIsOffset*/false,
06021            honorCutlist);
06022 }
06023 
06024 void TV::DoArbSeek(PlayerContext *ctx, ArbSeekWhence whence,
06025                    bool honorCutlist)
06026 {
06027     bool ok = false;
06028     int seek = GetQueuedInputAsInt(&ok);
06029     ClearInputQueues(ctx, true);
06030     if (!ok)
06031         return;
06032 
06033     float time = ((seek / 100) * 3600) + ((seek % 100) * 60);
06034 
06035     if (whence == ARBSEEK_FORWARD)
06036         DoSeek(ctx, time, tr("Jump Ahead"),
06037                /*timeIsOffset*/true, honorCutlist);
06038     else if (whence == ARBSEEK_REWIND)
06039         DoSeek(ctx, -time, tr("Jump Back"),
06040                /*timeIsOffset*/true, honorCutlist);
06041     else if (whence == ARBSEEK_END)
06042     {
06043         ctx->LockDeletePlayer(__FILE__, __LINE__);
06044         if (!ctx->player)
06045         {
06046             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06047             return;
06048         }
06049         time = (ctx->player->CalcMaxFFTime(LONG_MAX, false) /
06050                 ctx->player->GetFrameRate()) - time;
06051         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06052         DoSeek(ctx, time, tr("Jump To"),
06053                /*timeIsOffset*/(whence != ARBSEEK_SET), honorCutlist);
06054     }
06055     else
06056         DoSeekAbsolute(ctx, time, honorCutlist);
06057 }
06058 
06059 void TV::NormalSpeed(PlayerContext *ctx)
06060 {
06061     if (!ctx->ff_rew_speed)
06062         return;
06063 
06064     ctx->ff_rew_speed = 0;
06065 
06066     ctx->LockDeletePlayer(__FILE__, __LINE__);
06067     if (ctx->player)
06068         ctx->player->Play(ctx->ts_normal, true);
06069     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06070 
06071     SetSpeedChangeTimer(0, __LINE__);
06072 }
06073 
06074 void TV::ChangeSpeed(PlayerContext *ctx, int direction)
06075 {
06076     int old_speed = ctx->ff_rew_speed;
06077 
06078     if (ContextIsPaused(ctx, __FILE__, __LINE__))
06079         ctx->ff_rew_speed = -4;
06080 
06081     ctx->ff_rew_speed += direction;
06082 
06083     float time = StopFFRew(ctx);
06084     float speed;
06085     QString mesg;
06086 
06087     switch (ctx->ff_rew_speed)
06088     {
06089         case  4: speed = 16.0;     mesg = QString(tr("Speed 16X"));   break;
06090         case  3: speed = 8.0;      mesg = QString(tr("Speed 8X"));    break;
06091         case  2: speed = 3.0;      mesg = QString(tr("Speed 3X"));    break;
06092         case  1: speed = 2.0;      mesg = QString(tr("Speed 2X"));    break;
06093         case  0: speed = 1.0;      mesg = ctx->GetPlayMessage();      break;
06094         case -1: speed = 1.0 / 3;  mesg = QString(tr("Speed 1/3X"));  break;
06095         case -2: speed = 1.0 / 8;  mesg = QString(tr("Speed 1/8X"));  break;
06096         case -3: speed = 1.0 / 16; mesg = QString(tr("Speed 1/16X")); break;
06097         case -4:
06098             DoTogglePause(ctx, true);
06099             return;
06100         default:
06101             ctx->ff_rew_speed = old_speed;
06102             return;
06103     }
06104 
06105     ctx->LockDeletePlayer(__FILE__, __LINE__);
06106     if (ctx->player && !ctx->player->Play(
06107             (!ctx->ff_rew_speed) ? ctx->ts_normal: speed, !ctx->ff_rew_speed))
06108     {
06109         ctx->ff_rew_speed = old_speed;
06110         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06111         return;
06112     }
06113     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06114     DoPlayerSeek(ctx, time);
06115     UpdateOSDSeekMessage(ctx, mesg, kOSDTimeout_Med);
06116 
06117     SetSpeedChangeTimer(0, __LINE__);
06118 }
06119 
06120 float TV::StopFFRew(PlayerContext *ctx)
06121 {
06122     float time = 0.0;
06123 
06124     if (!ctx->ff_rew_state)
06125         return time;
06126 
06127     if (ctx->ff_rew_state > 0)
06128         time = -ff_rew_speeds[ctx->ff_rew_index] * ff_rew_repos;
06129     else
06130         time = ff_rew_speeds[ctx->ff_rew_index] * ff_rew_repos;
06131 
06132     ctx->ff_rew_state = 0;
06133     ctx->ff_rew_index = kInitFFRWSpeed;
06134 
06135     ctx->LockDeletePlayer(__FILE__, __LINE__);
06136     if (ctx->player)
06137         ctx->player->Play(ctx->ts_normal, true);
06138     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06139 
06140     SetSpeedChangeTimer(0, __LINE__);
06141 
06142     return time;
06143 }
06144 
06145 void TV::ChangeFFRew(PlayerContext *ctx, int direction)
06146 {
06147     if (ctx->ff_rew_state == direction)
06148     {
06149         while (++ctx->ff_rew_index < (int)ff_rew_speeds.size())
06150             if (ff_rew_speeds[ctx->ff_rew_index])
06151                 break;
06152         if (ctx->ff_rew_index >= (int)ff_rew_speeds.size())
06153             ctx->ff_rew_index = kInitFFRWSpeed;
06154         SetFFRew(ctx, ctx->ff_rew_index);
06155     }
06156     else if (!ff_rew_reverse && ctx->ff_rew_state == -direction)
06157     {
06158         while (--ctx->ff_rew_index >= kInitFFRWSpeed)
06159             if (ff_rew_speeds[ctx->ff_rew_index])
06160                 break;
06161         if (ctx->ff_rew_index >= kInitFFRWSpeed)
06162             SetFFRew(ctx, ctx->ff_rew_index);
06163         else
06164         {
06165             float time = StopFFRew(ctx);
06166             DoPlayerSeek(ctx, time);
06167             UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Med);
06168         }
06169     }
06170     else
06171     {
06172         NormalSpeed(ctx);
06173         ctx->ff_rew_state = direction;
06174         SetFFRew(ctx, kInitFFRWSpeed);
06175     }
06176 }
06177 
06178 void TV::SetFFRew(PlayerContext *ctx, int index)
06179 {
06180     if (!ctx->ff_rew_state)
06181     {
06182         return;
06183     }
06184 
06185     if (!ff_rew_speeds[index])
06186     {
06187         return;
06188     }
06189 
06190     int speed;
06191     QString mesg;
06192     if (ctx->ff_rew_state > 0)
06193     {
06194         speed = ff_rew_speeds[index];
06195         // Don't allow ffwd if seeking is needed but not available
06196         if (!ctx->buffer->IsSeekingAllowed() && speed > 3)
06197             return;
06198 
06199         ctx->ff_rew_index = index;
06200         mesg = tr("Forward %1X").arg(ff_rew_speeds[ctx->ff_rew_index]);
06201         ctx->ff_rew_speed = speed;
06202     }
06203     else
06204     {
06205         // Don't rewind if we cannot seek
06206         if (!ctx->buffer->IsSeekingAllowed())
06207             return;
06208 
06209         ctx->ff_rew_index = index;
06210         mesg = tr("Rewind %1X").arg(ff_rew_speeds[ctx->ff_rew_index]);
06211         speed = -ff_rew_speeds[ctx->ff_rew_index];
06212         ctx->ff_rew_speed = speed;
06213     }
06214 
06215     ctx->LockDeletePlayer(__FILE__, __LINE__);
06216     if (ctx->player)
06217         ctx->player->Play((float)speed, (speed == 1) && (ctx->ff_rew_state > 0));
06218     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06219 
06220     UpdateOSDSeekMessage(ctx, mesg, kOSDTimeout_None);
06221 
06222     SetSpeedChangeTimer(0, __LINE__);
06223 }
06224 
06225 void TV::DoQueueTranscode(PlayerContext *ctx, QString profile)
06226 {
06227     ctx->LockPlayingInfo(__FILE__, __LINE__);
06228 
06229     if (ctx->GetState() == kState_WatchingPreRecorded)
06230     {
06231         bool stop = false;
06232         if (queuedTranscode)
06233             stop = true;
06234         else if (JobQueue::IsJobQueuedOrRunning(
06235                      JOB_TRANSCODE,
06236                      ctx->playingInfo->GetChanID(),
06237                      ctx->playingInfo->GetRecordingStartTime()))
06238         {
06239             stop = true;
06240         }
06241 
06242         if (stop)
06243         {
06244             JobQueue::ChangeJobCmds(
06245                 JOB_TRANSCODE,
06246                 ctx->playingInfo->GetChanID(),
06247                 ctx->playingInfo->GetRecordingStartTime(), JOB_STOP);
06248             queuedTranscode = false;
06249             SetOSDMessage(ctx, tr("Stopping Transcode"));
06250         }
06251         else
06252         {
06253             const RecordingInfo recinfo(*ctx->playingInfo);
06254             recinfo.ApplyTranscoderProfileChange(profile);
06255             QString jobHost = "";
06256 
06257             if (db_run_jobs_on_remote)
06258                 jobHost = ctx->playingInfo->GetHostname();
06259 
06260             QString msg = tr("Try Again");
06261             if (JobQueue::QueueJob(JOB_TRANSCODE,
06262                        ctx->playingInfo->GetChanID(),
06263                        ctx->playingInfo->GetRecordingStartTime(),
06264                        jobHost, "", "", JOB_USE_CUTLIST))
06265             {
06266                 queuedTranscode = true;
06267                 msg = tr("Transcoding");
06268             }
06269             SetOSDMessage(ctx, msg);
06270         }
06271     }
06272     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
06273 }
06274 
06275 int TV::GetNumChapters(const PlayerContext *ctx) const
06276 {
06277     int num_chapters = 0;
06278     ctx->LockDeletePlayer(__FILE__, __LINE__);
06279     if (ctx->player)
06280         num_chapters = ctx->player->GetNumChapters();
06281     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06282     return num_chapters;
06283 }
06284 
06285 void TV::GetChapterTimes(const PlayerContext *ctx, QList<long long> &times) const
06286 {
06287     ctx->LockDeletePlayer(__FILE__, __LINE__);
06288     if (ctx->player)
06289         ctx->player->GetChapterTimes(times);
06290     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06291 }
06292 
06293 int TV::GetCurrentChapter(const PlayerContext *ctx) const
06294 {
06295     int chapter = 0;
06296     ctx->LockDeletePlayer(__FILE__, __LINE__);
06297     if (ctx->player)
06298         chapter = ctx->player->GetCurrentChapter();
06299     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06300     return chapter;
06301 }
06302 
06303 void TV::DoJumpChapter(PlayerContext *ctx, int chapter)
06304 {
06305     NormalSpeed(ctx);
06306     StopFFRew(ctx);
06307 
06308     PauseAudioUntilBuffered(ctx);
06309 
06310     UpdateOSDSeekMessage(ctx, tr("Jump Chapter"), kOSDTimeout_Med);
06311     SetUpdateOSDPosition(true);
06312 
06313     ctx->LockDeletePlayer(__FILE__, __LINE__);
06314     if (ctx->player)
06315         ctx->player->JumpChapter(chapter);
06316     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06317 }
06318 
06319 int TV::GetNumTitles(const PlayerContext *ctx) const
06320 {
06321     int num_titles = 0;
06322     ctx->LockDeletePlayer(__FILE__, __LINE__);
06323     if (ctx->player)
06324         num_titles = ctx->player->GetNumTitles();
06325     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06326     return num_titles;
06327 }
06328 
06329 int TV::GetCurrentTitle(const PlayerContext *ctx) const
06330 {
06331     int currentTitle = 0;
06332     ctx->LockDeletePlayer(__FILE__, __LINE__);
06333     if (ctx->player)
06334         currentTitle = ctx->player->GetCurrentTitle();
06335     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06336     return currentTitle;
06337 }
06338 
06339 int TV::GetNumAngles(const PlayerContext *ctx) const
06340 {
06341     int num_angles = 0;
06342     ctx->LockDeletePlayer(__FILE__, __LINE__);
06343     if (ctx->player)
06344         num_angles = ctx->player->GetNumAngles();
06345     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06346     return num_angles;
06347 }
06348 
06349 int TV::GetCurrentAngle(const PlayerContext *ctx) const
06350 {
06351     int currentAngle = 0;
06352     ctx->LockDeletePlayer(__FILE__, __LINE__);
06353     if (ctx->player)
06354         currentAngle = ctx->player->GetCurrentAngle();
06355     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06356     return currentAngle;
06357 }
06358 
06359 QString TV::GetAngleName(const PlayerContext *ctx, int angle) const
06360 {
06361     QString name;
06362     ctx->LockDeletePlayer(__FILE__, __LINE__);
06363     if (ctx->player)
06364         name = ctx->player->GetAngleName(angle);
06365     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06366     return name;
06367 }
06368 
06369 int TV::GetTitleDuration(const PlayerContext *ctx, int title) const
06370 {
06371     int seconds = 0;
06372     ctx->LockDeletePlayer(__FILE__, __LINE__);
06373     if (ctx->player)
06374         seconds = ctx->player->GetTitleDuration(title);
06375     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06376     return seconds;
06377 }
06378 
06379 
06380 QString TV::GetTitleName(const PlayerContext *ctx, int title) const
06381 {
06382     QString name;
06383     ctx->LockDeletePlayer(__FILE__, __LINE__);
06384     if (ctx->player)
06385         name = ctx->player->GetTitleName(title);
06386     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06387     return name;
06388 }
06389 
06390 void TV::DoSwitchTitle(PlayerContext *ctx, int title)
06391 {
06392     NormalSpeed(ctx);
06393     StopFFRew(ctx);
06394 
06395     PauseAudioUntilBuffered(ctx);
06396 
06397     UpdateOSDSeekMessage(ctx, tr("Switch Title"), kOSDTimeout_Med);
06398     SetUpdateOSDPosition(true);
06399 
06400     ctx->LockDeletePlayer(__FILE__, __LINE__);
06401     if (ctx->player)
06402         ctx->player->SwitchTitle(title);
06403     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06404 }
06405 
06406 void TV::DoSwitchAngle(PlayerContext *ctx, int angle)
06407 {
06408     NormalSpeed(ctx);
06409     StopFFRew(ctx);
06410 
06411     PauseAudioUntilBuffered(ctx);
06412 
06413     UpdateOSDSeekMessage(ctx, tr("Switch Angle"), kOSDTimeout_Med);
06414     SetUpdateOSDPosition(true);
06415 
06416     ctx->LockDeletePlayer(__FILE__, __LINE__);
06417     if (ctx->player)
06418         ctx->player->SwitchAngle(angle);
06419     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06420 }
06421 
06422 void TV::DoSkipCommercials(PlayerContext *ctx, int direction)
06423 {
06424     NormalSpeed(ctx);
06425     StopFFRew(ctx);
06426 
06427     if (StateIsLiveTV(GetState(ctx)))
06428         return;
06429 
06430     PauseAudioUntilBuffered(ctx);
06431 
06432     osdInfo info;
06433     ctx->CalcPlayerSliderPosition(info);
06434     info.text["title"] = tr("Skip");
06435     info.text["description"] = tr("Searching");
06436     UpdateOSDStatus(ctx, info, kOSDFunctionalType_Default, kOSDTimeout_Med);
06437     SetUpdateOSDPosition(true);
06438 
06439     ctx->LockDeletePlayer(__FILE__, __LINE__);
06440     if (ctx->player)
06441         ctx->player->SkipCommercials(direction);
06442     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06443 }
06444 
06445 void TV::SwitchSource(PlayerContext *ctx, uint source_direction)
06446 {
06447     QMap<uint,InputInfo> sources;
06448     uint         cardid  = ctx->GetCardID();
06449     vector<uint> excluded_cardids;
06450     excluded_cardids.push_back(cardid);
06451     vector<uint> cardids = RemoteRequestFreeRecorderList(excluded_cardids);
06452     stable_sort(cardids.begin(), cardids.end());
06453 
06454     InfoMap info;
06455     ctx->recorder->GetChannelInfo(info);
06456     uint sourceid = info["sourceid"].toUInt();
06457 
06458     vector<uint>::const_iterator it = cardids.begin();
06459     for (; it != cardids.end(); ++it)
06460     {
06461         vector<InputInfo> inputs = RemoteRequestFreeInputList(
06462             *it, excluded_cardids);
06463 
06464         if (inputs.empty())
06465             continue;
06466 
06467         for (uint i = 0; i < inputs.size(); i++)
06468         {
06469             // prefer the current card's input in sources list
06470             if ((sources.find(inputs[i].sourceid) == sources.end()) ||
06471                 ((cardid == inputs[i].cardid) &&
06472                  (cardid != sources[inputs[i].sourceid].cardid)))
06473             {
06474                 sources[inputs[i].sourceid] = inputs[i];
06475             }
06476         }
06477     }
06478 
06479     // Source switching
06480     QMap<uint,InputInfo>::const_iterator beg = sources.find(sourceid);
06481     QMap<uint,InputInfo>::const_iterator sit = beg;
06482 
06483     if (sit == sources.end())
06484     {
06485         return;
06486     }
06487 
06488     if (kNextSource == source_direction)
06489     {
06490         ++sit;
06491         if (sit == sources.end())
06492             sit = sources.begin();
06493     }
06494 
06495     if (kPreviousSource == source_direction)
06496     {
06497         if (sit != sources.begin())
06498             --sit;
06499         else
06500         {
06501             QMap<uint,InputInfo>::const_iterator tmp = sources.begin();
06502             while (tmp != sources.end())
06503             {
06504                 sit = tmp;
06505                 ++tmp;
06506             }
06507         }
06508     }
06509 
06510     if (sit == beg)
06511     {
06512         return;
06513     }
06514 
06515     switchToInputId = (*sit).inputid;
06516 
06517     QMutexLocker locker(&timerIdLock);
06518     if (!switchToInputTimerId)
06519         switchToInputTimerId = StartTimer(1, __LINE__);
06520 }
06521 
06522 void TV::SwitchInputs(PlayerContext *ctx, uint inputid)
06523 {
06524     if (!ctx->recorder)
06525     {
06526         return;
06527     }
06528 
06529     LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SwitchInputs(%1)").arg(inputid));
06530 
06531     if ((uint)ctx->GetCardID() == CardUtil::GetCardID(inputid))
06532     {
06533         ToggleInputs(ctx, inputid);
06534     }
06535     else
06536     {
06537         SwitchCards(ctx, 0, QString::null, inputid);
06538     }
06539 }
06540 
06541 void TV::SwitchCards(PlayerContext *ctx,
06542                      uint chanid, QString channum, uint inputid)
06543 {
06544     LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SwitchCards(%1,'%2',%3)")
06545             .arg(chanid).arg(channum).arg(inputid));
06546 
06547     RemoteEncoder *testrec = NULL;
06548 
06549     if (!StateIsLiveTV(GetState(ctx)))
06550     {
06551         return;
06552     }
06553 
06554     uint input_cardid = 0;
06555     QStringList reclist;
06556     if (inputid)
06557     {
06558         // If we are switching to a specific input..
06559         input_cardid = CardUtil::GetCardID(inputid);
06560         if (input_cardid)
06561             reclist.push_back(QString::number(input_cardid));
06562     }
06563     else if (chanid || !channum.isEmpty())
06564     {
06565         // If we are switching to a channel not on the current recorder
06566         // we need to find the next free recorder with that channel.
06567         reclist = ChannelUtil::GetValidRecorderList(chanid, channum);
06568     }
06569 
06570     if (!reclist.empty())
06571     {
06572         vector<uint> excluded_cardids;
06573         excluded_cardids.push_back(ctx->GetCardID());
06574         testrec = RemoteRequestFreeRecorderFromList(reclist, excluded_cardids);
06575     }
06576 
06577     if (testrec && testrec->IsValidRecorder())
06578     {
06579         uint cardid = testrec->GetRecorderNumber();
06580         int cardinputid = (int) inputid;
06581         QString inputname;
06582 
06583         // We are switching to a specific input..
06584         if (inputid)
06585             inputname = CardUtil::GetInputName(inputid);
06586 
06587         // We are switching to a specific channel...
06588         if (inputname.isEmpty() && (chanid || !channum.isEmpty()))
06589         {
06590             if (chanid && channum.isEmpty())
06591                 channum = ChannelUtil::GetChanNum(chanid);
06592 
06593             cardinputid = CardUtil::GetCardInputID(
06594                 cardid, channum, inputname);
06595         }
06596 
06597         if (cardid && cardinputid>0 && !inputname.isEmpty())
06598         {
06599             if (!channum.isEmpty())
06600                 CardUtil::SetStartChannel(cardinputid, channum);
06601         }
06602         else
06603         {
06604             LOG(VB_GENERAL, LOG_WARNING, LOC +
06605                 QString("SwitchCards(%1,'%2',%3)")
06606                     .arg(chanid).arg(channum).arg(inputid) +
06607                 "\n\t\t\tWe should have been able to set a start "
06608                 "channel or input but failed to do so.");
06609         }
06610     }
06611 
06612     // If we are just switching recorders find first available recorder.
06613     if (!testrec)
06614         testrec = RemoteRequestNextFreeRecorder(ctx->GetCardID());
06615 
06616     if (testrec && testrec->IsValidRecorder())
06617     {
06618         // Switching cards so clear the pseudoLiveTVState.
06619         ctx->SetPseudoLiveTV(NULL, kPseudoNormalLiveTV);
06620 
06621         PlayerContext *mctx = GetPlayer(ctx, 0);
06622         if (mctx != ctx)
06623             PIPRemovePlayer(mctx, ctx);
06624 
06625         bool muted = false;
06626         ctx->LockDeletePlayer(__FILE__, __LINE__);
06627         if (ctx->player && ctx->player->IsMuted())
06628             muted = true;
06629         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
06630 
06631         // pause the decoder first, so we're not reading too close to the end.
06632         ctx->buffer->IgnoreLiveEOF(true);
06633         ctx->buffer->StopReads();
06634         ctx->player->PauseDecoder();
06635 
06636         // shutdown stuff
06637         ctx->buffer->Pause();
06638         ctx->buffer->WaitForPause();
06639         ctx->StopPlaying();
06640         ctx->recorder->StopLiveTV();
06641         ctx->SetPlayer(NULL);
06642 
06643         // now restart stuff
06644         ctx->lastSignalUIInfo.clear();
06645         lockTimerOn = false;
06646 
06647         ctx->SetRecorder(testrec);
06648         ctx->recorder->Setup();
06649         // We need to set channum for SpawnLiveTV..
06650         if (channum.isEmpty() && chanid)
06651             channum = ChannelUtil::GetChanNum(chanid);
06652         if (channum.isEmpty() && inputid)
06653             channum = CardUtil::GetStartingChannel(inputid);
06654         ctx->recorder->SpawnLiveTV(ctx->tvchain->GetID(), false, channum);
06655 
06656         if (!ctx->ReloadTVChain())
06657         {
06658             LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully restarted");
06659             RestoreScreenSaver(ctx);
06660             ctx->SetRecorder(NULL);
06661             SetErrored(ctx);
06662             SetExitPlayer(true, false);
06663         }
06664         else
06665         {
06666             ctx->LockPlayingInfo(__FILE__, __LINE__);
06667             QString playbackURL = ctx->playingInfo->GetPlaybackURL(true);
06668             bool opennow = (ctx->tvchain->GetCardType(-1) != "DUMMY");
06669             ctx->SetRingBuffer(
06670                 RingBuffer::Create(
06671                     playbackURL, false, true,
06672                     opennow ? RingBuffer::kLiveTVOpenTimeout : -1));
06673 
06674             ctx->tvchain->SetProgram(*ctx->playingInfo);
06675             ctx->buffer->SetLiveMode(ctx->tvchain);
06676             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
06677         }
06678 
06679         bool ok = false;
06680         if (ctx->playingInfo && StartRecorder(ctx,-1))
06681         {
06682             PlayerContext *mctx = GetPlayer(ctx, 0);
06683             QRect dummy = QRect();
06684             if (ctx->CreatePlayer(
06685                     this, GetMythMainWindow(), ctx->GetState(),
06686                     false, dummy, muted))
06687             {
06688                 ScheduleStateChange(ctx);
06689                 ok = true;
06690                 ctx->PushPreviousChannel();
06691                 for (uint i = 1; i < player.size(); i++)
06692                     PIPAddPlayer(mctx, GetPlayer(ctx, i));
06693 
06694                 SetSpeedChangeTimer(25, __LINE__);
06695             }
06696             else
06697                 StopStuff(mctx, ctx, true, true, true);
06698         }
06699 
06700         if (!ok)
06701         {
06702             LOG(VB_GENERAL, LOG_ERR, LOC + "LiveTV not successfully started");
06703             RestoreScreenSaver(ctx);
06704             ctx->SetRecorder(NULL);
06705             SetErrored(ctx);
06706             SetExitPlayer(true, false);
06707         }
06708         else
06709         {
06710             lockTimer.start();
06711             lockTimerOn = true;
06712         }
06713     }
06714     else
06715     {
06716         LOG(VB_GENERAL, LOG_ERR, LOC + "No recorder to switch to...");
06717         delete testrec;
06718     }
06719 
06720     UpdateOSDInput(ctx);
06721     UnpauseLiveTV(ctx);
06722 
06723     ITVRestart(ctx, true);
06724 }
06725 
06726 void TV::ToggleInputs(PlayerContext *ctx, uint inputid)
06727 {
06728     if (!ctx->recorder)
06729     {
06730         return;
06731     }
06732 
06733     // If MythPlayer is paused, unpause it
06734     if (ContextIsPaused(ctx, __FILE__, __LINE__))
06735     {
06736         HideOSDWindow(ctx, "osd_status");
06737         GetMythUI()->DisableScreensaver();
06738     }
06739 
06740     const QString curinputname = ctx->recorder->GetInput();
06741     QString inputname = curinputname;
06742 
06743     uint cardid = ctx->GetCardID();
06744     vector<uint> excluded_cardids;
06745     excluded_cardids.push_back(cardid);
06746     vector<InputInfo> inputs = RemoteRequestFreeInputList(
06747         cardid, excluded_cardids);
06748 
06749     vector<InputInfo>::const_iterator it = inputs.end();
06750 
06751     if (inputid)
06752     {
06753         it = find(inputs.begin(), inputs.end(), inputid);
06754     }
06755     else
06756     {
06757         it = find(inputs.begin(), inputs.end(), inputname);
06758         if (it != inputs.end())
06759             ++it;
06760     }
06761 
06762     if (it == inputs.end())
06763         it = inputs.begin();
06764 
06765     if (it != inputs.end())
06766         inputname = (*it).name;
06767 
06768     if (curinputname != inputname)
06769     {
06770         // Pause the backend recorder, send command, and then unpause..
06771         PauseLiveTV(ctx);
06772         lockTimerOn = false;
06773         inputname = ctx->recorder->SetInput(inputname);
06774         UnpauseLiveTV(ctx);
06775     }
06776 
06777     UpdateOSDInput(ctx, inputname);
06778 }
06779 
06780 void TV::ToggleChannelFavorite(PlayerContext *ctx)
06781 {
06782     // TOGGLEFAV was broken in [20523], this just prints something
06783     // out so as not to cause further confusion. See #8948.
06784     LOG(VB_GENERAL, LOG_ERR,
06785         "TV::ToggleChannelFavorite() -- currently disabled");
06786 }
06787 
06788 void TV::ToggleChannelFavorite(PlayerContext *ctx, QString changroup_name)
06789 {
06790     if (ctx->recorder)
06791         ctx->recorder->ToggleChannelFavorite(changroup_name);
06792 }
06793 
06794 QString TV::GetQueuedInput(void) const
06795 {
06796     QMutexLocker locker(&timerIdLock);
06797     QString ret = queuedInput;
06798     ret.detach();
06799     return ret;
06800 }
06801 
06802 int TV::GetQueuedInputAsInt(bool *ok, int base) const
06803 {
06804     QMutexLocker locker(&timerIdLock);
06805     return queuedInput.toInt(ok, base);
06806 }
06807 
06808 QString TV::GetQueuedChanNum(void) const
06809 {
06810     QMutexLocker locker(&timerIdLock);
06811 
06812     if (queuedChanNum.isEmpty())
06813         return "";
06814 
06815     // strip initial zeros and other undesirable characters
06816     int i = 0;
06817     for (; i < queuedChanNum.length(); i++)
06818     {
06819         if ((queuedChanNum[i] > '0') && (queuedChanNum[i] <= '9'))
06820             break;
06821     }
06822     queuedChanNum = queuedChanNum.right(queuedChanNum.length() - i);
06823 
06824     // strip whitespace at end of string
06825     queuedChanNum = queuedChanNum.trimmed();
06826 
06827     QString ret = queuedChanNum;
06828     ret.detach();
06829     return ret;
06830 }
06831 
06836 void TV::ClearInputQueues(const PlayerContext *ctx, bool hideosd)
06837 {
06838     if (hideosd)
06839         HideOSDWindow(ctx, "osd_input");
06840 
06841     QMutexLocker locker(&timerIdLock);
06842     queuedInput   = "";
06843     queuedChanNum = "";
06844     queuedChanID  = 0;
06845     if (queueInputTimerId)
06846     {
06847         KillTimer(queueInputTimerId);
06848         queueInputTimerId = 0;
06849     }
06850 }
06851 
06852 void TV::AddKeyToInputQueue(PlayerContext *ctx, char key)
06853 {
06854     if (key)
06855     {
06856         QMutexLocker locker(&timerIdLock);
06857         queuedInput   = queuedInput.append(key).right(kInputKeysMax);
06858         queuedChanNum = queuedChanNum.append(key).right(kInputKeysMax);
06859         if (!queueInputTimerId)
06860             queueInputTimerId = StartTimer(10, __LINE__);
06861     }
06862 
06863     bool commitSmart = false;
06864     QString inputStr = GetQueuedInput();
06865 
06866     // Always use immediate channel change when channel numbers are entered
06867     // in browse mode because in browse mode space/enter exit browse
06868     // mode and change to the currently browsed channel.
06869     if (StateIsLiveTV(GetState(ctx)) && !ccInputMode && !asInputMode &&
06870         browsehelper->IsBrowsing())
06871     {
06872         commitSmart = ProcessSmartChannel(ctx, inputStr);
06873     }
06874 
06875     // Handle OSD...
06876     inputStr = inputStr.isEmpty() ? "?" : inputStr;
06877     if (ccInputMode)
06878     {
06879         QString entryStr = (vbimode==VBIMode::PAL_TT) ? tr("TXT:") : tr("CC:");
06880         inputStr = entryStr + " " + inputStr;
06881     }
06882     else if (asInputMode)
06883         inputStr = tr("Seek:", "seek to location") + " " + inputStr;
06884     SetOSDText(ctx, "osd_input", "osd_number_entry", inputStr,
06885                kOSDTimeout_Med);
06886 
06887     // Commit the channel if it is complete and smart changing is enabled.
06888     if (commitSmart)
06889         CommitQueuedInput(ctx);
06890 }
06891 
06892 static QString add_spacer(const QString &chan, const QString &spacer)
06893 {
06894     if ((chan.length() >= 2) && !spacer.isEmpty())
06895         return chan.left(chan.length()-1) + spacer + chan.right(1);
06896     return chan;
06897 }
06898 
06899 bool TV::ProcessSmartChannel(const PlayerContext *ctx, QString &inputStr)
06900 {
06901     QString chan = GetQueuedChanNum();
06902 
06903     if (chan.isEmpty())
06904         return false;
06905 
06906     // Check for and remove duplicate separator characters
06907     if ((chan.length() > 2) && (chan.right(1) == chan.right(2).left(1)))
06908     {
06909         bool ok;
06910         chan.right(1).toUInt(&ok);
06911         if (!ok)
06912         {
06913             chan = chan.left(chan.length()-1);
06914 
06915             QMutexLocker locker(&timerIdLock);
06916             queuedChanNum = chan;
06917             if (!queueInputTimerId)
06918                 queueInputTimerId = StartTimer(10, __LINE__);
06919         }
06920     }
06921 
06922     // Look for channel in line-up
06923     QString needed_spacer;
06924     uint    pref_cardid;
06925     bool    is_not_complete = true;
06926 
06927     bool valid_prefix = false;
06928     if (ctx->recorder)
06929     {
06930         valid_prefix = ctx->recorder->CheckChannelPrefix(
06931             chan, pref_cardid, is_not_complete, needed_spacer);
06932     }
06933 
06934 #if DEBUG_CHANNEL_PREFIX
06935     LOG(VB_GENERAL, LOG_DEBUG, QString("valid_pref(%1) cardid(%2) chan(%3) "
06936                                        "pref_cardid(%4) complete(%5) sp(%6)")
06937             .arg(valid_prefix).arg(0).arg(chan)
06938             .arg(pref_cardid).arg(is_not_complete).arg(needed_spacer));
06939 #endif
06940 
06941     if (!valid_prefix)
06942     {
06943         // not a valid prefix.. reset...
06944         QMutexLocker locker(&timerIdLock);
06945         queuedChanNum = "";
06946     }
06947     else if (!needed_spacer.isEmpty())
06948     {
06949         // need a spacer..
06950         QMutexLocker locker(&timerIdLock);
06951         queuedChanNum = add_spacer(chan, needed_spacer);
06952     }
06953 
06954 #if DEBUG_CHANNEL_PREFIX
06955     LOG(VB_GENERAL, LOG_DEBUG, QString(" ValidPref(%1) CardId(%2) Chan(%3) "
06956                                        " PrefCardId(%4) Complete(%5) Sp(%6)")
06957             .arg(valid_prefix).arg(0).arg(GetQueuedChanNum())
06958             .arg(pref_cardid).arg(is_not_complete).arg(needed_spacer));
06959 #endif
06960 
06961     QMutexLocker locker(&timerIdLock);
06962     inputStr = queuedChanNum;
06963     inputStr.detach();
06964     if (!queueInputTimerId)
06965         queueInputTimerId = StartTimer(10, __LINE__);
06966 
06967     return !is_not_complete;
06968 }
06969 
06970 bool TV::CommitQueuedInput(PlayerContext *ctx)
06971 {
06972     bool commited = false;
06973 
06974     LOG(VB_PLAYBACK, LOG_INFO, LOC + "CommitQueuedInput() " +
06975         QString("livetv(%1) qchannum(%2) qchanid(%3)")
06976             .arg(StateIsLiveTV(GetState(ctx)))
06977             .arg(GetQueuedChanNum())
06978             .arg(GetQueuedChanID()));
06979 
06980     if (ccInputMode)
06981     {
06982         commited = true;
06983         if (HasQueuedInput())
06984             HandleTrackAction(ctx, ACTION_TOGGLESUBS);
06985     }
06986     else if (asInputMode)
06987     {
06988         commited = true;
06989         if (HasQueuedInput())
06990             // XXX Should the cutlist be honored?
06991             DoArbSeek(ctx, ARBSEEK_FORWARD, /*honorCutlist*/false);
06992     }
06993     else if (StateIsLiveTV(GetState(ctx)))
06994     {
06995         QString channum = GetQueuedChanNum();
06996         QString chaninput = GetQueuedInput();
06997         if (browsehelper->IsBrowsing())
06998         {
06999             uint sourceid = 0;
07000             ctx->LockPlayingInfo(__FILE__, __LINE__);
07001             if (ctx->playingInfo)
07002                 sourceid = ctx->playingInfo->GetSourceID();
07003             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
07004 
07005             commited = true;
07006             if (channum.isEmpty())
07007                 channum = browsehelper->GetBrowsedInfo().m_channum;
07008             uint chanid = browsehelper->GetChanId(
07009                 channum, ctx->GetCardID(), sourceid);
07010             if (chanid)
07011                 browsehelper->BrowseChannel(ctx, channum);
07012 
07013             HideOSDWindow(ctx, "osd_input");
07014         }
07015         else if (GetQueuedChanID() || !channum.isEmpty())
07016         {
07017             commited = true;
07018             ChangeChannel(ctx, GetQueuedChanID(), channum);
07019         }
07020     }
07021 
07022     ClearInputQueues(ctx, true);
07023     return commited;
07024 }
07025 
07026 void TV::ChangeChannel(PlayerContext *ctx, int direction)
07027 {
07028     if (db_use_channel_groups || (direction == CHANNEL_DIRECTION_FAVORITE))
07029     {
07030         uint old_chanid = 0;
07031         if (channelGroupId > -1)
07032         {
07033             ctx->LockPlayingInfo(__FILE__, __LINE__);
07034             if (!ctx->playingInfo)
07035             {
07036                 LOG(VB_GENERAL, LOG_ERR, LOC +
07037                     "ChangeChannel(): no active ctx playingInfo.");
07038                 ctx->UnlockPlayingInfo(__FILE__, __LINE__);
07039                 ReturnPlayerLock(ctx);
07040                 return;
07041             }
07042             // Collect channel info
07043             old_chanid = ctx->playingInfo->GetChanID();
07044             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
07045         }
07046 
07047         if (old_chanid)
07048         {
07049             QMutexLocker locker(&channelGroupLock);
07050             if (channelGroupId > -1)
07051             {
07052                 uint chanid = ChannelUtil::GetNextChannel(
07053                     channelGroupChannelList, old_chanid, 0, direction);
07054                 if (chanid)
07055                     ChangeChannel(ctx, chanid, "");
07056                 return;
07057             }
07058         }
07059     }
07060 
07061     if (direction == CHANNEL_DIRECTION_FAVORITE)
07062         direction = CHANNEL_DIRECTION_UP;
07063 
07064     QString oldinputname = ctx->recorder->GetInput();
07065 
07066     if (ContextIsPaused(ctx, __FILE__, __LINE__))
07067     {
07068         HideOSDWindow(ctx, "osd_status");
07069         GetMythUI()->DisableScreensaver();
07070     }
07071 
07072     // Save the current channel if this is the first time
07073     if (ctx->prevChan.empty())
07074         ctx->PushPreviousChannel();
07075 
07076     PauseAudioUntilBuffered(ctx);
07077     PauseLiveTV(ctx);
07078 
07079     ctx->LockDeletePlayer(__FILE__, __LINE__);
07080     if (ctx->player)
07081     {
07082         ctx->player->ResetCaptions();
07083         ctx->player->ResetTeletext();
07084     }
07085     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
07086 
07087     ctx->recorder->ChangeChannel(direction);
07088     ClearInputQueues(ctx, false);
07089 
07090     if (ctx->player)
07091         ctx->player->GetAudio()->Reset();
07092 
07093     UnpauseLiveTV(ctx);
07094 
07095     if (oldinputname != ctx->recorder->GetInput())
07096         UpdateOSDInput(ctx);
07097 }
07098 
07099 static uint get_chanid(const PlayerContext *ctx,
07100                        uint cardid, const QString &channum)
07101 {
07102     uint chanid = 0, cur_sourceid = 0;
07103     // try to find channel on current input
07104     if (ctx && ctx->playingInfo && ctx->playingInfo->GetSourceID())
07105     {
07106         cur_sourceid = ctx->playingInfo->GetSourceID();
07107         chanid = max(ChannelUtil::GetChanID(cur_sourceid, channum), 0);
07108         if (chanid)
07109             return chanid;
07110     }
07111     // try to find channel on all inputs
07112     vector<uint> inputs = CardUtil::GetInputIDs(cardid);
07113     for (vector<uint>::const_iterator it = inputs.begin();
07114          it != inputs.end(); ++it)
07115     {
07116         uint sourceid = CardUtil::GetSourceID(*it);
07117         if (cur_sourceid == sourceid)
07118             continue; // already tested above
07119         if (sourceid)
07120         {
07121             chanid = max(ChannelUtil::GetChanID(sourceid, channum), 0);
07122             if (chanid)
07123                 return chanid;
07124         }
07125     }
07126     return chanid;
07127 }
07128 
07129 void TV::ChangeChannel(PlayerContext *ctx, uint chanid, const QString &chan)
07130 {
07131     LOG(VB_CHANNEL, LOG_INFO, LOC + QString("ChangeChannel(%1, '%2') ")
07132             .arg(chanid).arg(chan));
07133 
07134     if ((!chanid && chan.isEmpty()) || !ctx || !ctx->recorder)
07135         return;
07136 
07137     QString channum = chan;
07138     QStringList reclist;
07139     QSet<uint> tunable_on;
07140 
07141     QString oldinputname = ctx->recorder->GetInput();
07142 
07143     if (channum.isEmpty() && chanid)
07144     {
07145         channum = ChannelUtil::GetChanNum(chanid);
07146     }
07147 
07148     bool getit = false;
07149     if (ctx->recorder)
07150     {
07151         if (ctx->pseudoLiveTVState == kPseudoRecording)
07152         {
07153             getit = true;
07154         }
07155         else if (chanid)
07156         {
07157             tunable_on = IsTunableOn(ctx, chanid, true, false);
07158             getit = !tunable_on.contains(ctx->GetCardID());
07159         }
07160         else
07161         {
07162             QString needed_spacer;
07163             uint pref_cardid;
07164             uint cardid = ctx->GetCardID();
07165             bool dummy;
07166 
07167             ctx->recorder->CheckChannelPrefix(chan,  pref_cardid,
07168                                               dummy, needed_spacer);
07169 
07170             LOG(VB_CHANNEL, LOG_INFO, LOC +
07171                 QString("CheckChannelPrefix(%1, pref_cardid %2, %3, '%4') "
07172                         "cardid %5")
07173                 .arg(chan).arg(pref_cardid).arg(dummy).arg(needed_spacer)
07174                 .arg(cardid));
07175 
07176             channum = add_spacer(chan, needed_spacer);
07177             if (pref_cardid != cardid)
07178             {
07179                 getit = true;
07180             }
07181             else
07182             {
07183                 if (!chanid)
07184                     chanid = get_chanid(ctx, cardid, chan);
07185                 tunable_on = IsTunableOn(ctx, chanid, true, false);
07186                 getit = !tunable_on.contains(cardid);
07187             }
07188         }
07189 
07190         if (getit)
07191         {
07192             QStringList tmp =
07193                 ChannelUtil::GetValidRecorderList(chanid, channum);
07194             if (tunable_on.empty())
07195             {
07196                 if (!chanid)
07197                     chanid = get_chanid(ctx, ctx->GetCardID(), chan);
07198                 tunable_on = IsTunableOn(ctx, chanid, true, false);
07199             }
07200             QStringList::const_iterator it = tmp.begin();
07201             for (; it != tmp.end(); ++it)
07202             {
07203                 if (!chanid || tunable_on.contains((*it).toUInt()))
07204                     reclist.push_back(*it);
07205             }
07206         }
07207     }
07208 
07209     if (reclist.size())
07210     {
07211         RemoteEncoder *testrec = NULL;
07212         vector<uint> excluded_cardids;
07213         excluded_cardids.push_back(ctx->GetCardID());
07214         testrec = RemoteRequestFreeRecorderFromList(reclist, excluded_cardids);
07215         if (!testrec || !testrec->IsValidRecorder())
07216         {
07217             ClearInputQueues(ctx, true);
07218             ShowNoRecorderDialog(ctx);
07219             if (testrec)
07220                 delete testrec;
07221             return;
07222         }
07223 
07224         if (!ctx->prevChan.empty() && ctx->prevChan.back() == channum)
07225         {
07226             // need to remove it if the new channel is the same as the old.
07227             ctx->prevChan.pop_back();
07228         }
07229 
07230         uint new_cardid = testrec->GetRecorderNumber();
07231         uint sourceid = ChannelUtil::GetSourceIDForChannel(chanid);
07232         uint inputid = CardUtil::GetInputID(new_cardid, sourceid);
07233 
07234         // found the card on a different recorder.
07235         delete testrec;
07236         // Save the current channel if this is the first time
07237         if (ctx->prevChan.empty())
07238             ctx->PushPreviousChannel();
07239         SwitchCards(ctx, chanid, channum, inputid);
07240         return;
07241     }
07242 
07243     if (getit || !ctx->recorder || !ctx->recorder->CheckChannel(channum))
07244         return;
07245 
07246     if (ContextIsPaused(ctx, __FILE__, __LINE__))
07247     {
07248         HideOSDWindow(ctx, "osd_status");
07249         GetMythUI()->DisableScreensaver();
07250     }
07251 
07252     // Save the current channel if this is the first time
07253     if (ctx->prevChan.empty())
07254         ctx->PushPreviousChannel();
07255 
07256     PauseAudioUntilBuffered(ctx);
07257     PauseLiveTV(ctx);
07258 
07259     ctx->LockDeletePlayer(__FILE__, __LINE__);
07260     if (ctx->player)
07261     {
07262         ctx->player->ResetCaptions();
07263         ctx->player->ResetTeletext();
07264     }
07265     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
07266 
07267     ctx->recorder->SetChannel(channum);
07268 
07269     if (ctx->player)
07270         ctx->player->GetAudio()->Reset();
07271 
07272     UnpauseLiveTV(ctx, chanid && GetQueuedChanID());
07273 
07274     if (oldinputname != ctx->recorder->GetInput())
07275         UpdateOSDInput(ctx);
07276 }
07277 
07278 void TV::ChangeChannel(const PlayerContext *ctx, const DBChanList &options)
07279 {
07280     for (uint i = 0; i < options.size(); i++)
07281     {
07282         uint    chanid  = options[i].chanid;
07283         QString channum = options[i].channum;
07284 
07285         if (chanid && !channum.isEmpty() && IsTunable(ctx, chanid))
07286         {
07287             // hide the channel number, activated by certain signal monitors
07288             HideOSDWindow(ctx, "osd_input");
07289 
07290             QMutexLocker locker(&timerIdLock);
07291             queuedInput   = channum; queuedInput.detach();
07292             queuedChanNum = channum; queuedChanNum.detach();
07293             queuedChanID  = chanid;
07294             if (!queueInputTimerId)
07295                 queueInputTimerId = StartTimer(10, __LINE__);
07296             break;
07297         }
07298     }
07299 }
07300 
07301 void TV::ShowPreviousChannel(PlayerContext *ctx)
07302 {
07303     QString channum = ctx->GetPreviousChannel();
07304 
07305     LOG(VB_CHANNEL, LOG_INFO, LOC + QString("ShowPreviousChannel: '%1'")
07306             .arg(channum));
07307 
07308     if (channum.isEmpty())
07309         return;
07310 
07311     SetOSDText(ctx, "osd_input", "osd_number_entry", channum, kOSDTimeout_Med);
07312 }
07313 
07314 void TV::PopPreviousChannel(PlayerContext *ctx, bool immediate_change)
07315 {
07316     if (!ctx->tvchain)
07317         return;
07318 
07319     if (!immediate_change)
07320         ShowPreviousChannel(ctx);
07321 
07322     QString prev_channum = ctx->PopPreviousChannel();
07323     QString cur_channum  = ctx->tvchain->GetChannelName(-1);
07324 
07325     LOG(VB_CHANNEL, LOG_INFO, LOC + QString("PopPreviousChannel: '%1'->'%2'")
07326             .arg(cur_channum).arg(prev_channum));
07327 
07328     // Only change channel if previous channel != current channel
07329     if (cur_channum != prev_channum && !prev_channum.isEmpty())
07330     {
07331         QMutexLocker locker(&timerIdLock);
07332         queuedInput   = prev_channum; queuedInput.detach();
07333         queuedChanNum = prev_channum; queuedChanNum.detach();
07334         queuedChanID  = 0;
07335         if (!queueInputTimerId)
07336             queueInputTimerId = StartTimer(10, __LINE__);
07337     }
07338 
07339     if (immediate_change)
07340     {
07341         // Turn off OSD Channel Num so the channel changes right away
07342         HideOSDWindow(ctx, "osd_input");
07343     }
07344 }
07345 
07346 bool TV::ClearOSD(const PlayerContext *ctx)
07347 {
07348     bool res = false;
07349 
07350     if (HasQueuedInput() || HasQueuedChannel())
07351     {
07352         ClearInputQueues(ctx, true);
07353         res = true;
07354     }
07355 
07356     OSD *osd = GetOSDLock(ctx);
07357     if (osd)
07358     {
07359         osd->DialogQuit();
07360         osd->HideAll();
07361         res = true;
07362     }
07363     ReturnOSDLock(ctx, osd);
07364 
07365     if (browsehelper->IsBrowsing())
07366         browsehelper->BrowseEnd(NULL, false);
07367 
07368     return res;
07369 }
07370 
07374 void TV::ToggleOSD(PlayerContext *ctx, bool includeStatusOSD)
07375 {
07376     OSD *osd = GetOSDLock(ctx);
07377     if (!osd)
07378     {
07379         ReturnOSDLock(ctx, osd);
07380         return;
07381     }
07382 
07383     bool hideAll    = false;
07384     bool showStatus = false;
07385     bool paused     = ContextIsPaused(ctx, __FILE__, __LINE__);
07386     bool is_status_disp    = osd->IsWindowVisible("osd_status");
07387     bool has_prog_info     = osd->HasWindow("program_info");
07388     bool is_prog_info_disp = osd->IsWindowVisible("program_info");
07389 
07390     ReturnOSDLock(ctx, osd);
07391 
07392     if (is_status_disp)
07393     {
07394         if (has_prog_info)
07395             UpdateOSDProgInfo(ctx, "program_info");
07396         else
07397             hideAll = true;
07398     }
07399     else if (is_prog_info_disp && !paused)
07400     {
07401         hideAll = true;
07402     }
07403     else if (includeStatusOSD)
07404     {
07405         showStatus = true;
07406     }
07407     else
07408     {
07409         if (has_prog_info)
07410             UpdateOSDProgInfo(ctx, "program_info");
07411     }
07412 
07413     if (hideAll || showStatus)
07414     {
07415         OSD *osd = GetOSDLock(ctx);
07416         if (osd)
07417             osd->HideAll();
07418         ReturnOSDLock(ctx, osd);
07419     }
07420 
07421     if (showStatus)
07422     {
07423         osdInfo info;
07424         if (ctx->CalcPlayerSliderPosition(info))
07425         {
07426             info.text["title"] = paused ? tr("Paused") : tr("Position");
07427             UpdateOSDStatus(ctx, info, kOSDFunctionalType_Default,
07428                             paused ? kOSDTimeout_None : kOSDTimeout_Med);
07429             SetUpdateOSDPosition(true);
07430         }
07431         else
07432         {
07433             SetUpdateOSDPosition(false);
07434         }
07435     }
07436     else
07437     {
07438         SetUpdateOSDPosition(false);
07439     }
07440 }
07441 
07442 void TV::ToggleOSDDebug(PlayerContext *ctx)
07443 {
07444     bool show = false;
07445     OSD *osd = GetOSDLock(ctx);
07446     if (osd && osd->IsWindowVisible("osd_debug"))
07447     {
07448         ctx->buffer->EnableBitrateMonitor(false);
07449         if (ctx->player)
07450             ctx->player->EnableFrameRateMonitor(false);
07451         osd->HideWindow("osd_debug");
07452     }
07453     else if (osd)
07454     {
07455         ctx->buffer->EnableBitrateMonitor(true);
07456         if (ctx->player)
07457             ctx->player->EnableFrameRateMonitor(true);
07458         show = true;
07459         QMutexLocker locker(&timerIdLock);
07460         if (!updateOSDDebugTimerId)
07461             updateOSDDebugTimerId = StartTimer(250, __LINE__);
07462     }
07463     ReturnOSDLock(ctx, osd);
07464     if (show)
07465         UpdateOSDDebug(ctx);
07466 }
07467 
07468 void TV::UpdateOSDDebug(const PlayerContext *ctx)
07469 {
07470     OSD *osd = GetOSDLock(ctx);
07471     if (osd && ctx->player)
07472     {
07473         InfoMap infoMap;
07474         ctx->player->GetPlaybackData(infoMap);
07475         osd->ResetWindow("osd_debug");
07476         osd->SetText("osd_debug", infoMap, kOSDTimeout_None);
07477     }
07478     ReturnOSDLock(ctx, osd);
07479 }
07480 
07484 void TV::UpdateOSDProgInfo(const PlayerContext *ctx, const char *whichInfo)
07485 {
07486     InfoMap infoMap;
07487     ctx->GetPlayingInfoMap(infoMap);
07488 
07489     QString nightmode = gCoreContext->GetNumSetting("NightModeEnabled", 0)
07490                             ? "yes" : "no";
07491     infoMap["nightmode"] = nightmode;
07492 
07493     // Clear previous osd and add new info
07494     OSD *osd = GetOSDLock(ctx);
07495     if (osd)
07496     {
07497         osd->HideAll();
07498         osd->SetText(whichInfo, infoMap, kOSDTimeout_Long);
07499     }
07500     ReturnOSDLock(ctx, osd);
07501 }
07502 
07503 void TV::UpdateOSDStatus(const PlayerContext *ctx, osdInfo &info,
07504                          int type, OSDTimeout timeout)
07505 {
07506     OSD *osd = GetOSDLock(ctx);
07507     if (osd)
07508     {
07509         osd->ResetWindow("osd_status");
07510         QString nightmode = gCoreContext->GetNumSetting("NightModeEnabled", 0)
07511                                 ? "yes" : "no";
07512         info.text.insert("nightmode", nightmode);
07513         osd->SetValues("osd_status", info.values, timeout);
07514         osd->SetText("osd_status",   info.text, timeout);
07515         if (type != kOSDFunctionalType_Default)
07516             osd->SetFunctionalWindow("osd_status", (OSDFunctionalType)type);
07517     }
07518     ReturnOSDLock(ctx, osd);
07519 }
07520 
07521 void TV::UpdateOSDStatus(const PlayerContext *ctx, QString title, QString desc,
07522                          QString value, int type, QString units,
07523                          int position, OSDTimeout timeout)
07524 {
07525     osdInfo info;
07526     info.values.insert("position", position);
07527     info.values.insert("relposition", position);
07528     info.text.insert("title", title);
07529     info.text.insert("description", desc);
07530     info.text.insert("value", value);
07531     info.text.insert("units", units);
07532     UpdateOSDStatus(ctx, info, type, timeout);
07533 }
07534 
07535 void TV::UpdateOSDSeekMessage(const PlayerContext *ctx,
07536                               const QString &mesg, enum OSDTimeout timeout)
07537 {
07538     LOG(VB_PLAYBACK, LOG_INFO, QString("UpdateOSDSeekMessage(%1, %2)")
07539             .arg(mesg).arg(timeout));
07540 
07541     osdInfo info;
07542     if (ctx->CalcPlayerSliderPosition(info))
07543     {
07544         int osdtype = (doSmartForward) ? kOSDFunctionalType_SmartForward :
07545             kOSDFunctionalType_Default;
07546         info.text["title"] = mesg;
07547         UpdateOSDStatus(ctx, info, osdtype, timeout);
07548         SetUpdateOSDPosition(true);
07549     }
07550 }
07551 
07552 void TV::UpdateOSDInput(const PlayerContext *ctx, QString inputname)
07553 {
07554     if (!ctx->recorder || !ctx->tvchain)
07555         return;
07556 
07557     int cardid = ctx->GetCardID();
07558 
07559     if (inputname.isEmpty())
07560         inputname = ctx->tvchain->GetInputName(-1);
07561 
07562     QString displayName = CardUtil::GetDisplayName(cardid, inputname);
07563     // If a display name doesn't exist use cardid and inputname
07564     if (displayName.isEmpty())
07565         displayName = QString("%1: %2").arg(cardid).arg(inputname);
07566 
07567     SetOSDMessage(ctx, displayName);
07568 }
07569 
07573 void TV::UpdateOSDSignal(const PlayerContext *ctx, const QStringList &strlist)
07574 {
07575     OSD *osd = GetOSDLock(ctx);
07576     if (!osd || browsehelper->IsBrowsing() || !queuedChanNum.isEmpty())
07577     {
07578         if (&ctx->lastSignalMsg != &strlist)
07579         {
07580             ctx->lastSignalMsg = strlist;
07581             ctx->lastSignalMsg.detach();
07582         }
07583         ReturnOSDLock(ctx, osd);
07584 
07585         QMutexLocker locker(&timerIdLock);
07586         signalMonitorTimerId[StartTimer(1, __LINE__)] =
07587             const_cast<PlayerContext*>(ctx);
07588         return;
07589     }
07590     ReturnOSDLock(ctx, osd);
07591 
07592     SignalMonitorList slist = SignalMonitorValue::Parse(strlist);
07593 
07594     InfoMap infoMap = ctx->lastSignalUIInfo;
07595     if (ctx->lastSignalUIInfoTime.elapsed() > 5000 ||
07596         infoMap["callsign"].isEmpty())
07597     {
07598         ctx->lastSignalUIInfo.clear();
07599         ctx->GetPlayingInfoMap(ctx->lastSignalUIInfo);
07600 
07601         infoMap = ctx->lastSignalUIInfo;
07602         ctx->lastSignalUIInfoTime.start();
07603     }
07604 
07605     int i = 0;
07606     SignalMonitorList::const_iterator it;
07607     for (it = slist.begin(); it != slist.end(); ++it)
07608         if ("error" == it->GetShortName())
07609             infoMap[QString("error%1").arg(i++)] = it->GetName();
07610     i = 0;
07611     for (it = slist.begin(); it != slist.end(); ++it)
07612         if ("message" == it->GetShortName())
07613             infoMap[QString("message%1").arg(i++)] = it->GetName();
07614 
07615     uint  sig  = 0;
07616     float snr  = 0.0f;
07617     uint  ber  = 0xffffffff;
07618     int   pos  = -1;
07619     int   tuned = -1;
07620     QString pat(""), pmt(""), mgt(""), vct(""), nit(""), sdt(""), crypt("");
07621     QString err = QString::null, msg = QString::null;
07622     for (it = slist.begin(); it != slist.end(); ++it)
07623     {
07624         if ("error" == it->GetShortName())
07625         {
07626             err = it->GetName();
07627             continue;
07628         }
07629 
07630         if ("message" == it->GetShortName())
07631         {
07632             msg = it->GetName();
07633             LOG(VB_GENERAL, LOG_INFO, "msg: " + msg);
07634             continue;
07635         }
07636 
07637         infoMap[it->GetShortName()] = QString::number(it->GetValue());
07638         if ("signal" == it->GetShortName())
07639             sig = it->GetNormalizedValue(0, 100);
07640         else if ("snr" == it->GetShortName())
07641             snr = it->GetValue();
07642         else if ("ber" == it->GetShortName())
07643             ber = it->GetValue();
07644         else if ("pos" == it->GetShortName())
07645             pos = it->GetValue();
07646         else if ("tuned" == it->GetShortName())
07647             tuned = it->GetValue();
07648         else if ("seen_pat" == it->GetShortName())
07649             pat = it->IsGood() ? "a" : "_";
07650         else if ("matching_pat" == it->GetShortName())
07651             pat = it->IsGood() ? "A" : pat;
07652         else if ("seen_pmt" == it->GetShortName())
07653             pmt = it->IsGood() ? "m" : "_";
07654         else if ("matching_pmt" == it->GetShortName())
07655             pmt = it->IsGood() ? "M" : pmt;
07656         else if ("seen_mgt" == it->GetShortName())
07657             mgt = it->IsGood() ? "g" : "_";
07658         else if ("matching_mgt" == it->GetShortName())
07659             mgt = it->IsGood() ? "G" : mgt;
07660         else if ("seen_vct" == it->GetShortName())
07661             vct = it->IsGood() ? "v" : "_";
07662         else if ("matching_vct" == it->GetShortName())
07663             vct = it->IsGood() ? "V" : vct;
07664         else if ("seen_nit" == it->GetShortName())
07665             nit = it->IsGood() ? "n" : "_";
07666         else if ("matching_nit" == it->GetShortName())
07667             nit = it->IsGood() ? "N" : nit;
07668         else if ("seen_sdt" == it->GetShortName())
07669             sdt = it->IsGood() ? "s" : "_";
07670         else if ("matching_sdt" == it->GetShortName())
07671             sdt = it->IsGood() ? "S" : sdt;
07672         else if ("seen_crypt" == it->GetShortName())
07673             crypt = it->IsGood() ? "c" : "_";
07674         else if ("matching_crypt" == it->GetShortName())
07675             crypt = it->IsGood() ? "C" : crypt;
07676     }
07677     if (sig)
07678         infoMap["signal"] = QString::number(sig); // use normalized value
07679 
07680     bool    allGood = SignalMonitorValue::AllGood(slist);
07681     char    tuneCode;
07682     QString slock   = ("1" == infoMap["slock"]) ? "L" : "l";
07683     QString lockMsg = (slock=="L") ? tr("Partial Lock") : tr("No Lock");
07684     QString sigMsg  = allGood ? tr("Lock") : lockMsg;
07685 
07686     QString sigDesc = tr("Signal %1%").arg(sig,2);
07687     if (snr > 0.0f)
07688         sigDesc += " | " + tr("S/N %1dB").arg(log10f(snr), 3, 'f', 1);
07689     if (ber != 0xffffffff)
07690         sigDesc += " | " + tr("BE %1", "Bit Errors").arg(ber, 2);
07691     if ((pos >= 0) && (pos < 100))
07692         sigDesc += " | " + tr("Rotor %1%").arg(pos,2);
07693 
07694     if (tuned == 1)
07695         tuneCode = 't';
07696     else if (tuned == 2)
07697         tuneCode = 'F';
07698     else if (tuned == 3)
07699         tuneCode = 'T';
07700     else
07701         tuneCode = '_';
07702 
07703     sigDesc = sigDesc + QString(" | (%1%2%3%4%5%6%7%8%9) %10")
07704               .arg(tuneCode).arg(slock).arg(pat).arg(pmt).arg(mgt).arg(vct)
07705               .arg(nit).arg(sdt).arg(crypt).arg(sigMsg);
07706 
07707     if (!err.isEmpty())
07708         sigDesc = err;
07709     else if (!msg.isEmpty())
07710         sigDesc = msg;
07711 
07712     osd = GetOSDLock(ctx);
07713     if (osd)
07714     {
07715         infoMap["description"] = sigDesc;
07716         osd->SetText("program_info", infoMap, kOSDTimeout_Med);
07717     }
07718     ReturnOSDLock(ctx, osd);
07719 
07720     ctx->lastSignalMsg.clear();
07721     ctx->lastSignalMsgTime.start();
07722 
07723     // Turn off lock timer if we have an "All Good" or good PMT
07724     if (allGood || (pmt == "M"))
07725     {
07726         lockTimerOn = false;
07727         lastLockSeenTime = QDateTime::currentDateTime();
07728     }
07729 }
07730 
07731 void TV::UpdateOSDTimeoutMessage(PlayerContext *ctx)
07732 {
07733     bool timed_out = false;
07734 
07735     if (ctx->recorder)
07736     {
07737         QString input = ctx->recorder->GetInput();
07738         uint timeout  = ctx->recorder->GetSignalLockTimeout(input);
07739         timed_out = lockTimerOn && ((uint)lockTimer.elapsed() > timeout);
07740     }
07741 
07742     OSD *osd = GetOSDLock(ctx);
07743 
07744     if (!osd)
07745     {
07746         if (timed_out)
07747         {
07748             LOG(VB_GENERAL, LOG_ERR, LOC +
07749                 "You have no OSD, but tuning has already taken too long.");
07750         }
07751         ReturnOSDLock(ctx, osd);
07752         return;
07753     }
07754 
07755     bool showing = osd->DialogVisible(OSD_DLG_INFO);
07756     if (!timed_out)
07757     {
07758         if (showing)
07759             osd->DialogQuit();
07760         ReturnOSDLock(ctx, osd);
07761         return;
07762     }
07763 
07764     if (showing)
07765     {
07766         ReturnOSDLock(ctx, osd);
07767         return;
07768     }
07769 
07770     // create dialog...
07771     static QString chan_up   = GET_KEY("TV Playback", ACTION_CHANNELUP);
07772     static QString chan_down = GET_KEY("TV Playback", ACTION_CHANNELDOWN);
07773     static QString next_src  = GET_KEY("TV Playback", "NEXTSOURCE");
07774     static QString tog_cards = GET_KEY("TV Playback", "NEXTINPUT");
07775 
07776     QString message = tr(
07777         "You should have received a channel lock by now. "
07778         "You can continue to wait for a signal, or you "
07779         "can change the channel with %1 or %2, change "
07780         "video source (%3), inputs (%4), etc.")
07781         .arg(chan_up).arg(chan_down).arg(next_src).arg(tog_cards);
07782 
07783     osd->DialogShow(OSD_DLG_INFO, message);
07784     QString action = "DIALOG_INFO_CHANNELLOCK_0";
07785     osd->DialogAddButton(tr("OK"), action);
07786     osd->DialogBack("", action, true);
07787 
07788     ReturnOSDLock(ctx, osd);
07789 }
07790 
07791 void TV::UpdateLCD(void)
07792 {
07793     // Make sure the LCD information gets updated shortly
07794     QMutexLocker locker(&timerIdLock);
07795     if (lcdTimerId)
07796         KillTimer(lcdTimerId);
07797     lcdTimerId = StartTimer(1, __LINE__);
07798 }
07799 
07800 void TV::ShowLCDChannelInfo(const PlayerContext *ctx)
07801 {
07802     LCD *lcd = LCD::Get();
07803     ctx->LockPlayingInfo(__FILE__, __LINE__);
07804     if (!lcd || !ctx->playingInfo)
07805     {
07806         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
07807         return;
07808     }
07809 
07810     QString title    = ctx->playingInfo->GetTitle();
07811     QString subtitle = ctx->playingInfo->GetSubtitle();
07812     QString callsign = ctx->playingInfo->GetChannelSchedulingID();
07813 
07814     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
07815 
07816     if ((callsign != lcdCallsign) || (title != lcdTitle) ||
07817         (subtitle != lcdSubtitle))
07818     {
07819         lcd->switchToChannel(callsign, title, subtitle);
07820         lcdCallsign = callsign;
07821         lcdTitle = title;
07822         lcdSubtitle = subtitle;
07823     }
07824 }
07825 
07826 static void format_time(int seconds, QString &tMin, QString &tHrsMin)
07827 {
07828     int minutes     = seconds / 60;
07829     int hours       = minutes / 60;
07830     int min         = minutes % 60;
07831 
07832     tMin = TV::tr("%n minute(s)", "", minutes);
07833     tHrsMin.sprintf("%d:%02d", hours, min);
07834 }
07835 
07836 
07837 void TV::ShowLCDDVDInfo(const PlayerContext *ctx)
07838 {
07839     LCD *lcd = LCD::Get();
07840 
07841     if (!lcd || !ctx->buffer || !ctx->buffer->IsDVD())
07842     {
07843         return;
07844     }
07845 
07846     DVDRingBuffer *dvd = ctx->buffer->DVD();
07847     QString dvdName, dvdSerial;
07848     QString mainStatus, subStatus;
07849 
07850     if (!dvd->GetNameAndSerialNum(dvdName, dvdSerial))
07851     {
07852         dvdName = tr("DVD");
07853     }
07854 
07855     if (dvd->IsInMenu())
07856     {
07857         mainStatus = tr("Menu");
07858     }
07859     else if (dvd->IsInStillFrame())
07860     {
07861         mainStatus = tr("Still Frame");
07862     }
07863     else
07864     {
07865         QString timeMins, timeHrsMin;
07866         int playingTitle, playingPart, totalParts;
07867 
07868         dvd->GetPartAndTitle(playingPart, playingTitle);
07869         totalParts = dvd->NumPartsInTitle();
07870         format_time(dvd->GetTotalTimeOfTitle(), timeMins, timeHrsMin);
07871 
07872         mainStatus = tr("Title: %1 (%2)").arg(playingTitle).arg(timeHrsMin);
07873         subStatus = tr("Chapter: %1/%2").arg(playingPart).arg(totalParts);
07874     }
07875     if ((dvdName != lcdCallsign) || (mainStatus != lcdTitle) ||
07876                                     (subStatus != lcdSubtitle))
07877     {
07878         lcd->switchToChannel(dvdName, mainStatus, subStatus);
07879         lcdCallsign = dvdName;
07880         lcdTitle    = mainStatus;
07881         lcdSubtitle = subStatus;
07882     }
07883 }
07884 
07885 
07886 bool TV::IsTunable(const PlayerContext *ctx, uint chanid, bool use_cache)
07887 {
07888     return !IsTunableOn(ctx,chanid,use_cache,true).empty();
07889 }
07890 
07891 static QString toCommaList(const QSet<uint> &list)
07892 {
07893     QString ret = "";
07894     for (QSet<uint>::const_iterator it = list.begin(); it != list.end(); ++it)
07895         ret += QString("%1,").arg(*it);
07896 
07897     if (ret.length())
07898         return ret.left(ret.length()-1);
07899 
07900     return "";
07901 }
07902 
07903 QSet<uint> TV::IsTunableOn(
07904     const PlayerContext *ctx, uint chanid, bool use_cache, bool early_exit)
07905 {
07906     QSet<uint> tunable_cards;
07907 
07908     if (!chanid)
07909     {
07910         LOG(VB_CHANNEL, LOG_INFO, LOC +
07911             QString("IsTunableOn(%1) no").arg(chanid));
07912 
07913         return tunable_cards;
07914     }
07915 
07916     uint mplexid = ChannelUtil::GetMplexID(chanid);
07917     mplexid = (32767 == mplexid) ? 0 : mplexid;
07918 
07919     vector<uint> excluded_cards;
07920     if (ctx->recorder && ctx->pseudoLiveTVState == kPseudoNormalLiveTV)
07921         excluded_cards.push_back(ctx->GetCardID());
07922 
07923     uint sourceid = ChannelUtil::GetSourceIDForChannel(chanid);
07924     vector<uint> connected   = RemoteRequestFreeRecorderList(excluded_cards);
07925     vector<uint> interesting = CardUtil::GetCardIDs(sourceid);
07926 
07927     // filter disconnected cards
07928     vector<uint> cardids = excluded_cards;
07929     for (uint i = 0; i < connected.size(); i++)
07930     {
07931         for (uint j = 0; j < interesting.size(); j++)
07932         {
07933             if (connected[i] == interesting[j])
07934             {
07935                 cardids.push_back(interesting[j]);
07936                 break;
07937             }
07938         }
07939     }
07940 
07941 #if 0
07942     {
07943         QString msg = QString("cardids[%1]: ").arg(sourceid);
07944         for (uint i = 0; i < cardids.size(); i++)
07945             msg += QString("%1, ").arg(cardids[i]);
07946         LOG(VB_CHANNEL, LOG_INFO,  msg);
07947     }
07948 #endif
07949 
07950     for (uint i = 0; i < cardids.size(); i++)
07951     {
07952         vector<InputInfo> inputs;
07953 
07954         bool used_cache = false;
07955         if (use_cache)
07956         {
07957             QMutexLocker locker(&is_tunable_cache_lock);
07958             if (is_tunable_cache_inputs.contains(cardids[i]))
07959             {
07960                 inputs = is_tunable_cache_inputs[cardids[i]];
07961                 used_cache = true;
07962             }
07963         }
07964 
07965         if (!used_cache)
07966         {
07967             inputs = RemoteRequestFreeInputList(cardids[i], excluded_cards);
07968             QMutexLocker locker(&is_tunable_cache_lock);
07969             is_tunable_cache_inputs[cardids[i]] = inputs;
07970         }
07971 
07972 #if 0
07973     {
07974         QString msg = QString("inputs[%1]: ").arg(cardids[i]);
07975         for (uint j = 0; j < inputs.size(); j++)
07976             msg += QString("%1, ").arg(inputs[j].inputid);
07977         LOG(VB_CHANNEL, LOG_INFO, msg);
07978     }
07979 #endif
07980 
07981         for (uint j = 0; j < inputs.size(); j++)
07982         {
07983             if (inputs[j].sourceid != sourceid)
07984                 continue;
07985 
07986             if (inputs[j].mplexid &&
07987                 inputs[j].mplexid != mplexid)
07988                 continue;
07989 
07990             tunable_cards.insert(cardids[i]);
07991 
07992             break;
07993         }
07994 
07995         if (early_exit && !tunable_cards.empty())
07996             break;
07997     }
07998 
07999     if (tunable_cards.empty())
08000     {
08001         LOG(VB_CHANNEL, LOG_INFO, LOC + QString("IsTunableOn(%1) no")
08002             .arg(chanid));
08003     }
08004     else
08005     {
08006         LOG(VB_CHANNEL, LOG_INFO, LOC + QString("IsTunableOn(%1) yes { %2 }")
08007             .arg(chanid).arg(toCommaList(tunable_cards)));
08008     }
08009 
08010     return tunable_cards;
08011 }
08012 
08013 void TV::ClearTunableCache(void)
08014 {
08015     QMutexLocker locker(&is_tunable_cache_lock);
08016     LOG(VB_CHANNEL, LOG_INFO, LOC + "ClearTunableCache()");
08017     is_tunable_cache_inputs.clear();
08018 }
08019 
08020 bool TV::StartEmbedding(const QRect &embedRect)
08021 {
08022     PlayerContext *ctx = GetPlayerReadLock(-1, __FILE__, __LINE__);
08023     if (!ctx)
08024         return false;
08025 
08026     WId wid = GetMythMainWindow()->GetPaintWindow()->winId();
08027 
08028     if (!ctx->IsNullVideoDesired())
08029         ctx->StartEmbedding(wid, embedRect);
08030     else
08031     {
08032         LOG(VB_GENERAL, LOG_WARNING, LOC +
08033             QString("StartEmbedding called with null video context #%1")
08034                 .arg(find_player_index(ctx)));
08035         ctx->ResizePIPWindow(embedRect);
08036     }
08037 
08038     // Hide any PIP windows...
08039     PlayerContext *mctx = GetPlayer(ctx, 0);
08040     for (uint i = 1; (mctx == ctx) && (i < player.size()); i++)
08041     {
08042         GetPlayer(ctx,i)->LockDeletePlayer(__FILE__, __LINE__);
08043         if (GetPlayer(ctx,i)->player)
08044             GetPlayer(ctx,i)->player->SetPIPVisible(false);
08045         GetPlayer(ctx,i)->UnlockDeletePlayer(__FILE__, __LINE__);
08046     }
08047 
08048     // Start checking for end of file for embedded window..
08049     QMutexLocker locker(&timerIdLock);
08050     if (embedCheckTimerId)
08051         KillTimer(embedCheckTimerId);
08052     embedCheckTimerId = StartTimer(kEmbedCheckFrequency, __LINE__);
08053 
08054     bool embedding = ctx->IsEmbedding();
08055     ReturnPlayerLock(ctx);
08056     return embedding;
08057 }
08058 
08059 void TV::StopEmbedding(void)
08060 {
08061     PlayerContext *ctx = GetPlayerReadLock(-1, __FILE__, __LINE__);
08062     if (!ctx)
08063         return;
08064 
08065     if (ctx->IsEmbedding())
08066         ctx->StopEmbedding();
08067 
08068     // Undo any PIP hiding
08069     PlayerContext *mctx = GetPlayer(ctx, 0);
08070     for (uint i = 1; (mctx == ctx) && (i < player.size()); i++)
08071     {
08072         GetPlayer(ctx,i)->LockDeletePlayer(__FILE__, __LINE__);
08073         if (GetPlayer(ctx,i)->player)
08074             GetPlayer(ctx,i)->player->SetPIPVisible(true);
08075         GetPlayer(ctx,i)->UnlockDeletePlayer(__FILE__, __LINE__);
08076     }
08077 
08078     // Stop checking for end of file for embedded window..
08079     QMutexLocker locker(&timerIdLock);
08080     if (embedCheckTimerId)
08081         KillTimer(embedCheckTimerId);
08082     embedCheckTimerId = 0;
08083 
08084     ReturnPlayerLock(ctx);
08085 }
08086 
08087 void TV::DrawUnusedRects(void)
08088 {
08089     if (disableDrawUnusedRects)
08090         return;
08091 
08092     LOG(VB_PLAYBACK, LOG_INFO, LOC + "DrawUnusedRects() -- begin");
08093 
08094     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08095     for (uint i = 0; mctx && (i < player.size()); i++)
08096     {
08097         PlayerContext *ctx = GetPlayer(mctx, i);
08098         ctx->LockDeletePlayer(__FILE__, __LINE__);
08099         if (ctx->player)
08100             ctx->player->ExposeEvent();
08101         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08102     }
08103     ReturnPlayerLock(mctx);
08104 
08105     LOG(VB_PLAYBACK, LOG_INFO, LOC + "DrawUnusedRects() -- end");
08106 }
08107 
08108 vector<bool> TV::DoSetPauseState(PlayerContext *lctx, const vector<bool> &pause)
08109 {
08110     vector<bool> was_paused;
08111     vector<float> times;
08112     for (uint i = 0; lctx && i < player.size() && i < pause.size(); i++)
08113     {
08114         PlayerContext *actx = GetPlayer(lctx, i);
08115         if (actx)
08116             was_paused.push_back(ContextIsPaused(actx, __FILE__, __LINE__));
08117         float time = 0.0f;
08118         if (pause[i] ^ was_paused.back())
08119             time = DoTogglePauseStart(GetPlayer(lctx,i));
08120         times.push_back(time);
08121     }
08122 
08123     for (uint i = 0; lctx && i < player.size() && i < pause.size(); i++)
08124     {
08125         if (pause[i] ^ was_paused[i])
08126             DoTogglePauseFinish(GetPlayer(lctx,i), times[i], false);
08127     }
08128 
08129     return was_paused;
08130 }
08131 
08132 void TV::DoEditSchedule(int editType)
08133 {
08134     if ((editType == kScheduleProgramGuide  && !RunProgramGuidePtr) ||
08135         (editType == kScheduleProgramFinder && !RunProgramFinderPtr) ||
08136         (editType == kScheduledRecording    && !RunScheduleEditorPtr) ||
08137         (editType == kViewSchedule          && !RunViewScheduledPtr))
08138     {
08139         return;
08140     }
08141 
08142     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
08143 
08144     actx->LockPlayingInfo(__FILE__, __LINE__);
08145     if (!actx->playingInfo)
08146     {
08147         LOG(VB_GENERAL, LOG_ERR, LOC +
08148             "doEditSchedule(): no active ctx playingInfo.");
08149         actx->UnlockPlayingInfo(__FILE__, __LINE__);
08150         ReturnPlayerLock(actx);
08151         return;
08152     }
08153 
08154     // Collect channel info
08155     const ProgramInfo pginfo(*actx->playingInfo);
08156     uint    chanid  = pginfo.GetChanID();
08157     QString channum = pginfo.GetChanNum();
08158     actx->UnlockPlayingInfo(__FILE__, __LINE__);
08159 
08160     ClearOSD(actx);
08161 
08162     // Pause playback as needed...
08163     bool pause_active = true;
08164     bool isNearEnd = false;
08165     bool isLiveTV = StateIsLiveTV(GetState(actx));
08166     bool allowEmbedding = false;
08167     bool paused = false;
08168 
08169     {
08170         actx->LockDeletePlayer(__FILE__, __LINE__);
08171         pause_active = !actx->player || !actx->player->GetVideoOutput();
08172         if (actx->player)
08173         {
08174             paused = actx->player->IsPaused();
08175             if (actx->player->GetVideoOutput())
08176                 allowEmbedding =
08177                     actx->player->GetVideoOutput()->AllowPreviewEPG();
08178             if (!pause_active)
08179                 isNearEnd = actx->player->IsNearEnd();
08180         }
08181         actx->UnlockDeletePlayer(__FILE__, __LINE__);
08182     }
08183 
08184     pause_active |= kScheduledRecording == editType;
08185     pause_active |= kViewSchedule == editType;
08186     pause_active |= kScheduleProgramFinder == editType;
08187     pause_active |= !isLiveTV && (!db_continue_embedded || isNearEnd);
08188     pause_active |= paused;
08189     vector<bool> do_pause;
08190     do_pause.insert(do_pause.begin(), true, player.size());
08191     do_pause[find_player_index(actx)] = pause_active;
08192     LOG(VB_PLAYBACK, LOG_INFO, LOC +
08193         QString("Pausing player: %1").arg(pause_active));
08194 
08195     saved_pause = DoSetPauseState(actx, do_pause);
08196 
08197     // Resize window to the MythTV GUI size
08198     PlayerContext *mctx = GetPlayer(actx,0);
08199     mctx->LockDeletePlayer(__FILE__, __LINE__);
08200     if (mctx->player && mctx->player->GetVideoOutput())
08201         mctx->player->GetVideoOutput()->ResizeForGui();
08202     mctx->UnlockDeletePlayer(__FILE__, __LINE__);
08203     ReturnPlayerLock(actx);
08204     MythMainWindow *mwnd = GetMythMainWindow();
08205     if (!db_use_gui_size_for_tv || !db_use_fixed_size)
08206     {
08207         mwnd->setFixedSize(saved_gui_bounds.size());
08208         mwnd->setGeometry(saved_gui_bounds.left(), saved_gui_bounds.top(),
08209                           saved_gui_bounds.width(), saved_gui_bounds.height());
08210     }
08211 
08212     // Actually show the pop-up UI
08213     switch (editType)
08214     {
08215         case kScheduleProgramGuide:
08216         {
08217             isEmbedded = (isLiveTV && !pause_active && allowEmbedding);
08218             RunProgramGuidePtr(chanid, channum, this,
08219                                isEmbedded, true, channelGroupId);
08220             ignoreKeyPresses = true;
08221             break;
08222         }
08223         case kScheduleProgramFinder:
08224         {
08225             isEmbedded = (isLiveTV && !pause_active && allowEmbedding);
08226             RunProgramFinderPtr(this, isEmbedded, true);
08227             ignoreKeyPresses = true;
08228             break;
08229         }
08230         case kScheduledRecording:
08231         {
08232             RunScheduleEditorPtr(&pginfo, (void *)this);
08233             ignoreKeyPresses = true;
08234             break;
08235         }
08236         case kViewSchedule:
08237         {
08238             RunViewScheduledPtr((void *)this, !pause_active);
08239             ignoreKeyPresses = true;
08240             break;
08241         }
08242         case kPlaybackBox:
08243         {
08244             RunPlaybackBoxPtr((void *)this, !pause_active);
08245             ignoreKeyPresses = true;
08246             break;
08247         }
08248     }
08249 
08250     // If the video is paused, don't paint its unused rects & chromakey
08251     disableDrawUnusedRects = pause_active;
08252 
08253     // We are embedding in a mythui window so assuming no one
08254     // else has disabled painting show the MythUI window again.
08255     if (GetMythMainWindow() && weDisabledGUI)
08256     {
08257         GetMythMainWindow()->PopDrawDisabled();
08258         weDisabledGUI = false;
08259     }
08260 }
08261 
08262 void TV::EditSchedule(const PlayerContext *ctx, int editType)
08263 {
08264     // post the request so the guide will be created in the UI thread
08265     QString message = QString("START_EPG %1").arg(editType);
08266     MythEvent* me = new MythEvent(message);
08267     qApp->postEvent(this, me);
08268 }
08269 
08270 void TV::ChangeVolume(PlayerContext *ctx, bool up, int newvolume)
08271 {
08272     ctx->LockDeletePlayer(__FILE__, __LINE__);
08273     if (!ctx->player ||
08274         (ctx->player && !ctx->player->PlayerControlsVolume()))
08275     {
08276         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08277         return;
08278     }
08279 
08280     bool setabsolute = (newvolume >= 0 && newvolume <= 100);
08281 
08282     if (ctx->player->IsMuted() && (up || setabsolute))
08283         ToggleMute(ctx);
08284 
08285     uint curvol = setabsolute ?
08286                       ctx->player->SetVolume(newvolume) :
08287                       ctx->player->AdjustVolume((up) ? +2 : -2);
08288 
08289     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08290 
08291     if (!browsehelper->IsBrowsing())
08292     {
08293         UpdateOSDStatus(ctx, tr("Adjust Volume"), tr("Volume"),
08294                         QString::number(curvol),
08295                         kOSDFunctionalType_PictureAdjust, "%", curvol * 10,
08296                         kOSDTimeout_Med);
08297         SetUpdateOSDPosition(false);
08298 
08299         if (LCD *lcd = LCD::Get())
08300         {
08301             QString appName = tr("Video");
08302 
08303             if (StateIsLiveTV(GetState(ctx)))
08304                 appName = tr("TV");
08305 
08306             if (ctx->buffer && ctx->buffer->IsDVD())
08307                 appName = tr("DVD");
08308 
08309             lcd->switchToVolume(appName);
08310             lcd->setVolumeLevel((float)curvol / 100);
08311 
08312             QMutexLocker locker(&timerIdLock);
08313             if (lcdVolumeTimerId)
08314                 KillTimer(lcdVolumeTimerId);
08315 
08316             lcdVolumeTimerId = StartTimer(2000, __LINE__);
08317         }
08318     }
08319 }
08320 
08321 void TV::ToggleTimeStretch(PlayerContext *ctx)
08322 {
08323     if (ctx->ts_normal == 1.0f)
08324     {
08325         ctx->ts_normal = ctx->ts_alt;
08326     }
08327     else
08328     {
08329         ctx->ts_alt = ctx->ts_normal;
08330         ctx->ts_normal = 1.0f;
08331     }
08332     ChangeTimeStretch(ctx, 0, false);
08333 }
08334 
08335 void TV::ChangeTimeStretch(PlayerContext *ctx, int dir, bool allowEdit)
08336 {
08337     const float kTimeStretchMin = 0.5;
08338     const float kTimeStretchMax = 2.0;
08339     float new_ts_normal = ctx->ts_normal + 0.05*dir;
08340     stretchAdjustment = allowEdit;
08341 
08342     if (new_ts_normal > kTimeStretchMax &&
08343         ctx->ts_normal < kTimeStretchMax)
08344     {
08345         new_ts_normal = kTimeStretchMax;
08346     }
08347     else if (new_ts_normal < kTimeStretchMin &&
08348              ctx->ts_normal > kTimeStretchMin)
08349     {
08350         new_ts_normal = kTimeStretchMin;
08351     }
08352 
08353     if (new_ts_normal > kTimeStretchMax ||
08354         new_ts_normal < kTimeStretchMin)
08355     {
08356         return;
08357     }
08358 
08359     ctx->ts_normal = new_ts_normal;
08360 
08361     ctx->LockDeletePlayer(__FILE__, __LINE__);
08362     if (ctx->player && !ctx->player->IsPaused())
08363             ctx->player->Play(ctx->ts_normal, true);
08364     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08365 
08366     if (!browsehelper->IsBrowsing())
08367     {
08368         if (!allowEdit)
08369         {
08370             UpdateOSDSeekMessage(ctx, ctx->GetPlayMessage(), kOSDTimeout_Med);
08371         }
08372         else
08373         {
08374             UpdateOSDStatus(ctx, tr("Adjust Time Stretch"), tr("Time Stretch"),
08375                             QString::number(ctx->ts_normal),
08376                             kOSDFunctionalType_TimeStretchAdjust, "X",
08377                             (int)(ctx->ts_normal*(1000/kTimeStretchMax)),
08378                             kOSDTimeout_Med);
08379             SetUpdateOSDPosition(false);
08380         }
08381     }
08382 
08383     SetSpeedChangeTimer(0, __LINE__);
08384 }
08385 
08386 void TV::EnableUpmix(PlayerContext *ctx, bool enable, bool toggle)
08387 {
08388     if (!ctx->player || !ctx->player->HasAudioOut())
08389         return;
08390     QString text;
08391 
08392     bool enabled = false;
08393 
08394     ctx->LockDeletePlayer(__FILE__, __LINE__);
08395     if (toggle)
08396         enabled = ctx->player->GetAudio()->EnableUpmix(false, true);
08397     else
08398         enabled = ctx->player->GetAudio()->EnableUpmix(enable);
08399     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08400 
08401     if (!browsehelper->IsBrowsing())
08402         SetOSDMessage(ctx, enabled ? tr("Upmixer On") : tr("Upmixer Off"));
08403 }
08404 
08405 void TV::ChangeSubtitleZoom(PlayerContext *ctx, int dir)
08406 {
08407     ctx->LockDeletePlayer(__FILE__, __LINE__);
08408     if (!ctx->player)
08409     {
08410         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08411         return;
08412     }
08413 
08414     OSD *osd = GetOSDLock(ctx);
08415     SubtitleScreen *subs = NULL;
08416     if (osd)
08417         subs = osd->InitSubtitles();
08418     ReturnOSDLock(ctx, osd);
08419     subtitleZoomAdjustment = true;
08420     bool showing = ctx->player->GetCaptionsEnabled();
08421     int newval = (subs ? subs->GetZoom() : 100) + dir;
08422     newval = max(50, newval);
08423     newval = min(200, newval);
08424     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08425 
08426     if (showing && !browsehelper->IsBrowsing())
08427     {
08428         UpdateOSDStatus(ctx, tr("Adjust Subtitle Zoom"), tr("Subtitle Zoom"),
08429                         QString::number(newval),
08430                         kOSDFunctionalType_SubtitleZoomAdjust,
08431                         "%", newval * 1000 / 200, kOSDTimeout_Long);
08432         SetUpdateOSDPosition(false);
08433         if (subs)
08434             subs->SetZoom(newval);
08435     }
08436 }
08437 
08438 // dir in 10ms jumps
08439 void TV::ChangeAudioSync(PlayerContext *ctx, int dir, int newsync)
08440 {
08441     long long newval;
08442 
08443     ctx->LockDeletePlayer(__FILE__, __LINE__);
08444     if (!ctx->player)
08445     {
08446         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08447         return;
08448     }
08449 
08450     audiosyncAdjustment = true;
08451     newval = ctx->player->AdjustAudioTimecodeOffset(dir * 10, newsync);
08452     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08453 
08454     if (!browsehelper->IsBrowsing())
08455     {
08456         int val = (int)newval;
08457         UpdateOSDStatus(ctx, tr("Adjust Audio Sync"), tr("Audio Sync"),
08458                         QString::number(val),
08459                         kOSDFunctionalType_AudioSyncAdjust,
08460                         "ms", (val/2) + 500, kOSDTimeout_Med);
08461         SetUpdateOSDPosition(false);
08462     }
08463 }
08464 
08465 void TV::ToggleMute(PlayerContext *ctx, const bool muteIndividualChannels)
08466 {
08467     ctx->LockDeletePlayer(__FILE__, __LINE__);
08468     if (!ctx->player || !ctx->player->HasAudioOut() ||
08469         !ctx->player->PlayerControlsVolume())
08470     {
08471         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08472         return;
08473     }
08474 
08475     MuteState mute_status;
08476 
08477     if (!muteIndividualChannels)
08478     {
08479         ctx->player->SetMuted(!ctx->player->IsMuted());
08480         mute_status = (ctx->player->IsMuted()) ? kMuteAll : kMuteOff;
08481     }
08482     else
08483     {
08484         mute_status = ctx->player->IncrMuteState();
08485     }
08486     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08487 
08488     QString text;
08489 
08490     switch (mute_status)
08491     {
08492         case kMuteOff:   text = tr("Mute Off"); break;
08493         case kMuteAll:   text = tr("Mute On"); break;
08494         case kMuteLeft:  text = tr("Left Channel Muted"); break;
08495         case kMuteRight: text = tr("Right Channel Muted"); break;
08496     }
08497 
08498     SetOSDMessage(ctx, text);
08499 }
08500 
08501 void TV::ToggleSleepTimer(const PlayerContext *ctx)
08502 {
08503     QString text;
08504 
08505     // increment sleep index, cycle through
08506     if (++sleep_index == sleep_times.size())
08507         sleep_index = 0;
08508 
08509     // set sleep timer to next sleep_index timeout
08510     if (sleepTimerId)
08511     {
08512         KillTimer(sleepTimerId);
08513         sleepTimerId = 0;
08514         sleepTimerTimeout = 0;
08515     }
08516 
08517     if (sleep_times[sleep_index].seconds != 0)
08518     {
08519         sleepTimerTimeout = sleep_times[sleep_index].seconds * 1000;
08520         sleepTimerId = StartTimer(sleepTimerTimeout, __LINE__);
08521     }
08522 
08523     text = tr("Sleep ") + " " + sleep_times[sleep_index].dispString;
08524 
08525     if (!browsehelper->IsBrowsing())
08526         SetOSDMessage(ctx, text);
08527 }
08528 
08529 void TV::ShowOSDSleep(void)
08530 {
08531     KillTimer(sleepTimerId);
08532     sleepTimerId = 0;
08533 
08534     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08535     OSD *osd = GetOSDLock(mctx);
08536     if (osd)
08537     {
08538         QString message = QObject::tr(
08539             "MythTV was set to sleep after %1 minutes and "
08540             "will exit in %d seconds.\n"
08541             "Do you wish to continue watching?")
08542             .arg(sleepTimerTimeout * (1.0f/60000.0f));
08543 
08544         osd->DialogShow(OSD_DLG_SLEEP, message, kSleepTimerDialogTimeout);
08545         osd->DialogAddButton(tr("Yes"), "DIALOG_SLEEP_YES_0");
08546         osd->DialogAddButton(tr("No"),  "DIALOG_SLEEP_NO_0");
08547     }
08548     ReturnOSDLock(mctx, osd);
08549     ReturnPlayerLock(mctx);
08550 
08551     sleepDialogTimerId = StartTimer(kSleepTimerDialogTimeout, __LINE__);
08552 }
08553 
08554 void TV::HandleOSDSleep(PlayerContext *ctx, QString action)
08555 {
08556     if (!DialogIsVisible(ctx, OSD_DLG_SLEEP))
08557         return;
08558 
08559     if (action == "YES")
08560     {
08561         if (sleepDialogTimerId)
08562         {
08563             KillTimer(sleepDialogTimerId);
08564             sleepDialogTimerId = 0;
08565         }
08566         sleepTimerId = StartTimer(sleepTimerTimeout * 1000, __LINE__);
08567     }
08568     else
08569     {
08570         LOG(VB_GENERAL, LOG_INFO, LOC + "No longer watching TV, exiting");
08571         SetExitPlayer(true, true);
08572     }
08573 }
08574 
08575 void TV::SleepDialogTimeout(void)
08576 {
08577     KillTimer(sleepDialogTimerId);
08578     sleepDialogTimerId = 0;
08579 
08580     LOG(VB_GENERAL, LOG_INFO, LOC + "Sleep timeout reached, exiting player.");
08581 
08582     SetExitPlayer(true, true);
08583 }
08584 
08593 void TV::ShowOSDIdle(void)
08594 {
08595     KillTimer(idleTimerId);
08596     idleTimerId = 0;
08597 
08598     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08599     OSD *osd = GetOSDLock(mctx);
08600     if (osd)
08601     {
08602         QString message = QObject::tr(
08603             "MythTV has been idle for %1 minutes and "
08604             "will exit in %d seconds. Are you still watching?")
08605             .arg(db_idle_timeout * (1.0f/60000.0f));
08606 
08607         osd->DialogShow(OSD_DLG_IDLE, message, kIdleTimerDialogTimeout);
08608         osd->DialogAddButton(tr("Yes"), "DIALOG_IDLE_YES_0");
08609         osd->DialogAddButton(tr("No"),  "DIALOG_IDLE_NO_0");
08610     }
08611     ReturnOSDLock(mctx, osd);
08612     ReturnPlayerLock(mctx);
08613 
08614     idleDialogTimerId = StartTimer(kIdleTimerDialogTimeout, __LINE__);
08615 }
08616 
08617 void TV::HandleOSDIdle(PlayerContext *ctx, QString action)
08618 {
08619     if (!DialogIsVisible(ctx, OSD_DLG_IDLE))
08620         return;
08621 
08622     if (action == "YES")
08623     {
08624         if (idleDialogTimerId)
08625         {
08626             KillTimer(idleDialogTimerId);
08627             idleDialogTimerId = 0;
08628         }
08629         if (idleTimerId)
08630             KillTimer(idleTimerId);
08631         idleTimerId = StartTimer(db_idle_timeout, __LINE__);
08632     }
08633     else
08634     {
08635         LOG(VB_GENERAL, LOG_INFO, LOC + "No longer watching LiveTV, exiting");
08636         SetExitPlayer(true, true);
08637     }
08638 }
08639 
08640 void TV::IdleDialogTimeout(void)
08641 {
08642     KillTimer(idleDialogTimerId);
08643     idleDialogTimerId = 0;
08644 
08645     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08646     if (StateIsLiveTV(mctx->GetState()))
08647     {
08648         LOG(VB_GENERAL, LOG_INFO, LOC + "Idle timeout reached, leaving LiveTV");
08649         SetExitPlayer(true, true);
08650     }
08651     ReturnPlayerLock(mctx);
08652 }
08653 
08654 void TV::ToggleAspectOverride(PlayerContext *ctx, AspectOverrideMode aspectMode)
08655 {
08656     ctx->LockDeletePlayer(__FILE__, __LINE__);
08657     if (!ctx->player)
08658     {
08659         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08660         return;
08661     }
08662     ctx->player->ToggleAspectOverride(aspectMode);
08663     QString text = toString(ctx->player->GetAspectOverride());
08664     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08665 
08666     SetOSDMessage(ctx, text);
08667 }
08668 
08669 void TV::ToggleAdjustFill(PlayerContext *ctx, AdjustFillMode adjustfillMode)
08670 {
08671     if (ctx != GetPlayer(ctx,0) || ctx->IsPBP())
08672         return;
08673 
08674     ctx->LockDeletePlayer(__FILE__, __LINE__);
08675     if (!ctx->player)
08676     {
08677         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08678         return;
08679     }
08680     ctx->player->ToggleAdjustFill(adjustfillMode);
08681     QString text = toString(ctx->player->GetAdjustFill());
08682     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08683 
08684     SetOSDMessage(ctx, text);
08685 }
08686 
08687 void TV::PauseAudioUntilBuffered(PlayerContext *ctx)
08688 {
08689     if (!ctx)
08690         return;
08691 
08692     ctx->LockDeletePlayer(__FILE__, __LINE__);
08693     if (ctx->player)
08694         ctx->player->GetAudio()->PauseAudioUntilBuffered();
08695     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08696 }
08697 
08699 void TV::customEvent(QEvent *e)
08700 {
08701     if (e->type() == MythEvent::kUpdateTvProgressEventType && myWindow)
08702     {
08703         myWindow->UpdateProgress();
08704         return;
08705     }
08706 
08707     if (e->type() == MythEvent::MythUserMessage)
08708     {
08709         MythEvent *me = reinterpret_cast<MythEvent*>(e);
08710         QString message = me->Message();
08711 
08712         if (message.isEmpty())
08713             return;
08714 
08715         uint timeout = 0;
08716         if (me->ExtraDataCount() == 1)
08717         {
08718             uint t = me->ExtraData(0).toUInt();
08719             if (t > 0 && t < 1000)
08720                 timeout = t * 1000;
08721         }
08722 
08723         if (timeout > 0)
08724             message += " (%d)";
08725 
08726         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08727         OSD *osd = GetOSDLock(mctx);
08728         if (osd)
08729             osd->DialogShow(OSD_DLG_CONFIRM, message, timeout);
08730         ReturnOSDLock(mctx, osd);
08731         ReturnPlayerLock(mctx);
08732 
08733         return;
08734     }
08735 
08736     if (e->type() == MythEvent::kUpdateBrowseInfoEventType)
08737     {
08738         UpdateBrowseInfoEvent *b =
08739             reinterpret_cast<UpdateBrowseInfoEvent*>(e);
08740         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08741         OSD *osd = GetOSDLock(mctx);
08742         if (osd)
08743         {
08744             osd->SetText("browse_info", b->im, kOSDTimeout_None);
08745             osd->SetExpiry("browse_info", kOSDTimeout_None);
08746         }
08747         ReturnOSDLock(mctx, osd);
08748         ReturnPlayerLock(mctx);
08749         return;
08750     }
08751 
08752     if (e->type() == DialogCompletionEvent::kEventType)
08753     {
08754         DialogCompletionEvent *dce =
08755             reinterpret_cast<DialogCompletionEvent*>(e);
08756         OSDDialogEvent(dce->GetResult(), dce->GetResultText(),
08757                        dce->GetData().toString());
08758         return;
08759     }
08760 
08761     if (e->type() == OSDHideEvent::kEventType)
08762     {
08763         OSDHideEvent *ce = reinterpret_cast<OSDHideEvent*>(e);
08764         HandleOSDClosed(ce->GetFunctionalType());
08765         return;
08766     }
08767 
08768     if (e->type() != MythEvent::MythEventMessage)
08769         return;
08770 
08771     uint cardnum   = 0;
08772     MythEvent *me = reinterpret_cast<MythEvent*>(e);
08773     QString message = me->Message();
08774 
08775     // TODO Go through these and make sure they make sense...
08776     QStringList tokens = message.split(" ", QString::SkipEmptyParts);
08777 
08778     if (me->ExtraDataCount() == 1)
08779     {
08780         PlayerContext *ctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08781         int value = me->ExtraData(0).toInt();
08782         if (message == ACTION_SETVOLUME)
08783             ChangeVolume(ctx, false, value);
08784         else if (message == ACTION_SETAUDIOSYNC)
08785             ChangeAudioSync(ctx, 0, value);
08786         else if (message == ACTION_SETBRIGHTNESS)
08787             DoChangePictureAttribute(ctx, kAdjustingPicture_Playback,
08788                                      kPictureAttribute_Brightness,
08789                                      false, value);
08790         else if (message == ACTION_SETCONTRAST)
08791             DoChangePictureAttribute(ctx, kAdjustingPicture_Playback,
08792                                      kPictureAttribute_Contrast,
08793                                      false, value);
08794         else if (message == ACTION_SETCOLOUR)
08795             DoChangePictureAttribute(ctx, kAdjustingPicture_Playback,
08796                                      kPictureAttribute_Colour,
08797                                      false, value);
08798         else if (message == ACTION_SETHUE)
08799             DoChangePictureAttribute(ctx, kAdjustingPicture_Playback,
08800                                      kPictureAttribute_Hue,
08801                                      false, value);
08802         else if (message == ACTION_JUMPCHAPTER)
08803             DoJumpChapter(ctx, value);
08804         else if (message == ACTION_SWITCHTITLE)
08805             DoSwitchTitle(ctx, value - 1);
08806         else if (message == ACTION_SWITCHANGLE)
08807             DoSwitchAngle(ctx, value);
08808         else if (message == ACTION_SEEKABSOLUTE)
08809             DoSeekAbsolute(ctx, value, /*honorCutlist*/true);
08810         ReturnPlayerLock(ctx);
08811     }
08812 
08813     if (message == ACTION_SCREENSHOT)
08814     {
08815         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08816         int width = 0;
08817         int height = 0;
08818         QString filename;
08819 
08820         if (me->ExtraDataCount() >= 2)
08821         {
08822             width  = me->ExtraData(0).toInt();
08823             height = me->ExtraData(1).toInt();
08824 
08825             if (me->ExtraDataCount() == 3)
08826                 filename = me->ExtraData(2);
08827         }
08828         if (mctx && mctx->player &&
08829             mctx->player->GetScreenShot(width, height, filename))
08830         {
08831         }
08832         else
08833         {
08834             GetMythMainWindow()->ScreenShot(width, height, filename);
08835         }
08836         ReturnPlayerLock(mctx);
08837     }
08838     else if (message == ACTION_GETSTATUS)
08839     {
08840         GetStatus();
08841     }
08842     else if (message.left(14) == "DONE_RECORDING")
08843     {
08844         int seconds = 0;
08845         //long long frames = 0;
08846         if (tokens.size() >= 4)
08847         {
08848             cardnum = tokens[1].toUInt();
08849             seconds = tokens[2].toInt();
08850             //frames = tokens[3].toLongLong();
08851         }
08852 
08853         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08854         for (uint i = 0; mctx && (i < player.size()); i++)
08855         {
08856             PlayerContext *ctx = GetPlayer(mctx, i);
08857             if (ctx->GetState() == kState_WatchingRecording)
08858             {
08859                 if (ctx->recorder && (cardnum == ctx->GetCardID()))
08860                 {
08861                     ctx->LockDeletePlayer(__FILE__, __LINE__);
08862                     if (ctx->player)
08863                     {
08864                         ctx->player->SetWatchingRecording(false);
08865                         ctx->player->SetLength(seconds);
08866                     }
08867                     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08868 
08869                     ctx->ChangeState(kState_WatchingPreRecorded);
08870                     ScheduleStateChange(ctx);
08871                 }
08872             }
08873             else if (StateIsLiveTV(ctx->GetState()))
08874             {
08875                 if (ctx->recorder && cardnum == ctx->GetCardID() &&
08876                     ctx->tvchain && ctx->tvchain->HasNext())
08877                 {
08878                     ctx->LockDeletePlayer(__FILE__, __LINE__);
08879                     if (ctx->player)
08880                     {
08881                         ctx->player->SetWatchingRecording(false);
08882                         ctx->player->SetLength(seconds);
08883                     }
08884                     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
08885                 }
08886             }
08887         }
08888         ReturnPlayerLock(mctx);
08889     }
08890 
08891     if (message.left(14) == "ASK_RECORDING ")
08892     {
08893         int timeuntil = 0, hasrec = 0, haslater = 0;
08894         if (tokens.size() >= 5)
08895         {
08896             cardnum   = tokens[1].toUInt();
08897             timeuntil = tokens[2].toInt();
08898             hasrec    = tokens[3].toInt();
08899             haslater  = tokens[4].toInt();
08900         }
08901         LOG(VB_GENERAL, LOG_INFO,
08902             LOC + message + QString(" hasrec: %1 haslater: %2")
08903                 .arg(hasrec).arg(haslater));
08904 
08905         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
08906         if (mctx->recorder && cardnum == mctx->GetCardID())
08907         {
08908             AskAllowRecording(mctx, me->ExtraDataList(),
08909                               timeuntil, hasrec, haslater);
08910         }
08911 
08912         for (uint i = 1; i < player.size(); i++)
08913         {
08914             PlayerContext *ctx = GetPlayer(mctx, i);
08915             if (ctx->recorder && ctx->GetCardID() == cardnum)
08916             {
08917                 LOG(VB_GENERAL, LOG_INFO, LOC + "Disabling PxP for recording");
08918                 QString type = ctx->IsPIP() ?
08919                     tr("PiP", "Picture-in-Picture") :
08920                     tr("PbP", "Picture-by-Picture");
08921                 StopStuff(mctx, ctx, true, true, true);
08922                 SetOSDMessage(mctx, tr("Disabling %1 for recording").arg(type));
08923             }
08924         }
08925         ReturnPlayerLock(mctx);
08926     }
08927 
08928     if (message.left(11) == "QUIT_LIVETV")
08929     {
08930         cardnum = (tokens.size() >= 2) ? tokens[1].toUInt() : 0;
08931 
08932         PlayerContext *mctx = GetPlayerReadLock(-1, __FILE__, __LINE__);
08933         int match = -1;
08934         for (uint i = 0; mctx && (i < player.size()); i++)
08935         {
08936             PlayerContext *ctx = GetPlayer(mctx, i);
08937             match = (ctx->GetCardID() == cardnum) ? i : match;
08938         }
08939 
08940         if (match >= 0 && GetPlayer(mctx, match)->recorder)
08941         {
08942             if (0 == match)
08943             {
08944                 for (uint i = 1; mctx && (i < player.size()); i++)
08945                 {
08946                     PlayerContext *ctx = GetPlayer(mctx, i);
08947                     if (ctx->recorder && (ctx->GetCardID() == cardnum))
08948                     {
08949                         LOG(VB_GENERAL, LOG_INFO, LOC +
08950                             "Disabling PiP for QUIT_LIVETV");
08951                         StopStuff(mctx, ctx, true, true, true);
08952                     }
08953                 }
08954                 SetLastProgram(NULL);
08955                 jumpToProgram = true;
08956                 SetExitPlayer(true, false);
08957             }
08958             else
08959             {
08960                 PlayerContext *ctx = GetPlayer(mctx, match);
08961                 StopStuff(mctx, ctx, true, true, true);
08962             }
08963         }
08964         ReturnPlayerLock(mctx);
08965     }
08966 
08967     if (message.left(12) == "LIVETV_WATCH")
08968     {
08969         int watch = 0;
08970         if (tokens.size() >= 3)
08971         {
08972             cardnum = tokens[1].toUInt();
08973             watch   = tokens[2].toInt();
08974         }
08975 
08976         PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
08977         int match = -1;
08978         for (uint i = 0; mctx && (i < player.size()); i++)
08979         {
08980             PlayerContext *ctx = GetPlayer(mctx, i);
08981             match = (ctx->GetCardID() == cardnum) ? i : match;
08982         }
08983 
08984         if (match >= 0)
08985         {
08986             if (watch)
08987             {
08988                 ProgramInfo pi(me->ExtraDataList());
08989                 if (pi.HasPathname() || pi.GetChanID())
08990                 {
08991                     PlayerContext *ctx = GetPlayer(mctx, match);
08992                     ctx->SetPseudoLiveTV(&pi, kPseudoChangeChannel);
08993 
08994                     QMutexLocker locker(&timerIdLock);
08995                     if (!pseudoChangeChanTimerId)
08996                         pseudoChangeChanTimerId = StartTimer(0, __LINE__);
08997                 }
08998             }
08999             else
09000             {
09001                 PlayerContext *ctx = GetPlayer(mctx, match);
09002                 ctx->SetPseudoLiveTV(NULL, kPseudoNormalLiveTV);
09003             }
09004         }
09005         ReturnPlayerLock(mctx);
09006     }
09007 
09008     if (message.left(12) == "LIVETV_CHAIN")
09009     {
09010         QString id = QString::null;
09011         if ((tokens.size() >= 2) && tokens[1] == "UPDATE")
09012             id = tokens[2];
09013 
09014         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
09015         for (uint i = 0; mctx && (i < player.size()); i++)
09016         {
09017             PlayerContext *ctx = GetPlayer(mctx, i);
09018             if (ctx->tvchain && ctx->tvchain->GetID() == id)
09019             {
09020                 QMutexLocker locker(&timerIdLock);
09021                 tvchainUpdateTimerId[StartTimer(1, __LINE__)] = ctx;
09022                 break;
09023             }
09024         }
09025         ReturnPlayerLock(mctx);
09026     }
09027 
09028     if (message.left(12) == "EXIT_TO_MENU")
09029     {
09030         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
09031         for (uint i = 0; mctx && (i < player.size()); i++)
09032         {
09033             PlayerContext *ctx = GetPlayer(mctx, i);
09034             PrepareToExitPlayer(ctx, __LINE__);
09035         }
09036 
09037         SetExitPlayer(true, true);
09038         if (mctx && mctx->player)
09039             mctx->player->DisableEdit(-1);
09040         ReturnPlayerLock(mctx);
09041     }
09042 
09043     if (message.left(6) == "SIGNAL")
09044     {
09045         cardnum = (tokens.size() >= 2) ? tokens[1].toUInt() : 0;
09046         QStringList signalList = me->ExtraDataList();
09047 
09048         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
09049         OSD *osd = GetOSDLock(mctx);
09050         if (osd && !osd->IsWindowVisible(OSD_WIN_INTERACT))
09051         {
09052             for (uint i = 0; mctx && (i < player.size()); i++)
09053             {
09054                 PlayerContext *ctx = GetPlayer(mctx, i);
09055                 bool tc = ctx->recorder && (ctx->GetCardID() == cardnum);
09056                 if (tc && !signalList.empty())
09057                 {
09058                     UpdateOSDSignal(ctx, signalList);
09059                     UpdateOSDTimeoutMessage(ctx);
09060                 }
09061             }
09062         }
09063         ReturnOSDLock(mctx, osd);
09064         ReturnPlayerLock(mctx);
09065     }
09066 
09067     if (message.left(15) == "NETWORK_CONTROL")
09068     {
09069         if ((tokens.size() >= 2) &&
09070             (tokens[1] != "ANSWER") && (tokens[1] != "RESPONSE"))
09071         {
09072             QStringList tokens = message.split(" ", QString::SkipEmptyParts);
09073             if ((tokens.size() >= 2) &&
09074                 (tokens[1] != "ANSWER") && (tokens[1] != "RESPONSE"))
09075             {
09076                 QMutexLocker locker(&timerIdLock);
09077                 message.detach();
09078                 networkControlCommands.enqueue(message);
09079                 if (!networkControlTimerId)
09080                     networkControlTimerId = StartTimer(1, __LINE__);
09081             }
09082         }
09083     }
09084 
09085     if (message.left(9) == "START_EPG")
09086     {
09087         int editType = tokens[1].toInt();
09088         DoEditSchedule(editType);
09089     }
09090 
09091     if (message.left(11) == "EPG_EXITING" ||
09092         message.left(18) == "PROGFINDER_EXITING" ||
09093         message.left(21) == "VIEWSCHEDULED_EXITING" ||
09094         message.left(19)   == "PLAYBACKBOX_EXITING" ||
09095         message.left(22) == "SCHEDULEEDITOR_EXITING")
09096     {
09097         // Resize the window back to the MythTV Player size
09098         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
09099         PlayerContext *mctx;
09100         MythMainWindow *mwnd = GetMythMainWindow();
09101 
09102         StopEmbedding();
09103         MythPainter *painter = GetMythPainter();
09104         if (painter)
09105             painter->FreeResources();
09106 
09107         mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
09108         mctx->LockDeletePlayer(__FILE__, __LINE__);
09109         if (mctx->player && mctx->player->GetVideoOutput())
09110             mctx->player->GetVideoOutput()->ResizeForVideo();
09111         mctx->UnlockDeletePlayer(__FILE__, __LINE__);
09112         ReturnPlayerLock(mctx);
09113 
09114         if (!db_use_gui_size_for_tv || !db_use_fixed_size)
09115         {
09116             mwnd->setMinimumSize(QSize(16, 16));
09117             mwnd->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
09118             mwnd->setGeometry(player_bounds.left(), player_bounds.top(),
09119                               player_bounds.width(), player_bounds.height());
09120         }
09121 
09122         DoSetPauseState(actx, saved_pause); // Restore pause states
09123         disableDrawUnusedRects = false;
09124 
09125         qApp->processEvents();
09126 
09127         if (!weDisabledGUI)
09128         {
09129             weDisabledGUI = true;
09130             GetMythMainWindow()->PushDrawDisabled();
09131             DrawUnusedRects();
09132         }
09133 
09134         isEmbedded = false;
09135         ignoreKeyPresses = false;
09136 
09137         if (message.left(19) == "PLAYBACKBOX_EXITING")
09138         {
09139             ProgramInfo pginfo(me->ExtraDataList());
09140             if (pginfo.HasPathname() || pginfo.GetChanID())
09141                 PrepToSwitchToRecordedProgram(actx, pginfo);
09142         }
09143 
09144         ReturnPlayerLock(actx);
09145 
09146     }
09147 
09148     if (message.left(14) == "COMMFLAG_START" && (tokens.size() >= 2))
09149     {
09150         uint evchanid = 0;
09151         QDateTime evrecstartts;
09152         ProgramInfo::ExtractKey(tokens[1], evchanid, evrecstartts);
09153 
09154         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
09155         for (uint i = 0; mctx && (i < player.size()); i++)
09156         {
09157             PlayerContext *ctx = GetPlayer(mctx, i);
09158             ctx->LockPlayingInfo(__FILE__, __LINE__);
09159             bool doit =
09160                 ((ctx->playingInfo) &&
09161                  (ctx->playingInfo->GetChanID()             == evchanid) &&
09162                  (ctx->playingInfo->GetRecordingStartTime() == evrecstartts));
09163             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
09164 
09165             if (doit)
09166             {
09167                 QString msg = "COMMFLAG_REQUEST ";
09168                 msg += ProgramInfo::MakeUniqueKey(evchanid, evrecstartts);
09169                 gCoreContext->SendMessage(msg);
09170             }
09171         }
09172         ReturnPlayerLock(mctx);
09173     }
09174 
09175     if (message.left(15) == "COMMFLAG_UPDATE" && (tokens.size() >= 3))
09176     {
09177         uint evchanid = 0;
09178         QDateTime evrecstartts;
09179         ProgramInfo::ExtractKey(tokens[1], evchanid, evrecstartts);
09180 
09181         PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
09182         for (uint i = 0; mctx && evchanid && (i < player.size()); i++)
09183         {
09184             PlayerContext *ctx = GetPlayer(mctx, i);
09185             ctx->LockPlayingInfo(__FILE__, __LINE__);
09186             bool doit =
09187                 ((ctx->playingInfo) &&
09188                  (ctx->playingInfo->GetChanID()             == evchanid) &&
09189                  (ctx->playingInfo->GetRecordingStartTime() == evrecstartts));
09190             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
09191 
09192             if (doit)
09193             {
09194                 frm_dir_map_t newMap;
09195                 QStringList mark;
09196                 QStringList marks =
09197                     tokens[2].split(",", QString::SkipEmptyParts);
09198                 for (uint i = 0; i < (uint)marks.size(); i++)
09199                 {
09200                     mark = marks[i].split(":", QString::SkipEmptyParts);
09201                     if (marks.size() >= 2)
09202                     {
09203                         newMap[mark[0].toLongLong()] =
09204                             (MarkTypes) mark[1].toInt();
09205                     }
09206                 }
09207                 ctx->LockDeletePlayer(__FILE__, __LINE__);
09208                 if (ctx->player)
09209                     ctx->player->SetCommBreakMap(newMap);
09210                 ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09211             }
09212         }
09213         ReturnPlayerLock(mctx);
09214     }
09215 }
09216 
09217 void TV::ToggleRecord(PlayerContext *ctx)
09218 {
09219     BrowseInfo bi = browsehelper->GetBrowsedInfo();
09220     if (bi.m_chanid)
09221     {
09222         InfoMap infoMap;
09223         QDateTime startts = QDateTime::fromString(
09224             bi.m_starttime, Qt::ISODate);
09225 
09226         RecordingInfo::LoadStatus status;
09227         RecordingInfo recinfo(bi.m_chanid, startts, false, 0, &status);
09228         if (RecordingInfo::kFoundProgram == status)
09229             recinfo.ToggleRecord();
09230         recinfo.ToMap(infoMap);
09231         infoMap["iconpath"] = ChannelUtil::GetIcon(recinfo.GetChanID());
09232         if ((recinfo.IsVideoFile() || recinfo.IsVideoDVD() ||
09233             recinfo.IsVideoBD()) && recinfo.GetPathname() != recinfo.GetBasename())
09234         {
09235             infoMap["coverartpath"] = VideoMetaDataUtil::GetArtPath(
09236                 recinfo.GetPathname(), "Coverart");
09237             infoMap["fanartpath"] = VideoMetaDataUtil::GetArtPath(
09238                 recinfo.GetPathname(), "Fanart");
09239             infoMap["bannerpath"] = VideoMetaDataUtil::GetArtPath(
09240                 recinfo.GetPathname(), "Banners");
09241             infoMap["screenshotpath"] = VideoMetaDataUtil::GetArtPath(
09242                 recinfo.GetPathname(), "Screenshots");
09243         }
09244 
09245         OSD *osd = GetOSDLock(ctx);
09246         if (osd)
09247         {
09248             osd->SetText("browse_info", infoMap, kOSDTimeout_Med);
09249             QHash<QString,QString> map;
09250             map.insert("message_text", tr("Record"));
09251             osd->SetText("osd_message", map, kOSDTimeout_Med);
09252         }
09253         ReturnOSDLock(ctx, osd);
09254         return;
09255     }
09256 
09257     ctx->LockPlayingInfo(__FILE__, __LINE__);
09258     if (!ctx->playingInfo)
09259     {
09260         LOG(VB_GENERAL, LOG_INFO, LOC + "Unknown recording during live tv.");
09261         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
09262         return;
09263     }
09264 
09265     QString cmdmsg("");
09266     if (ctx->playingInfo->QueryAutoExpire() == kLiveTVAutoExpire)
09267     {
09268         RecordingInfo recInfo(*ctx->playingInfo);
09269         recInfo.SaveAutoExpire((AutoExpireType)db_autoexpire_default);
09270         recInfo.ApplyRecordRecGroupChange("Default");
09271         *ctx->playingInfo = recInfo;
09272 
09273         cmdmsg = tr("Record");
09274         ctx->SetPseudoLiveTV(ctx->playingInfo, kPseudoRecording);
09275         ctx->recorder->SetLiveRecording(true);
09276         LOG(VB_RECORD, LOG_INFO, LOC + "Toggling Record on");
09277     }
09278     else
09279     {
09280         RecordingInfo recInfo(*ctx->playingInfo);
09281         recInfo.SaveAutoExpire(kLiveTVAutoExpire);
09282         recInfo.ApplyRecordRecGroupChange("LiveTV");
09283         *ctx->playingInfo = recInfo;
09284 
09285         cmdmsg = tr("Cancel Record");
09286         ctx->SetPseudoLiveTV(ctx->playingInfo, kPseudoNormalLiveTV);
09287         ctx->recorder->SetLiveRecording(false);
09288         LOG(VB_RECORD, LOG_INFO, LOC + "Toggling Record off");
09289     }
09290 
09291     QString msg = cmdmsg + " \"" + ctx->playingInfo->GetTitle() + "\"";
09292 
09293     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
09294 
09295     SetOSDMessage(ctx, msg);
09296 }
09297 
09298 void TV::HandleOSDClosed(int osdType)
09299 {
09300     switch (osdType)
09301     {
09302         case kOSDFunctionalType_PictureAdjust:
09303             adjustingPicture = kAdjustingPicture_None;
09304             adjustingPictureAttribute = kPictureAttribute_None;
09305             break;
09306         case kOSDFunctionalType_SmartForward:
09307             doSmartForward = false;
09308             break;
09309         case kOSDFunctionalType_TimeStretchAdjust:
09310             stretchAdjustment = false;
09311             break;
09312         case kOSDFunctionalType_AudioSyncAdjust:
09313             audiosyncAdjustment = false;
09314             break;
09315         case kOSDFunctionalType_SubtitleZoomAdjust:
09316             subtitleZoomAdjustment = false;
09317             break;
09318         case kOSDFunctionalType_Default:
09319             break;
09320     }
09321 }
09322 
09323 PictureAttribute TV::NextPictureAdjustType(
09324     PictureAdjustType type, MythPlayer *mp, PictureAttribute attr)
09325 {
09326     if (!mp)
09327         return kPictureAttribute_None;
09328 
09329     uint sup = kPictureAttributeSupported_None;
09330     if ((kAdjustingPicture_Playback == type) && mp && mp->GetVideoOutput())
09331     {
09332         sup = mp->GetVideoOutput()->GetSupportedPictureAttributes();
09333         if (mp->HasAudioOut() && mp->PlayerControlsVolume())
09334             sup |= kPictureAttributeSupported_Volume;
09335     }
09336     else if (kAdjustingPicture_Channel == type)
09337     {
09338         sup = (kPictureAttributeSupported_Brightness |
09339                kPictureAttributeSupported_Contrast |
09340                kPictureAttributeSupported_Colour |
09341                kPictureAttributeSupported_Hue);
09342     }
09343     else if (kAdjustingPicture_Recording == type)
09344     {
09345         sup = (kPictureAttributeSupported_Brightness |
09346                kPictureAttributeSupported_Contrast |
09347                kPictureAttributeSupported_Colour |
09348                kPictureAttributeSupported_Hue);
09349     }
09350 
09351     return ::next((PictureAttributeSupported)sup, (PictureAttribute) attr);
09352 }
09353 
09354 void TV::DoToggleStudioLevels(const PlayerContext *ctx)
09355 {
09356     ctx->LockDeletePlayer(__FILE__, __LINE__);
09357     ctx->player->ToggleStudioLevels();
09358     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09359 }
09360 
09361 void TV::DoToggleNightMode(const PlayerContext *ctx)
09362 {
09363     ctx->LockDeletePlayer(__FILE__, __LINE__);
09364     ctx->player->ToggleNightMode();
09365     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09366 }
09367 
09368 void TV::DoTogglePictureAttribute(const PlayerContext *ctx,
09369                                   PictureAdjustType type)
09370 {
09371     ctx->LockDeletePlayer(__FILE__, __LINE__);
09372     PictureAttribute attr = NextPictureAdjustType(type, ctx->player,
09373                                                   adjustingPictureAttribute);
09374     if (kPictureAttribute_None == attr)
09375     {
09376         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09377         return;
09378     }
09379 
09380     adjustingPicture          = type;
09381     adjustingPictureAttribute = attr;
09382 
09383     QString title = toTitleString(type);
09384 
09385     int value = 99;
09386     if (kAdjustingPicture_Playback == type)
09387     {
09388         if (!ctx->player)
09389         {
09390             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09391             return;
09392         }
09393         if (kPictureAttribute_Volume != adjustingPictureAttribute)
09394         {
09395             value = ctx->player->GetVideoOutput()->GetPictureAttribute(attr);
09396         }
09397         else if (ctx->player->HasAudioOut())
09398         {
09399             value = ctx->player->GetVolume();
09400             title = tr("Adjust Volume");
09401         }
09402     }
09403     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09404 
09405     if (ctx->recorder && (kAdjustingPicture_Playback != type))
09406     {
09407         value = ctx->recorder->GetPictureAttribute(attr);
09408     }
09409 
09410     QString text = toString(attr) + " " + toTypeString(type);
09411 
09412     UpdateOSDStatus(ctx, title, text, QString::number(value),
09413                     kOSDFunctionalType_PictureAdjust, "%",
09414                     value * 10, kOSDTimeout_Med);
09415     SetUpdateOSDPosition(false);
09416 }
09417 
09418 void TV::DoChangePictureAttribute(
09419     PlayerContext *ctx,
09420     PictureAdjustType type, PictureAttribute attr,
09421     bool up, int newvalue)
09422 {
09423     int value = 99;
09424 
09425     ctx->LockDeletePlayer(__FILE__, __LINE__);
09426     if (kAdjustingPicture_Playback == type)
09427     {
09428         if (kPictureAttribute_Volume == attr)
09429         {
09430             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09431             ChangeVolume(ctx, up, newvalue);
09432             return;
09433         }
09434         if (!ctx->player)
09435         {
09436             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09437             return;
09438         }
09439 
09440         if (ctx->player->GetVideoOutput())
09441         {
09442             VideoOutput *vo = ctx->player->GetVideoOutput();
09443             if ((newvalue >= 0) && (newvalue <= 100))
09444                 value = vo->SetPictureAttribute(attr, newvalue);
09445             else
09446                 value = vo->ChangePictureAttribute(attr, up);
09447         }
09448     }
09449     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09450 
09451     if (ctx->recorder && (kAdjustingPicture_Playback != type))
09452     {
09453         value = ctx->recorder->ChangePictureAttribute(type, attr, up);
09454     }
09455 
09456     QString text = toString(attr) + " " + toTypeString(type);
09457 
09458     UpdateOSDStatus(ctx, toTitleString(type), text, QString::number(value),
09459                     kOSDFunctionalType_PictureAdjust, "%",
09460                     value * 10, kOSDTimeout_Med);
09461     SetUpdateOSDPosition(false);
09462 }
09463 
09464 void TV::SetActive(PlayerContext *lctx, int index, bool osd_msg)
09465 {
09466     if (!lctx)
09467         return;
09468 
09469     int new_index = (index < 0) ? (playerActive+1) % player.size() : index;
09470     new_index = ((uint)new_index >= player.size()) ? 0 : new_index;
09471 
09472     QString loc = LOC + QString("SetActive(%1,%2) %3 -> %4")
09473         .arg(index).arg((osd_msg) ? "with OSD" : "w/o OSD")
09474         .arg(playerActive).arg(new_index);
09475 
09476     LOG(VB_PLAYBACK, LOG_INFO, loc + " -- begin");
09477 
09478     for (uint i = 0; i < player.size(); i++)
09479         ClearOSD(GetPlayer(lctx,i));
09480 
09481     playerActive = new_index;
09482 
09483     for (int i = 0; i < (int)player.size(); i++)
09484     {
09485         PlayerContext *ctx = GetPlayer(lctx, i);
09486         ctx->LockDeletePlayer(__FILE__, __LINE__);
09487         if (ctx->player)
09488             ctx->player->SetPIPActive(i == playerActive);
09489         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09490     }
09491 
09492     if (osd_msg && !GetPlayer(lctx, -1)->IsPIP() && player.size() > 1)
09493     {
09494         PlayerContext *actx = GetPlayer(lctx, -1);
09495         SetOSDMessage(actx, tr("Active Changed"));
09496     }
09497 
09498     LOG(VB_PLAYBACK, LOG_INFO, loc + " -- end");
09499 }
09500 
09501 void TV::ShowOSDCutpoint(PlayerContext *ctx, const QString &type)
09502 {
09503     OSD *osd = GetOSDLock(ctx);
09504     if (!osd)
09505     {
09506         ReturnOSDLock(ctx, osd);
09507         return;
09508     }
09509 
09510 
09511     if (("EDIT_CUT_POINTS" == type) || ("EDIT_CUT_REGION" == type))
09512     {
09513         uint64_t frame   = ctx->player->GetFramesPlayed();
09514         uint64_t previous_cut = ctx->player->GetNearestMark(frame, false);
09515         uint64_t next_cut = ctx->player->GetNearestMark(frame, true);
09516         uint64_t total_frames = ctx->player->GetTotalFrameCount();
09517 
09518         osd->DialogShow(OSD_DLG_CUTPOINT,
09519                         QObject::tr("Edit Cut Points"));
09520         if (ctx->player->IsInDelete(frame))
09521         {
09522             if (ctx->player->IsTemporaryMark(frame))
09523             {
09524                 if (previous_cut > 0)
09525                     osd->DialogAddButton(QObject::tr("Move Previous Cut End "
09526                                                      "Here"),
09527                                          QString("DIALOG_CUTPOINT_MOVEPREV_%1")
09528                                                  .arg(frame));
09529                 else
09530                     osd->DialogAddButton(QObject::tr("Cut to Beginning"),
09531                                    QString("DIALOG_CUTPOINT_CUTTOBEGINNING_%1")
09532                                            .arg(frame));
09533 
09534                 if (next_cut == total_frames)
09535                     osd->DialogAddButton(QObject::tr("Cut to End"),
09536                                          QString("DIALOG_CUTPOINT_CUTTOEND_%1")
09537                                                  .arg(frame));
09538                 else
09539                     osd->DialogAddButton(QObject::tr("Move Next Cut Start "
09540                                                      "Here"),
09541                                          QString("DIALOG_CUTPOINT_MOVENEXT_%1")
09542                                                  .arg(frame));
09543             }
09544             else
09545             {
09546                 osd->DialogAddButton(QObject::tr("Move Start of Cut Here"),
09547                                      QString("DIALOG_CUTPOINT_MOVEPREV_%1")
09548                                              .arg(frame));
09549                 osd->DialogAddButton(QObject::tr("Move End of Cut Here"),
09550                                      QString("DIALOG_CUTPOINT_MOVENEXT_%1")
09551                                              .arg(frame));
09552             }
09553             osd->DialogAddButton(QObject::tr("Delete This Cut"),
09554                                  QString("DIALOG_CUTPOINT_DELETE_%1")
09555                                          .arg(frame));
09556         }
09557         else
09558         {
09559             if (previous_cut > 0)
09560                 osd->DialogAddButton(QObject::tr("Move Previous Cut End Here"),
09561                                      QString("DIALOG_CUTPOINT_MOVEPREV_%1")
09562                                              .arg(frame));
09563             else
09564                 osd->DialogAddButton(QObject::tr("Cut to Beginning"),
09565                                   QString("DIALOG_CUTPOINT_CUTTOBEGINNING_%1")
09566                                           .arg(frame));
09567             if (next_cut == total_frames)
09568                 osd->DialogAddButton(QObject::tr("Cut to End"),
09569                                      QString("DIALOG_CUTPOINT_CUTTOEND_%1")
09570                                              .arg(frame));
09571             else
09572                 osd->DialogAddButton(QObject::tr("Move Next Cut Start Here"),
09573                                      QString("DIALOG_CUTPOINT_MOVENEXT_%1")
09574                                              .arg(frame));
09575             osd->DialogAddButton(QObject::tr("Add New Cut"),
09576                                  QString("DIALOG_CUTPOINT_NEWCUT_%1")
09577                                          .arg(frame));
09578             osd->DialogAddButton(QObject::tr("Join Surrounding Cuts"),
09579                                  QString("DIALOG_CUTPOINT_DELETE_%1")
09580                                          .arg(frame));
09581         }
09582         if (ctx->player->DeleteMapHasUndo())
09583             osd->DialogAddButton(QObject::tr("Undo") + " - " +
09584                                  ctx->player->DeleteMapGetUndoMessage(),
09585                                  QString("DIALOG_CUTPOINT_UNDO_0"));
09586         if (ctx->player->DeleteMapHasRedo())
09587             osd->DialogAddButton(QObject::tr("Redo") + " - " +
09588                                  ctx->player->DeleteMapGetRedoMessage(),
09589                                  QString("DIALOG_CUTPOINT_REDO_0"));
09590         if ("EDIT_CUT_POINTS" == type)
09591             osd->DialogAddButton(QObject::tr("Cut List Options"),
09592                                  "DIALOG_CUTPOINT_CUTLISTOPTIONS_0", true);
09593     }
09594     else if ("CUT_LIST_OPTIONS" == type)
09595     {
09596         osd->DialogShow(OSD_DLG_CUTPOINT,
09597                         QObject::tr("Cut List Options"));
09598         osd->DialogAddButton(QObject::tr("Clear Cuts"),
09599                              "DIALOG_CUTPOINT_CLEARMAP_0");
09600         osd->DialogAddButton(QObject::tr("Reverse Cuts"),
09601                              "DIALOG_CUTPOINT_INVERTMAP_0");
09602         osd->DialogAddButton(QObject::tr("Load Detected Commercials"),
09603                              "DIALOG_CUTPOINT_LOADCOMMSKIP_0");
09604         osd->DialogAddButton(QObject::tr("Undo Changes"),
09605                              "DIALOG_CUTPOINT_REVERT_0");
09606         osd->DialogAddButton(QObject::tr("Exit Without Saving"),
09607                              "DIALOG_CUTPOINT_REVERTEXIT_0");
09608         osd->DialogAddButton(QObject::tr("Save Cuts"),
09609                              "DIALOG_CUTPOINT_SAVEMAP_0");
09610         osd->DialogAddButton(QObject::tr("Save Cuts and Exit"),
09611                              "DIALOG_CUTPOINT_SAVEEXIT_0");
09612     }
09613     else if ("EXIT_EDIT_MODE" == type)
09614     {
09615         osd->DialogShow(OSD_DLG_CUTPOINT,
09616                         QObject::tr("Exit Recording Editor"));
09617         osd->DialogAddButton(QObject::tr("Save Cuts and Exit"),
09618                              "DIALOG_CUTPOINT_SAVEEXIT_0");
09619         osd->DialogAddButton(QObject::tr("Exit Without Saving"),
09620                              "DIALOG_CUTPOINT_REVERTEXIT_0");
09621         osd->DialogAddButton(QObject::tr("Save Cuts"),
09622                              "DIALOG_CUTPOINT_SAVEMAP_0");
09623         osd->DialogAddButton(QObject::tr("Undo Changes"),
09624                              "DIALOG_CUTPOINT_REVERT_0");
09625     }
09626     osd->DialogBack("", "DIALOG_CUTPOINT_DONOTHING_0", true);
09627     QHash<QString,QString> map;
09628     map.insert("title", tr("Edit"));
09629     osd->SetText("osd_program_editor", map, kOSDTimeout_None);
09630     ReturnOSDLock(ctx, osd);
09631 }
09632 
09633 bool TV::HandleOSDCutpoint(PlayerContext *ctx, QString action, long long frame)
09634 {
09635     bool res = true;
09636     if (!DialogIsVisible(ctx, OSD_DLG_CUTPOINT))
09637         return res;
09638 
09639     OSD *osd = GetOSDLock(ctx);
09640     if (action == "CUTLISTOPTIONS" && osd)
09641     {
09642         ShowOSDCutpoint(ctx, "CUT_LIST_OPTIONS");
09643         res = false;
09644     }
09645     else if (action == "DONOTHING" && osd)
09646     {
09647     }
09648     else if (osd)
09649     {
09650         QStringList actions(action);
09651         if (!ctx->player->HandleProgramEditorActions(actions, frame))
09652             LOG(VB_GENERAL, LOG_ERR, LOC + "Unrecognised cutpoint action");
09653         else
09654             editmode = ctx->player->GetEditMode();
09655     }
09656     ReturnOSDLock(ctx, osd);
09657     return res;
09658 }
09659 
09663 void TV::StartProgramEditMode(PlayerContext *ctx)
09664 {
09665     ctx->LockPlayingInfo(__FILE__, __LINE__);
09666     bool isEditing = ctx->playingInfo->QueryIsEditing();
09667     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
09668 
09669     if (isEditing)
09670     {
09671         ShowOSDAlreadyEditing(ctx);
09672         return;
09673     }
09674 
09675     ctx->LockDeletePlayer(__FILE__, __LINE__);
09676     if (ctx->player)
09677         editmode = ctx->player->EnableEdit();
09678     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09679 }
09680 
09681 void TV::ShowOSDAlreadyEditing(PlayerContext *ctx)
09682 {
09683     OSD *osd = GetOSDLock(ctx);
09684     if (osd)
09685     {
09686         osd->DialogQuit();
09687         bool was_paused = ContextIsPaused(ctx, __FILE__, __LINE__);
09688         if (!was_paused)
09689             DoTogglePause(ctx, true);
09690 
09691         QString message = tr("This program is currently being edited");
09692         osd->DialogShow(OSD_DLG_EDITING, message);
09693         QString def = QString("DIALOG_EDITING_CONTINUE_%1").arg(was_paused);
09694         osd->DialogAddButton(tr("Continue Editing"), def, false, true);
09695         osd->DialogAddButton(tr("Do not edit"),
09696                              QString("DIALOG_EDITING_STOP_%1").arg(was_paused));
09697         osd->DialogBack("", def, true);
09698     }
09699     ReturnOSDLock(ctx, osd);
09700 }
09701 
09702 void TV::HandleOSDAlreadyEditing(PlayerContext *ctx, QString action,
09703                                  bool was_paused)
09704 {
09705     if (!DialogIsVisible(ctx, OSD_DLG_EDITING))
09706         return;
09707 
09708     bool paused = ContextIsPaused(ctx, __FILE__, __LINE__);
09709 
09710     if (action == "STOP")
09711     {
09712         ctx->LockPlayingInfo(__FILE__, __LINE__);
09713         if (ctx->playingInfo)
09714             ctx->playingInfo->SaveEditing(false);
09715         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
09716         if (!was_paused && paused)
09717             DoTogglePause(ctx, true);
09718     }
09719     else // action == "CONTINUE"
09720     {
09721         ctx->LockDeletePlayer(__FILE__, __LINE__);
09722         if (ctx->player)
09723         {
09724             ctx->playingInfo->SaveEditing(false);
09725             editmode = ctx->player->EnableEdit();
09726             if (!editmode && !was_paused && paused)
09727                 DoTogglePause(ctx, false);
09728         }
09729         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09730     }
09731 
09732 }
09733 
09734 static void insert_map(InfoMap &infoMap, const InfoMap &newMap)
09735 {
09736     InfoMap::const_iterator it = newMap.begin();
09737     for (; it != newMap.end(); ++it)
09738         infoMap.insert(it.key(), *it);
09739 }
09740 
09744 void TV::StartChannelEditMode(PlayerContext *ctx)
09745 {
09746     OSD *osd = GetOSDLock(ctx);
09747     if (!ctx->recorder || !osd)
09748     {
09749         ReturnOSDLock(ctx, osd);
09750         return;
09751     }
09752     ReturnOSDLock(ctx, osd);
09753 
09754     QMutexLocker locker(&chanEditMapLock);
09755     ddMapLoader->wait();
09756 
09757     // Get the info available from the backend
09758     chanEditMap.clear();
09759     ctx->recorder->GetChannelInfo(chanEditMap);
09760 
09761     // Assuming the data is valid, try to load DataDirect listings.
09762     uint sourceid = chanEditMap["sourceid"].toUInt();
09763 
09764     // Update with XDS and DataDirect Info
09765     ChannelEditAutoFill(ctx, chanEditMap);
09766 
09767     // Set proper initial values for channel editor, and make it visible..
09768     osd = GetOSDLock(ctx);
09769     if (osd)
09770     {
09771         osd->DialogQuit();
09772         osd->DialogShow(OSD_DLG_EDITOR);
09773         osd->SetText(OSD_DLG_EDITOR, chanEditMap, kOSDTimeout_None);
09774     }
09775     ReturnOSDLock(ctx, osd);
09776 
09777     if (sourceid && (sourceid != ddMapSourceId))
09778     {
09779         ddMapLoader->SetSourceID(sourceid);
09780         MThreadPool::globalInstance()->start(ddMapLoader, "DDMapLoader");
09781     }
09782 }
09783 
09787 bool TV::HandleOSDChannelEdit(PlayerContext *ctx, QString action)
09788 {
09789     QMutexLocker locker(&chanEditMapLock);
09790     bool hide = false;
09791 
09792     if (!DialogIsVisible(ctx, OSD_DLG_EDITOR))
09793         return hide;
09794 
09795     OSD *osd = GetOSDLock(ctx);
09796     if (osd && action == "PROBE")
09797     {
09798         InfoMap infoMap;
09799         osd->DialogGetText(infoMap);
09800         ChannelEditAutoFill(ctx, infoMap);
09801         insert_map(chanEditMap, infoMap);
09802         osd->SetText(OSD_DLG_EDITOR, chanEditMap, kOSDTimeout_None);
09803     }
09804     else if (osd && action == "OK")
09805     {
09806         InfoMap infoMap;
09807         osd->DialogGetText(infoMap);
09808         insert_map(chanEditMap, infoMap);
09809         ctx->recorder->SetChannelInfo(chanEditMap);
09810         hide = true;
09811     }
09812     else if (osd && action == "QUIT")
09813     {
09814         hide = true;
09815     }
09816     ReturnOSDLock(ctx, osd);
09817     return hide;
09818 }
09819 
09823 void TV::ChannelEditAutoFill(const PlayerContext *ctx, InfoMap &infoMap) const
09824 {
09825     QMap<QString,bool> dummy;
09826     ChannelEditAutoFill(ctx, infoMap, dummy);
09827 }
09828 
09832 void TV::ChannelEditAutoFill(const PlayerContext *ctx, InfoMap &infoMap,
09833                              const QMap<QString,bool> &changed) const
09834 {
09835     const QString keys[4] = { "XMLTV", "callsign", "channame", "channum", };
09836 
09837     // fill in uninitialized and unchanged fields from XDS
09838     ChannelEditXDSFill(ctx, infoMap);
09839 
09840     // if no data direct info we're done..
09841     if (!ddMapSourceId)
09842         return;
09843 
09844     if (changed.size())
09845     {
09846         ChannelEditDDFill(infoMap, changed, false);
09847     }
09848     else
09849     {
09850         QMutexLocker locker(&chanEditMapLock);
09851         QMap<QString,bool> chg;
09852         // check if anything changed
09853         for (uint i = 0; i < 4; i++)
09854             chg[keys[i]] = infoMap[keys[i]] != chanEditMap[keys[i]];
09855 
09856         // clean up case and extra spaces
09857         infoMap["callsign"] = infoMap["callsign"].toUpper().trimmed();
09858         infoMap["channum"]  = infoMap["channum"].trimmed();
09859         infoMap["channame"] = infoMap["channame"].trimmed();
09860         infoMap["XMLTV"]    = infoMap["XMLTV"].trimmed();
09861 
09862         // make sure changes weren't just chaff
09863         for (uint i = 0; i < 4; i++)
09864             chg[keys[i]] &= infoMap[keys[i]] != chanEditMap[keys[i]];
09865 
09866         ChannelEditDDFill(infoMap, chg, true);
09867     }
09868 }
09869 
09870 void TV::ChannelEditXDSFill(const PlayerContext *ctx, InfoMap &infoMap) const
09871 {
09872     QMap<QString,bool> modifiable;
09873     if (!(modifiable["callsign"] = infoMap["callsign"].isEmpty()))
09874     {
09875         QString unsetsign = QObject::tr("UNKNOWN%1", "Synthesized callsign");
09876         uint    unsetcmpl = unsetsign.length() - 2;
09877         unsetsign = unsetsign.left(unsetcmpl);
09878         if (infoMap["callsign"].left(unsetcmpl) == unsetsign) // was unsetcmpl????
09879             modifiable["callsign"] = true;
09880     }
09881     modifiable["channame"] = infoMap["channame"].isEmpty();
09882 
09883     const QString xds_keys[2] = { "callsign", "channame", };
09884     for (uint i = 0; i < 2; i++)
09885     {
09886         if (!modifiable[xds_keys[i]])
09887             continue;
09888 
09889         ctx->LockDeletePlayer(__FILE__, __LINE__);
09890         QString tmp = ctx->player->GetXDS(xds_keys[i]).toUpper();
09891         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
09892 
09893         if (tmp.isEmpty())
09894             continue;
09895 
09896         if ((xds_keys[i] == "callsign") &&
09897             ((tmp.length() > 5) || (tmp.indexOf(" ") >= 0)))
09898         {
09899             continue;
09900         }
09901 
09902         infoMap[xds_keys[i]] = tmp;
09903     }
09904 }
09905 
09906 void TV::ChannelEditDDFill(InfoMap &infoMap,
09907                            const QMap<QString,bool> &changed,
09908                            bool check_unchanged) const
09909 {
09910     if (!ddMapSourceId)
09911         return;
09912 
09913     QMutexLocker locker(&chanEditMapLock);
09914     const QString keys[4] = { "XMLTV", "callsign", "channame", "channum", };
09915 
09916     // First check changed keys for availability in our listings source.
09917     // Then, if check_unchanged is set, check unchanged fields.
09918     QString key = "", dd_xmltv = "";
09919     uint endj = (check_unchanged) ? 2 : 1;
09920     for (uint j = 0; (j < endj) && dd_xmltv.isEmpty(); j++)
09921     {
09922         for (uint i = 0; (i < 4) && dd_xmltv.isEmpty(); i++)
09923         {
09924             key = keys[i];
09925             if (((j == 1) ^ changed[key]) && !infoMap[key].isEmpty())
09926                 dd_xmltv = GetDataDirect(key, infoMap[key], "XMLTV");
09927         }
09928     }
09929 
09930     // If we found the channel in the listings, fill in all the data we have
09931     if (!dd_xmltv.isEmpty())
09932     {
09933         infoMap[keys[0]] = dd_xmltv;
09934         for (uint i = 1; i < 4; i++)
09935         {
09936             QString tmp = GetDataDirect(key, infoMap[key], keys[i]);
09937             if (!tmp.isEmpty())
09938                 infoMap[keys[i]] = tmp;
09939         }
09940         return;
09941     }
09942 
09943     // If we failed to find an exact match, try partial matches.
09944     // But only fill the current field since this data is dodgy.
09945     key = "callsign";
09946     if (!infoMap[key].isEmpty())
09947     {
09948         dd_xmltv = GetDataDirect(key, infoMap[key], "XMLTV", true);
09949         LOG(VB_GENERAL, LOG_INFO, QString("xmltv: %1 for key %2")
09950                 .arg(dd_xmltv).arg(key));
09951         if (!dd_xmltv.isEmpty())
09952             infoMap[key] = GetDataDirect("XMLTV", dd_xmltv, key);
09953     }
09954 
09955     key = "channame";
09956     if (!infoMap[key].isEmpty())
09957     {
09958         dd_xmltv = GetDataDirect(key, infoMap[key], "XMLTV", true);
09959         LOG(VB_GENERAL, LOG_INFO, QString("xmltv: %1 for key %2")
09960                 .arg(dd_xmltv).arg(key));
09961         if (!dd_xmltv.isEmpty())
09962             infoMap[key] = GetDataDirect("XMLTV", dd_xmltv, key);
09963     }
09964 }
09965 
09966 QString TV::GetDataDirect(QString key, QString value, QString field,
09967                           bool allow_partial_match) const
09968 {
09969     QMutexLocker locker(&chanEditMapLock);
09970 
09971     uint sourceid = chanEditMap["sourceid"].toUInt();
09972     if (!sourceid)
09973         return QString::null;
09974 
09975     if (sourceid != ddMapSourceId)
09976         return QString::null;
09977 
09978     DDKeyMap::const_iterator it_key = ddMap.find(key);
09979     if (it_key == ddMap.end())
09980         return QString::null;
09981 
09982     DDValueMap::const_iterator it_val = (*it_key).find(value);
09983     if (it_val != (*it_key).end())
09984     {
09985         InfoMap::const_iterator it_field = (*it_val).find(field);
09986         if (it_field != (*it_val).end())
09987         {
09988             QString ret = *it_field;
09989             ret.detach();
09990             return ret;
09991         }
09992     }
09993 
09994     if (!allow_partial_match || value.isEmpty())
09995         return QString::null;
09996 
09997     // Check for partial matches.. prefer early match, then short string
09998     DDValueMap::const_iterator best_match = (*it_key).end();
09999     int best_match_idx = INT_MAX, best_match_len = INT_MAX;
10000     for (it_val = (*it_key).begin(); it_val != (*it_key).end(); ++it_val)
10001     {
10002         int match_idx = it_val.key().indexOf(value);
10003         if (match_idx < 0)
10004             continue;
10005 
10006         int match_len = it_val.key().length();
10007         if ((match_idx < best_match_idx) && (match_len < best_match_len))
10008         {
10009             best_match     = it_val;
10010             best_match_idx = match_idx;
10011             best_match_len = match_len;
10012         }
10013     }
10014 
10015     if (best_match != (*it_key).end())
10016     {
10017         InfoMap::const_iterator it_field = (*best_match).find(field);
10018         if (it_field != (*it_val).end())
10019         {
10020             QString ret = *it_field;
10021             ret.detach();
10022             return ret;
10023         }
10024     }
10025 
10026     return QString::null;
10027 }
10028 
10029 void TV::RunLoadDDMap(uint sourceid)
10030 {
10031     QMutexLocker locker(&chanEditMapLock);
10032 
10033     const PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
10034 
10035     // Load DataDirect info
10036     LoadDDMap(sourceid);
10037 
10038     // Update with XDS and DataDirect Info
10039     ChannelEditAutoFill(actx, chanEditMap);
10040 
10041     OSD *osd = GetOSDLock(actx);
10042     if (osd)
10043     {
10044         if (osd->DialogVisible(OSD_DLG_EDITOR))
10045             osd->SetText(OSD_DLG_EDITOR, chanEditMap, kOSDTimeout_None);
10046         else
10047             LOG(VB_GENERAL, LOG_ERR, LOC + "No channel editor visible. Failed "
10048                                          "to update data direct channel info.");
10049     }
10050     ReturnOSDLock(actx, osd);
10051     ReturnPlayerLock(actx);
10052 }
10053 
10054 bool TV::LoadDDMap(uint sourceid)
10055 {
10056     QMutexLocker locker(&chanEditMapLock);
10057     const QString keys[4] = { "XMLTV", "callsign", "channame", "channum", };
10058 
10059     ddMap.clear();
10060     ddMapSourceId = 0;
10061 
10062     QString grabber, userid, passwd, lineupid;
10063     bool ok = SourceUtil::GetListingsLoginData(sourceid, grabber, userid,
10064                                                passwd, lineupid);
10065     if (!ok || (grabber != "datadirect"))
10066     {
10067         LOG(VB_PLAYBACK, LOG_ERR, LOC +
10068             QString("LoadDDMap() g(%1)").arg(grabber));
10069         return false;
10070     }
10071 
10072     DataDirectProcessor ddp(DD_ZAP2IT, userid, passwd);
10073     ddp.GrabFullLineup(lineupid, true, false, 36*60*60);
10074     const DDLineupChannels channels = ddp.GetDDLineup(lineupid);
10075 
10076     InfoMap tmp;
10077     DDLineupChannels::const_iterator it;
10078     for (it = channels.begin(); it != channels.end(); ++it)
10079     {
10080         DDStation station = ddp.GetDDStation((*it).stationid);
10081         tmp["XMLTV"]    = (*it).stationid;
10082         tmp["callsign"] = station.callsign;
10083         tmp["channame"] = station.stationname;
10084         tmp["channum"]  = (*it).channel;
10085         if (!(*it).channelMinor.isEmpty())
10086         {
10087             tmp["channum"] += SourceUtil::GetChannelSeparator(sourceid);
10088             tmp["channum"] += (*it).channelMinor;
10089         }
10090 
10091 #if 0
10092         LOG(VB_CHANNEL, LOG_INFO,
10093             QString("Adding channel: %1 -- %2 -- %3 -- %4")
10094                 .arg(tmp["channum"],4).arg(tmp["callsign"],7)
10095                 .arg(tmp["XMLTV"]).arg(tmp["channame"]));
10096 #endif
10097 
10098         for (uint j = 0; j < 4; j++)
10099             for (uint i = 0; i < 4; i++)
10100                 ddMap[keys[j]][tmp[keys[j]]][keys[i]] = tmp[keys[i]];
10101     }
10102 
10103     if (!ddMap.empty())
10104         ddMapSourceId = sourceid;
10105 
10106     return !ddMap.empty();
10107 }
10108 
10109 void TV::OSDDialogEvent(int result, QString text, QString action)
10110 {
10111     PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
10112 
10113     LOG(VB_GENERAL, LOG_DEBUG, LOC +
10114         QString("OSDDialogEvent: result %1 text %2 action %3")
10115             .arg(result).arg(text).arg(action));
10116 
10117     bool hide = true;
10118 
10119     if (action.startsWith("DIALOG_"))
10120     {
10121         action.remove("DIALOG_");
10122         QStringList desc = action.split("_");
10123         bool valid = desc.size() == 3;
10124         if (valid && desc[0] == "MENU")
10125         {
10126             ShowOSDMenu(actx, desc[1], text);
10127             hide = false;
10128         }
10129         else if (valid && desc[0] == ACTION_JUMPREC)
10130         {
10131             FillOSDMenuJumpRec(actx, desc[1], desc[2].toInt(), text);
10132             hide = false;
10133         }
10134         else if (valid && desc[0] == "VIDEOEXIT")
10135         {
10136             hide = HandleOSDVideoExit(actx, desc[1]);
10137         }
10138         else if (valid && desc[0] == "SLEEP")
10139         {
10140             HandleOSDSleep(actx, desc[1]);
10141         }
10142         else if (valid && desc[0] == "IDLE")
10143         {
10144             HandleOSDIdle(actx, desc[1]);
10145         }
10146         else if (valid && desc[0] == "INFO")
10147         {
10148             HandleOSDInfo(actx, desc[1]);
10149         }
10150         else if (valid && desc[0] == "EDITING")
10151         {
10152             HandleOSDAlreadyEditing(actx, desc[1], desc[2].toInt());
10153         }
10154         else if (valid && desc[0] == "ASKALLOW")
10155         {
10156             HandleOSDAskAllow(actx, desc[1]);
10157         }
10158         else if (valid && desc[0] == "EDITOR")
10159         {
10160             hide = HandleOSDChannelEdit(actx, desc[1]);
10161         }
10162         else if (valid && desc[0] == "CUTPOINT")
10163         {
10164             hide = HandleOSDCutpoint(actx, desc[1], desc[2].toLongLong());
10165         }
10166         else if (valid && desc[0] == "DELETE")
10167         {
10168         }
10169         else if (valid && desc[0] == ACTION_PLAY)
10170         {
10171             DoPlay(actx);
10172         }
10173         else if (valid && desc[0] == "CONFIRM")
10174         {
10175         }
10176         else
10177         {
10178             LOG(VB_GENERAL, LOG_ERR, "Unrecognised dialog event.");
10179         }
10180     }
10181     else if (result < 0)
10182         ; // exit dialog
10183     else if (HandleTrackAction(actx, action))
10184         ;
10185     else if (action == ACTION_PAUSE)
10186         DoTogglePause(actx, true);
10187     else if (action == ACTION_STOP)
10188     {
10189         PrepareToExitPlayer(actx, __LINE__);
10190         SetExitPlayer(true, true);
10191     }
10192     else if (action == "CANCELPLAYLIST")
10193     {
10194         setInPlayList(false);
10195         MythEvent xe("CANCEL_PLAYLIST");
10196         gCoreContext->dispatch(xe);
10197     }
10198     else if (action == ACTION_JUMPFFWD)
10199         DoJumpFFWD(actx);
10200     else if (action == ACTION_JUMPRWND)
10201         DoJumpRWND(actx);
10202     else if (action.startsWith("DEINTERLACER"))
10203         HandleDeinterlacer(actx, action);
10204     else if (action == ACTION_TOGGLEOSDDEBUG)
10205         ToggleOSDDebug(actx);
10206     else if (action == "TOGGLEMANUALZOOM")
10207         SetManualZoom(actx, true, tr("Zoom Mode ON"));
10208     else if (action == "TOGGLESTRETCH")
10209         ToggleTimeStretch(actx);
10210     else if (action == ACTION_ENABLEUPMIX)
10211         EnableUpmix(actx, true);
10212     else if (action == ACTION_DISABLEUPMIX)
10213         EnableUpmix(actx, false);
10214     else if (action.left(13) == "ADJUSTSTRETCH")
10215     {
10216         bool floatRead;
10217         float stretch = action.right(action.length() - 13).toFloat(&floatRead);
10218         if (floatRead &&
10219             stretch <= 2.0 &&
10220             stretch >= 0.48)
10221         {
10222             actx->ts_normal = stretch;   // alter speed before display
10223         }
10224 
10225         StopFFRew(actx);
10226 
10227         if (ContextIsPaused(actx, __FILE__, __LINE__))
10228             DoTogglePause(actx, true);
10229 
10230         ChangeTimeStretch(actx, 0, !floatRead);   // just display
10231     }
10232     else if (action.left(11) == "SELECTSCAN_")
10233     {
10234         QString msg = QString::null;
10235         actx->LockDeletePlayer(__FILE__, __LINE__);
10236         actx->player->SetScanType((FrameScanType) action.right(1).toInt());
10237         actx->UnlockDeletePlayer(__FILE__, __LINE__);
10238         msg = toString(actx->player->GetScanType());
10239 
10240         if (!msg.isEmpty())
10241             SetOSDMessage(actx, msg);
10242     }
10243     else if (action.left(15) == ACTION_TOGGELAUDIOSYNC)
10244         ChangeAudioSync(actx, 0);
10245     else if (action == ACTION_TOGGLESUBTITLEZOOM)
10246         ChangeSubtitleZoom(actx, 0);
10247     else if (action == ACTION_TOGGLEVISUALISATION)
10248         EnableVisualisation(actx, false, true /*toggle*/);
10249     else if (action == ACTION_ENABLEVISUALISATION)
10250         EnableVisualisation(actx, true);
10251     else if (action == ACTION_DISABLEVISUALISATION)
10252         EnableVisualisation(actx, false);
10253     else if (action.left(11) == ACTION_TOGGLESLEEP)
10254     {
10255         ToggleSleepTimer(actx, action.left(13));
10256     }
10257     else if (action.left(17) == "TOGGLEPICCONTROLS")
10258     {
10259         adjustingPictureAttribute = (PictureAttribute)
10260             (action.right(1).toInt() - 1);
10261         DoTogglePictureAttribute(actx, kAdjustingPicture_Playback);
10262     }
10263     else if (action.left(18) == ACTION_TOGGLESTUDIOLEVELS)
10264     {
10265         DoToggleStudioLevels(actx);
10266     }
10267     else if (action == ACTION_TOGGLENIGHTMODE)
10268     {
10269         DoToggleNightMode(actx);
10270     }
10271     else if (action.left(12) == "TOGGLEASPECT")
10272     {
10273         ToggleAspectOverride(actx,
10274                              (AspectOverrideMode) action.right(1).toInt());
10275     }
10276     else if (action.left(10) == "TOGGLEFILL")
10277     {
10278         ToggleAdjustFill(actx, (AdjustFillMode) action.right(1).toInt());
10279     }
10280     else if (action == "AUTODETECT_FILL")
10281     {
10282         actx->player->detect_letter_box->SetDetectLetterbox(!actx->player->detect_letter_box->GetDetectLetterbox());
10283     }
10284     else if (action == ACTION_GUIDE)
10285         EditSchedule(actx, kScheduleProgramGuide);
10286     else if (action.left(10) == "CHANGROUP_" && db_use_channel_groups)
10287     {
10288         if (action == "CHANGROUP_ALL_CHANNELS")
10289         {
10290             UpdateChannelList(-1);
10291         }
10292         else
10293         {
10294             action.remove("CHANGROUP_");
10295 
10296             UpdateChannelList(action.toInt());
10297 
10298             // make sure the current channel is from the selected group
10299             // or tune to the first in the group
10300             QString cur_channum, new_channum;
10301             if (actx->tvchain)
10302             {
10303                 QMutexLocker locker(&channelGroupLock);
10304                 const DBChanList &list = channelGroupChannelList;
10305                 cur_channum = actx->tvchain->GetChannelName(-1);
10306                 new_channum = cur_channum;
10307 
10308                 DBChanList::const_iterator it = list.begin();
10309                 for (; it != list.end(); ++it)
10310                 {
10311                     if ((*it).channum == cur_channum)
10312                     {
10313                         break;
10314                     }
10315                 }
10316 
10317                 if (it == list.end())
10318                 {
10319                     // current channel not found so switch to the
10320                     // first channel in the group
10321                     it = list.begin();
10322                     if (it != list.end())
10323                         new_channum = (*it).channum;
10324                 }
10325 
10326                 LOG(VB_CHANNEL, LOG_INFO, LOC +
10327                     QString("Channel Group: '%1'->'%2'")
10328                         .arg(cur_channum).arg(new_channum));
10329             }
10330 
10331             if (actx->tvchain)
10332             {
10333                 // Only change channel if new channel != current channel
10334                 if (cur_channum != new_channum && !new_channum.isEmpty())
10335                 {
10336                     QMutexLocker locker(&timerIdLock);
10337                     queuedInput   = new_channum; queuedInput.detach();
10338                     queuedChanNum = new_channum; queuedChanNum.detach();
10339                     queuedChanID  = 0;
10340                     if (!queueInputTimerId)
10341                         queueInputTimerId = StartTimer(10, __LINE__);
10342                 }
10343 
10344                 // Turn off OSD Channel Num so the channel
10345                 // changes right away
10346                 HideOSDWindow(actx, "osd_input");
10347             }
10348         }
10349     }
10350     else if (action == ACTION_FINDER)
10351         EditSchedule(actx, kScheduleProgramFinder);
10352     else if (action == "SCHEDULE")
10353         EditSchedule(actx, kScheduledRecording);
10354     else if (action == ACTION_VIEWSCHEDULED)
10355         EditSchedule(actx, kViewSchedule);
10356     else if (action.startsWith("VISUALISER"))
10357         EnableVisualisation(actx, true, false, action);
10358     else if (action.startsWith("3D"))
10359         Handle3D(actx, action);
10360     else if (HandleJumpToProgramAction(actx, QStringList(action)))
10361     {
10362     }
10363     else if (PxPHandleAction(actx, QStringList(action)))
10364     {
10365         for (uint i = 0; i < player.size(); i++)
10366             ClearOSD(GetPlayer(actx,i));
10367         actx = GetPlayer(actx,-1); // "NEXTPIPWINDOW" changes active context..
10368     }
10369     else if (StateIsLiveTV(GetState(actx)))
10370     {
10371         if (action == "TOGGLEBROWSE")
10372             browsehelper->BrowseStart(actx);
10373         else if (action == "PREVCHAN")
10374             PopPreviousChannel(actx, true);
10375         else if (action.left(14) == "SWITCHTOINPUT_")
10376         {
10377             switchToInputId = action.mid(14).toUInt();
10378             QMutexLocker locker(&timerIdLock);
10379             if (!switchToInputTimerId)
10380                 switchToInputTimerId = StartTimer(1, __LINE__);
10381         }
10382         else if (action == "EDIT")
10383         {
10384             StartChannelEditMode(actx);
10385             hide = false;
10386         }
10387         else
10388         {
10389             LOG(VB_GENERAL, LOG_ERR, LOC +
10390                 "Unknown menu action selected: " + action);
10391             hide = false;
10392         }
10393     }
10394     else if (StateIsPlaying(actx->GetState()))
10395     {
10396         if (action == ACTION_JUMPTODVDROOTMENU ||
10397             action == ACTION_JUMPTODVDCHAPTERMENU ||
10398             action == ACTION_JUMPTOPOPUPMENU ||
10399             action == ACTION_JUMPTODVDTITLEMENU)
10400         {
10401             QString menu = "root";
10402             if (action == ACTION_JUMPTODVDCHAPTERMENU)
10403                 menu = "chapter";
10404             else if (action == ACTION_JUMPTODVDTITLEMENU)
10405                 menu = "title";
10406             else if (action == ACTION_JUMPTOPOPUPMENU)
10407                 menu = "popup";
10408             actx->LockDeletePlayer(__FILE__, __LINE__);
10409             if (actx->player)
10410                 actx->player->GoToMenu(menu);
10411             actx->UnlockDeletePlayer(__FILE__, __LINE__);
10412         }
10413         else if (action.left(13) == ACTION_JUMPCHAPTER)
10414         {
10415             int chapter = action.right(3).toInt();
10416             DoJumpChapter(actx, chapter);
10417         }
10418         else if (action.left(11) == ACTION_SWITCHTITLE)
10419         {
10420             int title = action.right(3).toInt();
10421             DoSwitchTitle(actx, title);
10422         }
10423         else if (action.left(13) == ACTION_SWITCHANGLE)
10424         {
10425             int angle = action.right(3).toInt();
10426             DoSwitchAngle(actx, angle);
10427         }
10428         else if (action == "EDIT")
10429         {
10430             StartProgramEditMode(actx);
10431             hide = false;
10432         }
10433         else if (action == "TOGGLEAUTOEXPIRE")
10434             ToggleAutoExpire(actx);
10435         else if (action.left(14) == "TOGGLECOMMSKIP")
10436             SetAutoCommercialSkip(
10437                 actx, (CommSkipMode)(action.right(1).toInt()));
10438         else if (action == "QUEUETRANSCODE")
10439             DoQueueTranscode(actx, "Default");
10440         else if (action == "QUEUETRANSCODE_AUTO")
10441             DoQueueTranscode(actx, "Autodetect");
10442         else if (action == "QUEUETRANSCODE_HIGH")
10443             DoQueueTranscode(actx, "High Quality");
10444         else if (action == "QUEUETRANSCODE_MEDIUM")
10445             DoQueueTranscode(actx, "Medium Quality");
10446         else if (action == "QUEUETRANSCODE_LOW")
10447             DoQueueTranscode(actx, "Low Quality");
10448         else
10449         {
10450             LOG(VB_GENERAL, LOG_ERR, LOC +
10451                 "Unknown menu action selected: " + action);
10452             hide = false;
10453         }
10454     }
10455 
10456     if (hide)
10457     {
10458         OSD *osd = GetOSDLock(actx);
10459         if (osd)
10460             osd->DialogQuit();
10461         ReturnOSDLock(actx, osd);
10462     }
10463 
10464     ReturnPlayerLock(actx);
10465 }
10466 
10467 bool TV::DialogIsVisible(PlayerContext *ctx, const QString &dialog)
10468 {
10469     bool visible = false;
10470     OSD *osd = GetOSDLock(ctx);
10471     if (osd)
10472         visible = osd->DialogVisible(dialog);
10473     ReturnOSDLock(ctx, osd);
10474     return visible;
10475 }
10476 
10477 void TV::HandleOSDInfo(PlayerContext *ctx, QString action)
10478 {
10479     if (!DialogIsVisible(ctx, OSD_DLG_INFO))
10480         return;
10481 
10482     if (action == "CHANNELLOCK")
10483     {
10484         lockTimerOn = false;
10485     }
10486 }
10487 
10488 void TV::ShowOSDMenu(const PlayerContext *ctx, const QString category,
10489                      const QString selected)
10490 {
10491     QString cat = category.isEmpty() ? "MAIN" : category;
10492 
10493     OSD *osd = GetOSDLock(ctx);
10494     if (osd)
10495     {
10496         osd->DialogShow(OSD_DLG_MENU, tr("Playback Menu"));
10497         QString currenttext = QString();
10498         QString back = QString();
10499 
10500         FillOSDMenuAudio    (ctx, osd, cat, selected, currenttext, back);
10501         FillOSDMenuVideo    (ctx, osd, cat, selected, currenttext, back);
10502         FillOSDMenuSubtitles(ctx, osd, cat, selected, currenttext, back);
10503         FillOSDMenuPlayback (ctx, osd, cat, selected, currenttext, back);
10504         FillOSDMenuNavigate (ctx, osd, cat, selected, currenttext, back);
10505         FillOSDMenuSchedule (ctx, osd, cat, selected, currenttext, back);
10506         FillOSDMenuSource   (ctx, osd, cat, selected, currenttext, back);
10507         FillOSDMenuJobs     (ctx, osd, cat, selected, currenttext, back);
10508 
10509         if (!currenttext.isEmpty())
10510             osd->DialogSetText(currenttext);
10511         if (!back.isEmpty() && !category.isEmpty())
10512             osd->DialogBack(cat, QString("DIALOG_MENU_%1_0").arg(back));
10513     }
10514     ReturnOSDLock(ctx, osd);
10515 }
10516 
10517 void TV::FillOSDMenuAudio(const PlayerContext *ctx, OSD *osd,
10518                           QString category, const QString selected,
10519                           QString &currenttext, QString &backaction)
10520 {
10521     QStringList tracks;
10522     uint curtrack = ~0;
10523     bool avsync = true;
10524     bool visual = false;
10525     QString active = QString("");
10526     bool upmixing = false;
10527     bool canupmix = false;
10528     ctx->LockDeletePlayer(__FILE__, __LINE__);
10529     if (ctx->player)
10530     {
10531         visual = ctx->player->CanVisualise();
10532         active = ctx->player->GetVisualiserName();
10533         tracks = ctx->player->GetTracks(kTrackTypeAudio);
10534         if (!tracks.empty())
10535             curtrack = (uint) ctx->player->GetTrack(kTrackTypeAudio);
10536         avsync = (ctx->player->GetTrackCount(kTrackTypeVideo) > 0) &&
10537                   !tracks.empty();
10538         upmixing = ctx->player->GetAudio()->IsUpmixing();
10539         canupmix = ctx->player->GetAudio()->CanUpmix();
10540     }
10541     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
10542 
10543     if (tracks.empty()) // No audio
10544         return;
10545 
10546     if (category == "MAIN")
10547     {
10548         osd->DialogAddButton(tr("Audio"), "DIALOG_MENU_AUDIO_0",
10549                              true, selected == "AUDIO");
10550     }
10551     else if (category == "AUDIO")
10552     {
10553         // TODO Add mute and volume
10554         backaction = "MAIN";
10555         currenttext = tr("Audio");
10556         if (tracks.size() > 1)
10557         {
10558             osd->DialogAddButton(tr("Select Audio Track"),
10559                                  "DIALOG_MENU_AUDIOTRACKS_0", true,
10560                                  selected == "AUDIOTRACKS");
10561         }
10562         if (avsync)
10563             osd->DialogAddButton(tr("Adjust Audio Sync"), ACTION_TOGGELAUDIOSYNC);
10564         if (visual)
10565         {
10566             osd->DialogAddButton(tr("Visualisation"),
10567                                  "DIALOG_MENU_VISUALISATIONS_0", true,
10568                                  selected == "VISUALISATIONS");
10569         }
10570 
10571         if (canupmix)
10572         {
10573             if (upmixing)
10574             {
10575                 osd->DialogAddButton(tr("Disable Audio Upmixer"),
10576                                      ACTION_DISABLEUPMIX);
10577             }
10578             else
10579             {
10580                 osd->DialogAddButton(tr("Enable Audio Upmixer"),
10581                                      ACTION_ENABLEUPMIX);
10582             }
10583         }
10584 
10585     }
10586     else if (category == "AUDIOTRACKS")
10587     {
10588         backaction  = "AUDIO";
10589         currenttext = tr("Select Audio Track");
10590         for (uint i = 0; i < (uint)tracks.size(); i++)
10591         {
10592             osd->DialogAddButton(tracks[i],
10593                                  "SELECTAUDIO_" + QString::number(i),
10594                                  false, i == curtrack);
10595         }
10596     }
10597     else if (category == "VISUALISATIONS")
10598     {
10599         backaction = "AUDIO";
10600         currenttext = tr("Visualisation");
10601         osd->DialogAddButton(tr("None"),
10602                              ACTION_DISABLEVISUALISATION, false,
10603                              active.isEmpty());
10604         QStringList visualisers;
10605         ctx->LockDeletePlayer(__FILE__, __LINE__);
10606         if (ctx->player)
10607             visualisers = ctx->player->GetVisualiserList();
10608         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
10609         for (int i = 0; i < visualisers.size(); i++)
10610         {
10611             osd->DialogAddButton(visualisers[i],
10612                 "VISUALISER_" + visualisers[i], false,
10613                 active == visualisers[i]);
10614         }
10615     }
10616 }
10617 
10618 void TV::FillOSDMenuVideo(const PlayerContext *ctx, OSD *osd,
10619                           QString category, const QString selected,
10620                           QString &currenttext, QString &backaction)
10621 {
10622     QStringList tracks;
10623     //uint curtrack                     = ~0;
10624     uint sup                          = kPictureAttributeSupported_None;
10625     bool studio_levels                = false;
10626     bool autodetect                   = false;
10627     AdjustFillMode adjustfill         = kAdjustFill_Off;
10628     AspectOverrideMode aspectoverride = kAspect_Off;
10629     FrameScanType scan_type           = kScan_Ignore;
10630     bool scan_type_locked             = false;
10631     bool stereoallowed                = false;
10632     StereoscopicMode stereomode       = kStereoscopicModeNone;
10633 
10634     ctx->LockDeletePlayer(__FILE__, __LINE__);
10635     if (ctx->player)
10636     {
10637         tracks           = ctx->player->GetTracks(kTrackTypeVideo);
10638         aspectoverride   = ctx->player->GetAspectOverride();
10639         adjustfill       = ctx->player->GetAdjustFill();
10640         scan_type        = ctx->player->GetScanType();
10641         scan_type_locked = ctx->player->IsScanTypeLocked();
10642         //if (!tracks.empty())
10643         //    curtrack = (uint) ctx->player->GetTrack(kTrackTypeVideo);
10644         VideoOutput *vo = ctx->player->GetVideoOutput();
10645         if (vo)
10646         {
10647             sup = vo->GetSupportedPictureAttributes();
10648             studio_levels = vo->GetPictureAttribute(kPictureAttribute_StudioLevels) > 0;
10649             autodetect = !vo->hasHWAcceleration();
10650             stereoallowed = vo->StereoscopicModesAllowed();
10651             stereomode = vo->GetStereoscopicMode();
10652         }
10653     }
10654     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
10655 
10656     if (tracks.empty()) // No video
10657         return;
10658 
10659     if (category == "MAIN")
10660     {
10661         osd->DialogAddButton(tr("Video"), "DIALOG_MENU_VIDEO_0",
10662                              true, selected == "VIDEO");
10663     }
10664     else if (category == "VIDEO")
10665     {
10666         // TODO Add deinterlacer, filters, video track
10667         backaction = "MAIN";
10668         currenttext = tr("Video");
10669         if (ctx == GetPlayer(ctx, 0))
10670         {
10671             osd->DialogAddButton(tr("Change Aspect Ratio"),
10672                                  "DIALOG_MENU_VIDEOASPECT_0", true,
10673                                  selected == "VIDEOASPECT");
10674             osd->DialogAddButton(tr("Adjust Fill"),
10675                                  "DIALOG_MENU_ADJUSTFILL_0", true,
10676                                  selected == "ADJUSTFILL");
10677             osd->DialogAddButton(tr("Manual Zoom Mode"), "TOGGLEMANUALZOOM");
10678             if (sup != kPictureAttributeSupported_None)
10679             {
10680                 osd->DialogAddButton(tr("Adjust Picture"),
10681                                      "DIALOG_MENU_ADJUSTPICTURE_0", true,
10682                                      selected == "ADJUSTPICTURE");
10683             }
10684         }
10685         if (stereoallowed)
10686         {
10687             osd->DialogAddButton(tr("3D"), "DIALOG_MENU_3D_0",
10688                                  true, selected == "3D");
10689         }
10690         osd->DialogAddButton(tr("Advanced"), "DIALOG_MENU_ADVANCEDVIDEO_0",
10691                              true, selected == "ADVANCEDVIDEO");
10692     }
10693     else if (category == "VIDEOASPECT")
10694     {
10695         backaction = "VIDEO";
10696         currenttext = tr("Change Aspect Ratio");
10697 
10698         for (int j = kAspect_Off; j < kAspect_END; j++)
10699         {
10700             // swap 14:9 and 16:9
10701             int i = ((kAspect_14_9 == j) ? kAspect_16_9 :
10702                      ((kAspect_16_9 == j) ? kAspect_14_9 : j));
10703             osd->DialogAddButton(toString((AspectOverrideMode) i),
10704                                  QString("TOGGLEASPECT%1").arg(i), false,
10705                                  aspectoverride == i);
10706         }
10707     }
10708     else if (category == "ADJUSTFILL")
10709     {
10710         backaction = "VIDEO";
10711         currenttext = tr("Adjust Fill");
10712 
10713         if (autodetect)
10714         {
10715             osd->DialogAddButton(tr("Auto Detect"), "AUTODETECT_FILL",
10716                  false, (adjustfill == kAdjustFill_AutoDetect_DefaultHalf) ||
10717                         (adjustfill == kAdjustFill_AutoDetect_DefaultOff));
10718         }
10719         for (int i = kAdjustFill_Off; i < kAdjustFill_END; i++)
10720         {
10721             osd->DialogAddButton(toString((AdjustFillMode) i),
10722                                  QString("TOGGLEFILL%1").arg(i), false,
10723                                  adjustfill == i);
10724         }
10725     }
10726     else if (category == "ADJUSTPICTURE")
10727     {
10728         backaction = "VIDEO";
10729         currenttext = tr("Adjust Picture");
10730         for (int i = kPictureAttribute_MIN; i < kPictureAttribute_MAX; i++)
10731         {
10732             if (toMask((PictureAttribute)i) & sup)
10733             {
10734                 if ((PictureAttribute)i == kPictureAttribute_StudioLevels)
10735                 {
10736                     QString msg = studio_levels ? tr("Disable studio levels") :
10737                                                   tr("Enable studio levels");
10738                     osd->DialogAddButton(msg, ACTION_TOGGLESTUDIOLEVELS);
10739                 }
10740                 else
10741                 {
10742                     osd->DialogAddButton(toString((PictureAttribute) i),
10743                                    QString("TOGGLEPICCONTROLS%1").arg(i));
10744                 }
10745             }
10746         }
10747         osd->DialogAddButton(
10748             gCoreContext->GetNumSetting("NightModeEnabled", 0) ?
10749             tr("Disable Night Mode") : tr("Enable Night Mode"),
10750             ACTION_TOGGLENIGHTMODE);
10751     }
10752     else if (category == "3D")
10753     {
10754         backaction = "VIDEO";
10755         currenttext = tr("3D");
10756         osd->DialogAddButton(tr("None"),
10757                              ACTION_3DNONE, false,
10758                              stereomode == kStereoscopicModeNone);
10759         osd->DialogAddButton(tr("Side by Side"),
10760                              ACTION_3DSIDEBYSIDE, false,
10761                              stereomode == kStereoscopicModeSideBySide);
10762         osd->DialogAddButton(tr("Discard Side by Side"),
10763                              ACTION_3DSIDEBYSIDEDISCARD, false,
10764                              stereomode == kStereoscopicModeSideBySideDiscard);
10765         osd->DialogAddButton(tr("Top and Bottom"),
10766                              ACTION_3DTOPANDBOTTOM, false,
10767                              stereomode == kStereoscopicModeTopAndBottom);
10768         osd->DialogAddButton(tr("Discard Top and Bottom"),
10769                              ACTION_3DTOPANDBOTTOMDISCARD, false,
10770                              stereomode == kStereoscopicModeTopAndBottomDiscard);
10771     }
10772     else if (category == "ADVANCEDVIDEO")
10773     {
10774         osd->DialogAddButton(tr("Video Scan"),
10775                              "DIALOG_MENU_VIDEOSCAN_0", true,
10776                              selected == "VIDEOSCAN");
10777         if (kScan_Progressive != scan_type)
10778         {
10779             osd->DialogAddButton(tr("Deinterlacer"),
10780                                  "DIALOG_MENU_DEINTERLACER_0", true,
10781                                  selected == "DEINTERLACER");
10782         }
10783         backaction = "VIDEO";
10784         currenttext = tr("Advanced");
10785     }
10786     else if (category == "DEINTERLACER")
10787     {
10788         backaction = "ADVANCEDVIDEO";
10789         currenttext = tr("Deinterlacer");
10790 
10791         QStringList deinterlacers;
10792         QString     currentdeinterlacer;
10793         bool        doublerate = false;
10794         ctx->LockDeletePlayer(__FILE__, __LINE__);
10795         if (ctx->player && ctx->player->GetVideoOutput())
10796         {
10797             ctx->player->GetVideoOutput()->GetDeinterlacers(deinterlacers);
10798             currentdeinterlacer = ctx->player->GetVideoOutput()->GetDeinterlacer();
10799             doublerate = ctx->player->CanSupportDoubleRate();
10800         }
10801         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
10802 
10803         foreach (QString deint, deinterlacers)
10804         {
10805             if ((deint.contains("doublerate") ||
10806                 deint.contains("doubleprocess") ||
10807                 deint.contains("bobdeint")) && !doublerate)
10808             {
10809                 continue;
10810             }
10811             QString trans = VideoDisplayProfile::GetDeinterlacerName(deint);
10812             osd->DialogAddButton(trans, "DEINTERLACER_" + deint, false,
10813                                  deint == currentdeinterlacer);
10814         }
10815     }
10816     else if (category == "VIDEOSCAN")
10817     {
10818         backaction = "ADVANCEDVIDEO";
10819         currenttext = tr("Video Scan");
10820 
10821         QString cur_mode = "";
10822         if (!scan_type_locked)
10823         {
10824             if (kScan_Interlaced == scan_type)
10825                 cur_mode = tr("(I)", "Interlaced (Normal)");
10826             else if (kScan_Intr2ndField == scan_type)
10827                 cur_mode = tr("(i)", "Interlaced (Reversed)");
10828             else if (kScan_Progressive == scan_type)
10829                 cur_mode = tr("(P)", "Progressive");
10830             cur_mode = " " + cur_mode;
10831             scan_type = kScan_Detect;
10832         }
10833 
10834         osd->DialogAddButton(tr("Detect") + cur_mode, "SELECTSCAN_0", false,
10835                              scan_type == kScan_Detect);
10836         osd->DialogAddButton(tr("Progressive"), "SELECTSCAN_3", false,
10837                              scan_type == kScan_Progressive);
10838         osd->DialogAddButton(tr("Interlaced (Normal)"), "SELECTSCAN_1", false,
10839                              scan_type == kScan_Interlaced);
10840         osd->DialogAddButton(tr("Interlaced (Reversed)"), "SELECTSCAN_2", false,
10841                              scan_type == kScan_Intr2ndField);
10842     }
10843 }
10844 
10845 void TV::FillOSDMenuSubtitles(const PlayerContext *ctx, OSD *osd,
10846                               QString category, const QString selected,
10847                               QString &currenttext, QString &backaction)
10848 {
10849     uint capmode  = 0;
10850     QStringList av_tracks;
10851     QStringList cc708_tracks;
10852     QStringList cc608_tracks;
10853     QStringList ttx_tracks;
10854     QStringList ttm_tracks;
10855     QStringList text_tracks;
10856     uint av_curtrack    = ~0;
10857     uint cc708_curtrack = ~0;
10858     uint cc608_curtrack = ~0;
10859     uint ttx_curtrack   = ~0;
10860     uint text_curtrack  = ~0;
10861     bool havetext = false;
10862     bool forcedon = true;
10863     bool enabled  = false;
10864     ctx->LockDeletePlayer(__FILE__, __LINE__);
10865     if (ctx->player)
10866     {
10867         capmode      = ctx->player->GetCaptionMode();
10868         enabled      = ctx->player->GetCaptionsEnabled();
10869         havetext     = ctx->player->HasTextSubtitles();
10870         forcedon     = ctx->player->GetAllowForcedSubtitles();
10871         av_tracks    = ctx->player->GetTracks(kTrackTypeSubtitle);
10872         cc708_tracks = ctx->player->GetTracks(kTrackTypeCC708);
10873         cc608_tracks = ctx->player->GetTracks(kTrackTypeCC608);
10874         ttx_tracks   = ctx->player->GetTracks(kTrackTypeTeletextCaptions);
10875         ttm_tracks   = ctx->player->GetTracks(kTrackTypeTeletextMenu);
10876         text_tracks  = ctx->player->GetTracks(kTrackTypeRawText);
10877         if (!av_tracks.empty())
10878             av_curtrack = (uint) ctx->player->GetTrack(kTrackTypeSubtitle);
10879         if (!cc708_tracks.empty())
10880             cc708_curtrack = (uint) ctx->player->GetTrack(kTrackTypeCC708);
10881         if (!cc608_tracks.empty())
10882             cc608_curtrack = (uint) ctx->player->GetTrack(kTrackTypeCC608);
10883         if (!ttx_tracks.empty())
10884             ttx_curtrack = (uint) ctx->player->GetTrack(kTrackTypeTeletextCaptions);
10885         if (!text_tracks.empty())
10886             text_curtrack = (uint) ctx->player->GetTrack(kTrackTypeRawText);
10887     }
10888     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
10889 
10890     bool have_subs = !av_tracks.empty() || havetext || !cc708_tracks.empty() ||
10891                      !cc608_tracks.empty() || !ttx_tracks.empty() ||
10892                      !text_tracks.empty();
10893 
10894     if (category == "MAIN")
10895     {
10896         if (have_subs || !ttm_tracks.empty())
10897         {
10898             osd->DialogAddButton(tr("Subtitles"),
10899                                  "DIALOG_MENU_SUBTITLES_0",
10900                                  true, selected == "SUBTITLES");
10901         }
10902     }
10903     else if (category == "SUBTITLES")
10904     {
10905         backaction = "MAIN";
10906         currenttext = tr("Subtitles");
10907 
10908         if (have_subs && enabled)
10909             osd->DialogAddButton(tr("Disable Subtitles"), ACTION_DISABLESUBS);
10910         else if (have_subs && !enabled)
10911             osd->DialogAddButton(tr("Enable Subtitles"), ACTION_ENABLESUBS);
10912         if (!av_tracks.empty())
10913         {
10914             if (forcedon)
10915             {
10916                 osd->DialogAddButton(tr("Disable Forced Subtitles"),
10917                                      ACTION_DISABLEFORCEDSUBS);
10918             }
10919             else
10920             {
10921                 osd->DialogAddButton(tr("Enable Forced Subtitles"),
10922                                      ACTION_ENABLEFORCEDSUBS);
10923             }
10924             osd->DialogAddButton(tr("Select Subtitle"),
10925                                  "DIALOG_MENU_AVSUBTITLES_0",
10926                                  true, selected == "AVSUBTITLES");
10927         }
10928         if (havetext || !text_tracks.empty())
10929         {
10930             osd->DialogAddButton(tr("Text Subtitles"),
10931                                  "DIALOG_MENU_TEXTSUBTITLES_0",
10932                                  true, selected == "TEXTSUBTITLES");
10933         }
10934         if (!cc708_tracks.empty())
10935         {
10936             osd->DialogAddButton(tr("Select ATSC CC"),
10937                                  "DIALOG_MENU_708SUBTITLES_0",
10938                                  true, selected == "708SUBTITLES");
10939         }
10940         if (!cc608_tracks.empty())
10941         {
10942             osd->DialogAddButton(tr("Select VBI CC"),
10943                                  "DIALOG_MENU_608SUBTITLES_0",
10944                                  true, selected == "608SUBTITLES");
10945         }
10946         if (!ttx_tracks.empty())
10947         {
10948             osd->DialogAddButton(tr("Select Teletext CC"),
10949                                  "DIALOG_MENU_TTXSUBTITLES_0",
10950                                  true, selected == "TTXSUBTITLES");
10951         }
10952         if (!ttm_tracks.empty())
10953             osd->DialogAddButton(tr("Toggle Teletext Menu"), "TOGGLETTM");
10954         if (enabled)
10955             osd->DialogAddButton(tr("Adjust Subtitle Zoom"),
10956                                  ACTION_TOGGLESUBTITLEZOOM);
10957     }
10958     else if (category == "AVSUBTITLES")
10959     {
10960         backaction = "SUBTITLES";
10961         currenttext = tr("Select Subtitle");
10962         for (uint i = 0; i < (uint)av_tracks.size(); i++)
10963         {
10964             osd->DialogAddButton(av_tracks[i],
10965                                  "SELECTSUBTITLE_" + QString::number(i),
10966                                  false, i == av_curtrack);
10967         }
10968     }
10969     else if (category == "TEXTSUBTITLES")
10970     {
10971         backaction = "SUBTITLES";
10972         currenttext = tr("Text Subtitles");
10973         if (havetext)
10974         {
10975             if (capmode == kDisplayTextSubtitle)
10976             {
10977                 osd->DialogAddButton(tr("Disable External Subtitles"),
10978                                      ACTION_DISABLEEXTTEXT);
10979             }
10980             else
10981             {
10982                 osd->DialogAddButton(tr("Enable External Subtitles"),
10983                                      ACTION_ENABLEEXTTEXT);
10984             }
10985         }
10986         if (!text_tracks.empty())
10987         {
10988             for (uint i = 0; i < (uint)text_tracks.size(); i++)
10989             {
10990                 osd->DialogAddButton(text_tracks[i],
10991                                      "SELECTRAWTEXT_" + QString::number(i),
10992                                      false, i == text_curtrack);
10993             }
10994         }
10995     }
10996     else if (category == "708SUBTITLES")
10997     {
10998         backaction = "SUBTITLES";
10999         currenttext = tr("Select ATSC CC");
11000         for (uint i = 0; i < (uint)cc708_tracks.size(); i++)
11001         {
11002             osd->DialogAddButton(cc708_tracks[i],
11003                                  "SELECTCC708_" + QString::number(i),
11004                                  false, i == cc708_curtrack);
11005         }
11006     }
11007     else if (category == "608SUBTITLES")
11008     {
11009         backaction = "SUBTITLES";
11010         currenttext = tr("Select VBI CC");
11011         for (uint i = 0; i < (uint)cc608_tracks.size(); i++)
11012         {
11013             osd->DialogAddButton(cc608_tracks[i],
11014                                  "SELECTCC608_" + QString::number(i),
11015                                  false, i == cc608_curtrack);
11016         }
11017     }
11018     else if (category == "TTXSUBTITLES")
11019     {
11020         backaction = "SUBTITLES";
11021         currenttext = tr("Select Teletext CC");
11022         for (uint i = 0; i < (uint)ttx_tracks.size(); i++)
11023         {
11024             osd->DialogAddButton(ttx_tracks[i],
11025                                  "SELECTTTC_" + QString::number(i),
11026                                  false, i == ttx_curtrack);
11027         }
11028     }
11029 }
11030 
11031 void TV::FillOSDMenuNavigate(const PlayerContext *ctx, OSD *osd,
11032                              QString category, const QString selected,
11033                              QString &currenttext, QString &backaction)
11034 {
11035     int num_chapters  = GetNumChapters(ctx);
11036     int num_titles    = GetNumTitles(ctx);
11037     int num_angles    = GetNumAngles(ctx);
11038     TVState state     = ctx->GetState();
11039     bool isdvd        = state == kState_WatchingDVD;
11040     bool isbd         = ctx->buffer && ctx->buffer->IsBD() &&
11041                         ctx->buffer->BD()->IsHDMVNavigation();
11042     bool islivetv     = StateIsLiveTV(state);
11043     bool isrecording  = state == kState_WatchingPreRecorded ||
11044                         state == kState_WatchingRecording;
11045     bool previouschan = false;
11046     if (islivetv)
11047     {
11048         QString prev_channum = ctx->GetPreviousChannel();
11049         QString cur_channum  = QString();
11050         if (ctx->tvchain)
11051             cur_channum = ctx->tvchain->GetChannelName(-1);
11052         if (!prev_channum.isEmpty() && prev_channum != cur_channum)
11053             previouschan = true;
11054     }
11055 
11056     bool jump = !num_chapters && !isdvd && !isbd &&
11057                 ctx->buffer->IsSeekingAllowed();
11058     bool show = isdvd || num_chapters || num_titles || previouschan ||
11059                 isrecording || num_angles || jump;
11060 
11061     if (category == "MAIN")
11062     {
11063         if (show)
11064         {
11065             osd->DialogAddButton(tr("Navigate"), "DIALOG_MENU_NAVIGATE_0",
11066                                  true, selected == "NAVIGATE");
11067         }
11068     }
11069     else if (category == "NAVIGATE")
11070     {
11071         backaction = "MAIN";
11072         currenttext = tr("Navigate");
11073         if (jump)
11074         {
11075             osd->DialogAddButton(tr("Jump Ahead"), ACTION_JUMPFFWD, false, false);
11076             osd->DialogAddButton(tr("Jump Back"), ACTION_JUMPRWND, false, false);
11077         }
11078         if (isrecording)
11079         {
11080             osd->DialogAddButton(tr("Commercial Auto-Skip"),
11081                                  "DIALOG_MENU_COMMSKIP_0",
11082                                  true, selected == "COMMSKIP");
11083         }
11084         if (isbd)
11085         {
11086             osd->DialogAddButton(tr("Top menu"), ACTION_JUMPTODVDROOTMENU);
11087             osd->DialogAddButton(tr("Popup menu"), ACTION_JUMPTOPOPUPMENU);
11088         }
11089         if (isdvd)
11090         {
11091             osd->DialogAddButton(tr("DVD Root Menu"),    ACTION_JUMPTODVDROOTMENU);
11092             osd->DialogAddButton(tr("DVD Title Menu"),   ACTION_JUMPTODVDTITLEMENU);
11093             osd->DialogAddButton(tr("DVD Chapter Menu"), ACTION_JUMPTODVDCHAPTERMENU);
11094         }
11095         if (previouschan)
11096         {
11097             osd->DialogAddButton(tr("Previous Channel"), "PREVCHAN");
11098         }
11099         if (num_chapters)
11100         {
11101             osd->DialogAddButton(tr("Chapter"), "DIALOG_MENU_AVCHAPTER_0",
11102                                  true, selected == "AVCHAPTER");
11103         }
11104         if (num_angles > 1)
11105         {
11106             osd->DialogAddButton(tr("Angle"), "DIALOG_MENU_AVANGLE_0",
11107                                  true, selected == "AVANGLE");
11108         }
11109         if (num_titles)
11110         {
11111             osd->DialogAddButton(tr("Title"), "DIALOG_MENU_AVTITLE_0",
11112                                  true, selected == "AVTITLE");
11113         }
11114     }
11115     else if (category == "AVCHAPTER")
11116     {
11117         backaction = "NAVIGATE";
11118         currenttext = tr("Chapter");
11119         int current_chapter =  GetCurrentChapter(ctx);
11120         QList<long long> times;
11121         GetChapterTimes(ctx, times);
11122         if (num_chapters == times.size())
11123         {
11124             int size = QString::number(num_chapters).size();
11125             for (int i = 0; i < num_chapters; i++)
11126             {
11127                 int hours   = times[i] / 60 / 60;
11128                 int minutes = (times[i] / 60) - (hours * 60);
11129                 int secs    = times[i] % 60;
11130                 QString chapter1 = QString("%1").arg(i+1, size, 10, QChar(48));
11131                 QString chapter2 = QString("%1").arg(i+1, 3   , 10, QChar(48));
11132                 QString desc = chapter1 + QString(" (%1:%2:%3)")
11133                     .arg(hours, 2, 10, QChar(48)).arg(minutes, 2, 10, QChar(48))
11134                     .arg(secs, 2, 10, QChar(48));
11135                 osd->DialogAddButton(desc, ACTION_JUMPCHAPTER + chapter2,
11136                                      false, current_chapter == (i + 1));
11137             }
11138         }
11139     }
11140     else if (category == "AVTITLE")
11141     {
11142         backaction = "NAVIGATE";
11143         currenttext = tr("Title");
11144         int current_title = GetCurrentTitle(ctx);
11145 
11146         for (int i = 0; i < num_titles; i++)
11147         {
11148             if (GetTitleDuration(ctx, i) < 120) // Ignore < 2 minutes long
11149                 continue;
11150 
11151             QString titleIdx = QString("%1").arg(i, 3, 10, QChar(48));
11152             QString desc = GetTitleName(ctx, i);
11153             osd->DialogAddButton(desc, ACTION_SWITCHTITLE + titleIdx,
11154                                  false, current_title == i);
11155         }
11156     }
11157     else if (category == "AVANGLE")
11158     {
11159         backaction = "NAVIGATE";
11160         currenttext = tr("Angle");
11161         int current_angle = GetCurrentAngle(ctx);
11162 
11163         for (int i = 0; i < num_angles; i++)
11164         {
11165             QString angleIdx = QString("%1").arg(i, 3, 10, QChar(48));
11166             QString desc = GetAngleName(ctx, i);
11167             osd->DialogAddButton(desc, ACTION_SWITCHANGLE + angleIdx,
11168                                  false, current_angle == i);
11169         }
11170     }
11171     else if (category == "COMMSKIP")
11172     {
11173         backaction = "NAVIGATE";
11174         currenttext = tr("Commercial Auto-Skip");
11175         uint cas_ord[] = { 0, 2, 1 };
11176         ctx->LockDeletePlayer(__FILE__, __LINE__);
11177         CommSkipMode cur = kCommSkipOff;
11178         if (ctx->player)
11179             cur = ctx->player->GetAutoCommercialSkip();
11180         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
11181 
11182         for (uint i = 0; i < sizeof(cas_ord)/sizeof(uint); i++)
11183         {
11184             const CommSkipMode mode = (CommSkipMode) cas_ord[i];
11185             osd->DialogAddButton(toString((CommSkipMode) cas_ord[i]),
11186                                  QString("TOGGLECOMMSKIP%1").arg(cas_ord[i]),
11187                                  false, mode == cur);
11188         }
11189     }
11190 }
11191 
11192 void TV::FillOSDMenuSource(const PlayerContext *ctx, OSD *osd,
11193                            QString category, const QString selected,
11194                            QString &currenttext, QString &backaction)
11195 {
11196     QMap<uint,InputInfo> sources;
11197     vector<uint> cardids;
11198     uint cardid = 0;
11199     vector<uint> excluded_cardids;
11200     uint sourceid = 0;
11201 
11202     if ((category == "SOURCE" || category == "INPUTSWITCHING"||
11203          category == "SOURCESWITCHING") && ctx->recorder)
11204     {
11205         cardids = CardUtil::GetCardList();
11206         cardid  = ctx->GetCardID();
11207         // The cardids are already in the preferred order.  Don't
11208         // alter it if switching sources.
11209         if (category != "SOURCESWITCHING")
11210             stable_sort(cardids.begin(), cardids.end());
11211         excluded_cardids.push_back(cardid);
11212         InfoMap info;
11213         ctx->recorder->GetChannelInfo(info);
11214         sourceid = info["sourceid"].toUInt();
11215 
11216         if (category != "INPUTSWITCHING")
11217         {
11218             // Get sources available on other cards
11219             vector<uint>::const_iterator it = cardids.begin();
11220             for (; it != cardids.end(); ++it)
11221             {
11222                 vector<InputInfo> inputs =
11223                         RemoteRequestFreeInputList(*it, excluded_cardids);
11224                 if (inputs.empty())
11225                     continue;
11226 
11227                 for (uint i = 0; i < inputs.size(); i++)
11228                     if (!sources.contains(inputs[i].sourceid))
11229                        sources[inputs[i].sourceid] = inputs[i];
11230             }
11231             // Get other sources available on this card
11232             vector<uint> currentinputs = CardUtil::GetInputIDs(cardid);
11233             if (!currentinputs.empty())
11234             {
11235                 for (uint i = 0; i < currentinputs.size(); i++)
11236                 {
11237                     InputInfo info;
11238                     info.inputid = currentinputs[i];
11239                     if (CardUtil::GetInputInfo(info))
11240                         if (!sources.contains(info.sourceid) &&
11241                             info.livetvorder)
11242                             sources[info.sourceid] = info;
11243                 }
11244             }
11245             // delete current source from list
11246             sources.remove(sourceid);
11247         }
11248     }
11249 
11250     if (category == "MAIN")
11251     {
11252         osd->DialogAddButton(tr("Source"), "DIALOG_MENU_SOURCE_0",
11253                              true, selected == "SOURCE");
11254     }
11255     else if (category == "SOURCE")
11256     {
11257         backaction = "MAIN";
11258         currenttext = tr("Source");
11259         osd->DialogAddButton(tr("Jump to Program"),
11260                              "DIALOG_MENU_" + ACTION_JUMPREC + "_0",
11261                              true, selected == ACTION_JUMPREC);
11262 
11263         vector<uint>::const_iterator it = cardids.begin();
11264         for (; it != cardids.end(); ++it)
11265         {
11266             vector<InputInfo> inputs = RemoteRequestFreeInputList(
11267                 *it, excluded_cardids);
11268             uint testsize = 0;
11269             for (uint i = 0; i < inputs.size(); i++)
11270             {
11271                 if ((inputs[i].cardid   == cardid) &&
11272                     (inputs[i].sourceid == sourceid))
11273                 {
11274                     testsize = 1;
11275                     break;
11276                 }
11277             }
11278             if (inputs.size() <= testsize)
11279                 continue;
11280             osd->DialogAddButton(tr("Switch Input"),
11281                                  "DIALOG_MENU_INPUTSWITCHING_0",
11282                                  true, selected == "INPUTSWITCHING");
11283             break;
11284         }
11285         if (!sources.empty())
11286         {
11287             osd->DialogAddButton(tr("Switch Source"),
11288                                  "DIALOG_MENU_SOURCESWITCHING_0",
11289                                  true, selected == "SOURCESWITCHING");
11290         }
11291     }
11292     else if (category == ACTION_JUMPREC)
11293     {
11294         backaction = "SOURCE";
11295         currenttext = tr("Jump to Program");
11296         osd->DialogAddButton(tr("Recorded Program"),
11297                              "DIALOG_" + ACTION_JUMPREC + "_X_0",
11298                              true, selected == ACTION_JUMPREC + "2");
11299         if (lastProgram != NULL)
11300         {
11301             if (lastProgram->GetSubtitle().isEmpty())
11302                 osd->DialogAddButton(lastProgram->GetTitle(), ACTION_JUMPPREV);
11303             else
11304                 osd->DialogAddButton(QString("%1: %2")
11305                         .arg(lastProgram->GetTitle())
11306                         .arg(lastProgram->GetSubtitle()), ACTION_JUMPPREV);
11307         }
11308     }
11309     else if (category == "INPUTSWITCHING")
11310     {
11311         backaction = "SOURCE";
11312         currenttext = tr("Switch Input");
11313         vector<uint>::const_iterator it = cardids.begin();
11314         for (; it != cardids.end(); ++it)
11315         {
11316             vector<InputInfo> inputs = RemoteRequestFreeInputList(
11317                 *it, excluded_cardids);;
11318 
11319             for (uint i = 0; i < inputs.size(); i++)
11320             {
11321                 // don't add current input to list
11322                 if ((inputs[i].cardid   == cardid) &&
11323                     (inputs[i].sourceid == sourceid))
11324                 {
11325                     continue;
11326                 }
11327 
11328                 QString name = CardUtil::GetDisplayName(inputs[i].inputid);
11329                 if (name.isEmpty())
11330                 {
11331                     name = tr("C", "Card") + ":" + QString::number(*it) + " " +
11332                         tr("I", "Input") + ":" + inputs[i].name;
11333                 }
11334 
11335                 osd->DialogAddButton(name,
11336                     QString("SWITCHTOINPUT_%1").arg(inputs[i].inputid));
11337             }
11338         }
11339     }
11340     else if (category == "SOURCESWITCHING")
11341     {
11342         backaction = "SOURCE";
11343         currenttext = tr("Switch Source");
11344         QMap<uint,InputInfo>::const_iterator sit = sources.begin();
11345         for (; sit != sources.end(); ++sit)
11346         {
11347             osd->DialogAddButton(SourceUtil::GetSourceName((*sit).sourceid),
11348                                  QString("SWITCHTOINPUT_%1").arg((*sit).inputid));
11349         }
11350     }
11351 }
11352 
11353 void TV::FillOSDMenuJobs(const PlayerContext *ctx, OSD *osd,
11354                          QString category, const QString selected,
11355                          QString &currenttext, QString &backaction)
11356 {
11357     TVState state    = ctx->GetState();
11358     bool islivetv    = StateIsLiveTV(state);
11359     bool isrecorded  = state == kState_WatchingPreRecorded;
11360     bool isrecording = state == kState_WatchingRecording;
11361 
11362     if (category == "MAIN")
11363     {
11364         if (islivetv || isrecording || isrecorded)
11365         {
11366             osd->DialogAddButton(tr("Jobs"), "DIALOG_MENU_JOBS_0",
11367                                  true, selected == "JOBS");
11368         }
11369     }
11370     else if (category == "JOBS")
11371     {
11372         backaction = "MAIN";
11373         currenttext = tr("Jobs");
11374 
11375         ctx->LockPlayingInfo(__FILE__, __LINE__);
11376         bool is_on = ctx->playingInfo->QueryAutoExpire() != kDisableAutoExpire;
11377         bool transcoding = JobQueue::IsJobQueuedOrRunning(
11378                                 JOB_TRANSCODE,
11379                                 ctx->playingInfo->GetChanID(),
11380                                 ctx->playingInfo->GetScheduledStartTime());
11381         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
11382 
11383         if (islivetv)
11384         {
11385             osd->DialogAddButton(tr("Edit Channel"),   "EDIT");
11386         }
11387 
11388         if (isrecorded || isrecording)
11389         {
11390             osd->DialogAddButton(tr("Edit Recording"), "EDIT");
11391             osd->DialogAddButton(is_on ? tr("Turn Auto-Expire OFF") :
11392                                  tr("Turn Auto-Expire ON"), "TOGGLEAUTOEXPIRE");
11393         }
11394 
11395         if (isrecorded)
11396         {
11397             if (transcoding)
11398             {
11399                 osd->DialogAddButton(tr("Stop Transcoding"), "QUEUETRANSCODE");
11400             }
11401             else
11402             {
11403                 osd->DialogAddButton(tr("Begin Transcoding"),
11404                                      "DIALOG_MENU_TRANSCODE_0",
11405                                      true, selected == "TRANSCODE");
11406             }
11407         }
11408     }
11409     else if (category == "TRANSCODE")
11410     {
11411         backaction = "JOBS";
11412         currenttext = tr("Begin Transcoding");
11413         osd->DialogAddButton(tr("Default"),       "QUEUETRANSCODE");
11414         osd->DialogAddButton(tr("Autodetect"),    "QUEUETRANSCODE_AUTO");
11415         osd->DialogAddButton(tr("High Quality"),  "QUEUETRANSCODE_HIGH");
11416         osd->DialogAddButton(tr("Medium Quality"),"QUEUETRANSCODE_MEDIUM");
11417         osd->DialogAddButton(tr("Low Quality"),   "QUEUETRANSCODE_LOW");
11418     }
11419 }
11420 
11421 void TV::FillOSDMenuPlayback(const PlayerContext *ctx, OSD *osd,
11422                              QString category, const QString selected,
11423                              QString &currenttext, QString &backaction)
11424 {
11425     bool allowPIP = IsPIPSupported(ctx);
11426     bool allowPBP = IsPBPSupported(ctx);
11427     bool ispaused = false;
11428     ctx->LockDeletePlayer(__FILE__, __LINE__);
11429     if (ctx->player)
11430         ispaused = ctx->player->IsPaused();
11431     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
11432 
11433     if (category == "MAIN")
11434     {
11435         osd->DialogAddButton(tr("Playback"), "DIALOG_MENU_PLAYBACK_0",
11436                              true, selected == "PLAYBACK");
11437     }
11438     else if (category == "PLAYBACK")
11439     {
11440         backaction = "MAIN";
11441         currenttext = tr("Playback");
11442 
11443         osd->DialogAddButton(ispaused ? tr("Play") : tr("Pause"),
11444                              ACTION_PAUSE, false, false);
11445         osd->DialogAddButton(tr("Adjust Time Stretch"),
11446                              "DIALOG_MENU_TIMESTRETCH_0", true,
11447                               selected == "TIMESTRETCH");
11448         if (allowPIP || allowPBP)
11449         {
11450             osd->DialogAddButton(tr("Picture-in-Picture"),
11451                                  "DIALOG_MENU_PIP_1", true,
11452                                  selected == "PIP");
11453         }
11454         osd->DialogAddButton(tr("Sleep"),
11455                              "DIALOG_MENU_SLEEP_0", true,
11456                               selected == "SLEEP");
11457         if (db_use_channel_groups)
11458         {
11459             osd->DialogAddButton(tr("Channel Groups"),
11460                                  "DIALOG_MENU_CHANNELGROUP_0",
11461                                  true, selected == "CHANNELGROUP");
11462         }
11463         if (!db_browse_always)
11464             osd->DialogAddButton(tr("Toggle Browse Mode"), "TOGGLEBROWSE");
11465         if (inPlaylist)
11466             osd->DialogAddButton(tr("Cancel Playlist"), "CANCELPLAYLIST");
11467         osd->DialogAddButton(tr("Playback data"),
11468                              ACTION_TOGGLEOSDDEBUG, false, false);
11469     }
11470     else if (category == "TIMESTRETCH")
11471     {
11472         backaction = "PLAYBACK";
11473         currenttext = tr("Adjust Time Stretch");
11474         int speedX100 = (int)(round(ctx->ts_normal * 100));
11475         osd->DialogAddButton(tr("Toggle"), "TOGGLESTRETCH");
11476         osd->DialogAddButton(tr("Adjust"), "ADJUSTSTRETCH");
11477         osd->DialogAddButton(tr("0.5X"), "ADJUSTSTRETCH0.5", false, speedX100 == 50);
11478         osd->DialogAddButton(tr("0.9X"), "ADJUSTSTRETCH0.9", false, speedX100 == 90);
11479         osd->DialogAddButton(tr("1.0X"), "ADJUSTSTRETCH1.0", false, speedX100 == 100);
11480         osd->DialogAddButton(tr("1.1X"), "ADJUSTSTRETCH1.1", false, speedX100 == 110);
11481         osd->DialogAddButton(tr("1.2X"), "ADJUSTSTRETCH1.2", false, speedX100 == 120);
11482         osd->DialogAddButton(tr("1.3X"), "ADJUSTSTRETCH1.3", false, speedX100 == 130);
11483         osd->DialogAddButton(tr("1.4X"), "ADJUSTSTRETCH1.4", false, speedX100 == 140);
11484         osd->DialogAddButton(tr("1.5X"), "ADJUSTSTRETCH1.5", false, speedX100 == 150);
11485     }
11486     else if (category == "SLEEP")
11487     {
11488         backaction = "PLAYBACK";
11489         currenttext = tr("Sleep");
11490         if (sleepTimerId)
11491             osd->DialogAddButton(tr("Sleep Off"), ACTION_TOGGLESLEEP + "ON");
11492         osd->DialogAddButton(tr("%n minute(s)", "", 30),
11493                                 ACTION_TOGGLESLEEP + "30");
11494         osd->DialogAddButton(tr("%n minute(s)", "", 60),
11495                                 ACTION_TOGGLESLEEP + "60");
11496         osd->DialogAddButton(tr("%n minute(s)", "", 90),
11497                                 ACTION_TOGGLESLEEP + "90");
11498         osd->DialogAddButton(tr("%n minute(s)", "", 120),
11499                                 ACTION_TOGGLESLEEP + "120");
11500     }
11501     else if (category == "CHANNELGROUP")
11502     {
11503         backaction = "PLAYBACK";
11504         currenttext = tr("Channel Groups");
11505         osd->DialogAddButton(tr("All Channels"), "CHANGROUP_ALL_CHANNELS");
11506         ChannelGroupList::const_iterator it;
11507         for (it = db_channel_groups.begin();
11508              it != db_channel_groups.end(); ++it)
11509         {
11510             osd->DialogAddButton(it->name,
11511                                  QString("CHANGROUP_%1").arg(it->grpid),
11512                                  false, (int)(it->grpid) == channelGroupId);
11513         }
11514     }
11515     else if (category == "PIP" && (allowPIP || allowPBP))
11516     {
11517         backaction = "PLAYBACK";
11518         currenttext = tr("Picture-in-Picture");
11519         bool hasPBP = (player.size()>1) && GetPlayer(ctx,1)->IsPBP();
11520         bool hasPIP = (player.size()>1) && GetPlayer(ctx,1)->IsPIP();
11521 
11522         if (RemoteGetFreeRecorderCount())
11523         {
11524             if (player.size() <= kMaxPIPCount && !hasPBP && allowPIP)
11525                 osd->DialogAddButton(tr("Open Live TV PIP"), "CREATEPIPVIEW");
11526             if (player.size() < kMaxPBPCount && !hasPIP && allowPBP)
11527                 osd->DialogAddButton(tr("Open Live TV PBP"), "CREATEPBPVIEW");
11528         }
11529 
11530         if (player.size() <= kMaxPIPCount && !hasPBP && allowPIP)
11531             osd->DialogAddButton(tr("Open Recording PIP"), "JUMPRECPIP");
11532         if (player.size() < kMaxPBPCount && !hasPIP && allowPBP)
11533             osd->DialogAddButton(tr("Open Recording PBP"), "JUMPRECPBP");
11534 
11535         if (player.size() > 1)
11536         {
11537             osd->DialogAddButton(tr("Change Active Window"), "NEXTPIPWINDOW");
11538 
11539             QString pipType  = (ctx->IsPBP()) ? "PBP" : "PIP";
11540             QString toggleMode = QString("TOGGLE%1MODE").arg(pipType);
11541 
11542             bool isPBP = ctx->IsPBP();
11543             const PlayerContext *mctx = GetPlayer(ctx, 0);
11544             QString pipClose = (isPBP) ? tr("Close PBP") : tr("Close PIP");
11545             if (mctx == ctx)
11546             {
11547                 if (player.size() > 2)
11548                     pipClose = (isPBP) ? tr("Close PBPs") : tr("Close PIPs");
11549                 osd->DialogAddButton(pipClose, toggleMode);
11550 
11551                 if (player.size() == 2)
11552                     osd->DialogAddButton(tr("Swap Windows"), "SWAPPIP");
11553             }
11554             else
11555             {
11556                 osd->DialogAddButton(pipClose, toggleMode);
11557                 osd->DialogAddButton(tr("Swap Windows"), "SWAPPIP");
11558             }
11559 
11560             uint max_cnt = min(kMaxPBPCount, kMaxPIPCount+1);
11561             if (player.size() <= max_cnt &&
11562                 !(hasPIP && !allowPBP) &&
11563                 !(hasPBP && !allowPIP))
11564             {
11565                 QString switchTo = (isPBP) ? tr("Switch to PIP") : tr("Switch to PBP");
11566                 osd->DialogAddButton(switchTo, "TOGGLEPIPSTATE");
11567             }
11568         }
11569     }
11570 }
11571 
11572 void TV::FillOSDMenuSchedule(const PlayerContext *ctx, OSD *osd,
11573                              QString category, const QString selected,
11574                              QString &currenttext, QString &backaction)
11575 {
11576     if (category == "MAIN")
11577     {
11578         osd->DialogAddButton(tr("Schedule"),
11579                              "DIALOG_MENU_SCHEDULE_0",
11580                              true, selected == "SCHEDULE");
11581     }
11582     else if (category == "SCHEDULE")
11583     {
11584         backaction = "MAIN";
11585         currenttext = tr("Schedule");
11586         osd->DialogAddButton(tr("Program Guide"),           ACTION_GUIDE);
11587         osd->DialogAddButton(tr("Program Finder"),          ACTION_FINDER);
11588         osd->DialogAddButton(tr("Upcoming Recordings"),     ACTION_VIEWSCHEDULED);
11589         osd->DialogAddButton(tr("Edit Recording Schedule"), "SCHEDULE");
11590     }
11591 }
11592 
11593 void TV::FillOSDMenuJumpRec(PlayerContext* ctx, const QString category,
11594                             int level, const QString selected)
11595 {
11596     // bool in_recgroup = !category.isEmpty() && level > 0;
11597     if (level < 0 || level > 1)
11598     {
11599         level = 0;
11600         // in_recgroup = false;
11601     }
11602 
11603     OSD *osd = GetOSDLock(ctx);
11604     if (osd)
11605     {
11606         QString title = tr("Recorded Program");
11607         osd->DialogShow("osd_jumprec", title);
11608 
11609         QMutexLocker locker(&progListsLock);
11610         progLists.clear();
11611         vector<ProgramInfo*> *infoList = RemoteGetRecordedList(0);
11612         bool LiveTVInAllPrograms = gCoreContext->GetNumSetting("LiveTVInAllPrograms",0);
11613         if (infoList)
11614         {
11615             QList<QString> titles_seen;
11616 
11617             ctx->LockPlayingInfo(__FILE__, __LINE__);
11618             QString currecgroup = ctx->playingInfo->GetRecordingGroup();
11619             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
11620 
11621             vector<ProgramInfo *>::const_iterator it = infoList->begin();
11622             for ( ; it != infoList->end(); ++it)
11623             {
11624                 if ((*it)->GetRecordingGroup() != "LiveTV" || LiveTVInAllPrograms ||
11625                      (*it)->GetRecordingGroup() == currecgroup)
11626                 {
11627                     progLists[(*it)->GetRecordingGroup()].push_front(
11628                         new ProgramInfo(*(*it)));
11629                 }
11630             }
11631 
11632             ProgramInfo *lastprog = GetLastProgram();
11633             QMap<QString,ProgramList>::const_iterator Iprog;
11634             for (Iprog = progLists.begin(); Iprog != progLists.end(); ++Iprog)
11635             {
11636                 const ProgramList &plist = *Iprog;
11637                 uint progIndex = (uint) plist.size();
11638                 QString group = Iprog.key();
11639 
11640                 if (plist[0]->GetRecordingGroup() != currecgroup)
11641                     SetLastProgram(plist[0]);
11642 
11643                 if (progIndex == 1 && level == 0)
11644                 {
11645                     osd->DialogAddButton(Iprog.key(),
11646                                          QString("JUMPPROG %1 0").arg(group));
11647                 }
11648                 else if (progIndex > 1 && level == 0)
11649                 {
11650                     QString act = QString("DIALOG_%1_%2_1")
11651                                     .arg(ACTION_JUMPREC).arg(group);
11652                     osd->DialogAddButton(group, act,
11653                                          true, selected == group);
11654                 }
11655                 else if (level == 1 && Iprog.key() == category)
11656                 {
11657                     ProgramList::const_iterator it = plist.begin();
11658                     for (; it != plist.end(); ++it)
11659                     {
11660                         const ProgramInfo *p = *it;
11661 
11662                         if (titles_seen.contains(p->GetTitle()))
11663                             continue;
11664 
11665                         titles_seen.push_back(p->GetTitle());
11666 
11667                         ProgramList::const_iterator it2 = plist.begin();
11668                         int j = -1;
11669                         for (; it2 != plist.end(); ++it2)
11670                         {
11671                             const ProgramInfo *q = *it2;
11672                             j++;
11673 
11674                             if (q->GetTitle() != p->GetTitle())
11675                                 continue;
11676 
11677                             osd->DialogAddButton(q->GetSubtitle().isEmpty() ?
11678                                     q->GetTitle() : q->GetSubtitle(),
11679                                     QString("JUMPPROG %1 %2")
11680                                         .arg(Iprog.key()).arg(j));
11681                         }
11682                     }
11683                 }
11684             }
11685             SetLastProgram(lastprog);
11686             if (lastprog)
11687                 delete lastprog;
11688 
11689             while (!infoList->empty())
11690             {
11691                 delete infoList->back();
11692                 infoList->pop_back();
11693             }
11694             delete infoList;
11695         }
11696 
11697         if (!category.isEmpty())
11698         {
11699             if (level == 1)
11700                 osd->DialogBack(category, "DIALOG_" + ACTION_JUMPREC + "_X_0");
11701             else if (level == 0)
11702                 osd->DialogBack(ACTION_JUMPREC,
11703                                 "DIALOG_MENU_" + ACTION_JUMPREC +"_0");
11704         }
11705     }
11706     ReturnOSDLock(ctx, osd);
11707 }
11708 
11709 void TV::HandleDeinterlacer(PlayerContext *ctx, const QString &action)
11710 {
11711     if (!action.startsWith("DEINTERLACER"))
11712         return;
11713 
11714     QString deint = action.mid(13);
11715     ctx->LockDeletePlayer(__FILE__, __LINE__);
11716     if (ctx->player)
11717         ctx->player->ForceDeinterlacer(deint);
11718     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
11719 }
11720 
11721 void TV::ToggleAutoExpire(PlayerContext *ctx)
11722 {
11723     QString desc = QString::null;
11724 
11725     ctx->LockPlayingInfo(__FILE__, __LINE__);
11726 
11727     if (ctx->playingInfo->QueryAutoExpire() != kDisableAutoExpire)
11728     {
11729         ctx->playingInfo->SaveAutoExpire(kDisableAutoExpire);
11730         desc = tr("Auto-Expire OFF");
11731     }
11732     else
11733     {
11734         ctx->playingInfo->SaveAutoExpire(kNormalAutoExpire);
11735         desc = tr("Auto-Expire ON");
11736     }
11737 
11738     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
11739 
11740     if (!desc.isEmpty())
11741         UpdateOSDSeekMessage(ctx, desc, kOSDTimeout_Med);
11742 }
11743 
11744 void TV::SetAutoCommercialSkip(const PlayerContext *ctx,
11745                                CommSkipMode skipMode)
11746 {
11747     QString desc = QString::null;
11748 
11749     ctx->LockDeletePlayer(__FILE__, __LINE__);
11750     if (ctx->player)
11751     {
11752         ctx->player->SetAutoCommercialSkip(skipMode);
11753         desc = toString(ctx->player->GetAutoCommercialSkip());
11754     }
11755     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
11756 
11757     if (!desc.isEmpty())
11758         UpdateOSDSeekMessage(ctx, desc, kOSDTimeout_Med);
11759 }
11760 
11761 void TV::SetManualZoom(const PlayerContext *ctx, bool zoomON, QString desc)
11762 {
11763     if (ctx->GetPIPState() != kPIPOff)
11764         return;
11765 
11766     zoomMode = zoomON;
11767     if (zoomON)
11768         ClearOSD(ctx);
11769 
11770     if (!desc.isEmpty())
11771         UpdateOSDSeekMessage(ctx, desc, kOSDTimeout_Med);
11772 }
11773 
11774 bool TV::HandleJumpToProgramAction(
11775     PlayerContext *ctx, const QStringList &actions)
11776 {
11777     const PlayerContext *mctx = GetPlayer(ctx, 0);
11778     TVState s = ctx->GetState();
11779     if (has_action(ACTION_JUMPPREV, actions) ||
11780         (has_action("PREVCHAN", actions) && !StateIsLiveTV(s)))
11781     {
11782         if (mctx == ctx)
11783         {
11784             PrepareToExitPlayer(ctx, __LINE__);
11785             jumpToProgram = true;
11786             SetExitPlayer(true, true);
11787         }
11788         else
11789         {
11790             // TODO
11791         }
11792         return true;
11793     }
11794 
11795     QStringList::const_iterator it = actions.begin();
11796     for (; it != actions.end(); ++it)
11797     {
11798         if ((*it).left(8) != "JUMPPROG")
11799             continue;
11800 
11801         const QString &action = *it;
11802 
11803         bool ok;
11804         QString progKey   = action.section(" ",1,-2);
11805         uint    progIndex = action.section(" ",-1,-1).toUInt(&ok);
11806         ProgramInfo *p = NULL;
11807 
11808         if (ok)
11809         {
11810             QMutexLocker locker(&progListsLock);
11811             QMap<QString,ProgramList>::const_iterator it =
11812                 progLists.find(progKey);
11813             if (it != progLists.end())
11814             {
11815                 const ProgramInfo *tmp = (*it)[progIndex];
11816                 if (tmp)
11817                     p = new ProgramInfo(*tmp);
11818             }
11819         }
11820 
11821         if (!p)
11822         {
11823             LOG(VB_GENERAL, LOG_ERR, LOC +
11824                 QString("Failed to locate jump to program '%1' @ %2")
11825                     .arg(progKey).arg(action.section(" ",-1,-1)));
11826             return true;
11827         }
11828 
11829         PIPState state = kPIPOff;
11830         {
11831             QMutexLocker locker(&timerIdLock);
11832             state = jumpToProgramPIPState;
11833         }
11834 
11835         if (kPIPOff == state)
11836         {
11837             if (mctx == ctx)
11838             {
11839                 PrepToSwitchToRecordedProgram(ctx, *p);
11840             }
11841             else
11842             {
11843                 // TODO
11844             }
11845         }
11846         else
11847         {
11848             QString type = (kPIPonTV == jumpToProgramPIPState) ? "PIP" : "PBP";
11849             LOG(VB_GENERAL, LOG_INFO, LOC +
11850                 QString("Creating %1 with program: %2")
11851                     .arg(type).arg(p->toString(ProgramInfo::kTitleSubtitle)));
11852 
11853             if (jumpToProgramPIPState == kPIPonTV)
11854                 CreatePIP(ctx, p);
11855             else if (jumpToProgramPIPState == kPBPLeft)
11856                 CreatePBP(ctx, p);
11857         }
11858 
11859         delete p;
11860 
11861         return true;
11862     }
11863 
11864     bool wants_jump = has_action(ACTION_JUMPREC, actions);
11865     bool wants_pip = !wants_jump && has_action("JUMPRECPIP", actions);
11866     bool wants_pbp = !wants_jump && !wants_pip &&
11867         has_action("JUMPRECPBP", actions);
11868 
11869     if (!wants_jump && !wants_pip && !wants_pbp)
11870         return false;
11871 
11872     {
11873         QMutexLocker locker(&timerIdLock);
11874         jumpToProgramPIPState = wants_pip ? kPIPonTV :
11875             (wants_pbp ? kPBPLeft : kPIPOff);
11876     }
11877 
11878     if ((wants_pbp || wants_pip || db_jump_prefer_osd) &&
11879         (StateIsPlaying(s) || StateIsLiveTV(s)))
11880     {
11881         QMutexLocker locker(&timerIdLock);
11882         if (jumpMenuTimerId)
11883             KillTimer(jumpMenuTimerId);
11884         jumpMenuTimerId = StartTimer(1, __LINE__);
11885     }
11886     else if (RunPlaybackBoxPtr)
11887         EditSchedule(ctx, kPlaybackBox);
11888     else
11889         LOG(VB_GENERAL, LOG_ERR, "Failed to open jump to program GUI");
11890 
11891     return true;
11892 }
11893 
11894 #define MINUTE 60*1000
11895 
11896 void TV::ToggleSleepTimer(const PlayerContext *ctx, const QString &time)
11897 {
11898     int mins = 0;
11899 
11900     if (time == ACTION_TOGGLESLEEP + "ON")
11901     {
11902         if (sleepTimerId)
11903         {
11904             KillTimer(sleepTimerId);
11905             sleepTimerId = 0;
11906         }
11907         else
11908         {
11909             mins = 60;
11910             sleepTimerTimeout = mins * MINUTE;
11911             sleepTimerId = StartTimer(sleepTimerTimeout, __LINE__);
11912         }
11913     }
11914     else
11915     {
11916         if (sleepTimerId)
11917         {
11918             KillTimer(sleepTimerId);
11919             sleepTimerId = 0;
11920         }
11921 
11922         if (time.length() > 11)
11923         {
11924             bool intRead = false;
11925             mins = time.right(time.length() - 11).toInt(&intRead);
11926 
11927             if (intRead)
11928             {
11929                 // catch 120 -> 240 mins
11930                 if (mins < 30)
11931                 {
11932                     mins *= 10;
11933                 }
11934             }
11935             else
11936             {
11937                 mins = 0;
11938                 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid time " + time);
11939             }
11940         }
11941         else
11942         {
11943             LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid time string " + time);
11944         }
11945 
11946         if (mins)
11947         {
11948             sleepTimerTimeout = mins * MINUTE;
11949             sleepTimerId = StartTimer(sleepTimerTimeout, __LINE__);
11950         }
11951     }
11952 
11953     QString out;
11954     if (mins != 0)
11955         out = tr("Sleep") + " " + QString::number(mins);
11956     else
11957         out = tr("Sleep") + " " + sleep_times[0].dispString;
11958     SetOSDMessage(ctx, out);
11959 }
11960 
11961 void TV::ShowNoRecorderDialog(const PlayerContext *ctx, NoRecorderMsg msgType)
11962 {
11963     QString errorText;
11964 
11965     switch (msgType)
11966     {
11967         case kNoRecorders:
11968             errorText = tr("MythTV is already using all available "
11969                            "inputs for the channel you selected. "
11970                            "If you want to watch an in-progress recording, "
11971                            "select one from the playback menu.  If you "
11972                            "want to watch Live TV, cancel one of the "
11973                            "in-progress recordings from the delete "
11974                            "menu.");
11975             break;
11976         case kNoCurrRec:
11977             errorText = tr("Error: MythTV is using all inputs, "
11978                            "but there are no active recordings?");
11979             break;
11980         case kNoTuners:
11981             errorText = tr("MythTV has no capture cards defined. "
11982                            "Please run the mythtv-setup program.");
11983             break;
11984     }
11985 
11986     OSD *osd = GetOSDLock(ctx);
11987     if (osd)
11988     {
11989         osd->DialogShow(OSD_DLG_INFO, errorText);
11990         osd->DialogAddButton(tr("OK"), "DIALOG_INFO_X_X");
11991     }
11992     else
11993     {
11994         ShowOkPopup(errorText);
11995     }
11996     ReturnOSDLock(ctx, osd);
11997 }
11998 
12003 void TV::PauseLiveTV(PlayerContext *ctx)
12004 {
12005     LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("PauseLiveTV() player ctx %1")
12006             .arg(find_player_index(ctx)));
12007 
12008     lockTimerOn = false;
12009 
12010     ctx->LockDeletePlayer(__FILE__, __LINE__);
12011     if (ctx->player && ctx->buffer)
12012     {
12013         ctx->buffer->IgnoreLiveEOF(true);
12014         ctx->buffer->StopReads();
12015         ctx->player->PauseDecoder();
12016         ctx->buffer->StartReads();
12017     }
12018     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
12019 
12020     // XXX: Get rid of this?
12021     ctx->recorder->PauseRecorder();
12022 
12023     ctx->lastSignalMsg.clear();
12024     ctx->lastSignalUIInfo.clear();
12025 
12026     lockTimerOn = false;
12027 
12028     QString input = ctx->recorder->GetInput();
12029     uint timeout  = ctx->recorder->GetSignalLockTimeout(input);
12030 
12031     if (timeout < 0xffffffff && !ctx->IsPIP())
12032     {
12033         lockTimer.start();
12034         lockTimerOn = true;
12035     }
12036 
12037     SetSpeedChangeTimer(0, __LINE__);
12038 }
12039 
12044 void TV::UnpauseLiveTV(PlayerContext *ctx, bool bQuietly /*=false*/)
12045 {
12046     LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("UnpauseLiveTV() player ctx %1")
12047             .arg(find_player_index(ctx)));
12048 
12049     if (ctx->HasPlayer() && ctx->tvchain)
12050     {
12051         ctx->ReloadTVChain();
12052         ctx->tvchain->JumpTo(-1, 1);
12053         ctx->LockDeletePlayer(__FILE__, __LINE__);
12054         if (ctx->player)
12055             ctx->player->Play(ctx->ts_normal, true, false);
12056         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
12057         ctx->buffer->IgnoreLiveEOF(false);
12058 
12059         SetSpeedChangeTimer(0, __LINE__);
12060     }
12061 
12062     ITVRestart(ctx, true);
12063 
12064     if (ctx->HasPlayer() && !bQuietly)
12065     {
12066         UpdateOSDProgInfo(ctx, "program_info");
12067         UpdateLCD();
12068         ctx->PushPreviousChannel();
12069     }
12070 }
12071 
12075 void TV::ITVRestart(PlayerContext *ctx, bool isLive)
12076 {
12077     int chanid = -1;
12078     int sourceid = -1;
12079 
12080     if (ContextIsPaused(ctx, __FILE__, __LINE__))
12081         return;
12082 
12083     ctx->LockPlayingInfo(__FILE__, __LINE__);
12084     if (ctx->playingInfo)
12085     {
12086         chanid = ctx->playingInfo->GetChanID();
12087         sourceid = ChannelUtil::GetSourceIDForChannel(chanid);
12088     }
12089     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12090 
12091     ctx->LockDeletePlayer(__FILE__, __LINE__);
12092     if (ctx->player)
12093         ctx->player->ITVRestart(chanid, sourceid, isLive);
12094     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
12095 }
12096 
12097 void TV::DoJumpFFWD(PlayerContext *ctx)
12098 {
12099     if (GetState(ctx) == kState_WatchingDVD)
12100         DVDJumpForward(ctx);
12101     else if (GetNumChapters(ctx) > 0)
12102         DoJumpChapter(ctx, 9999);
12103     else
12104         DoSeek(ctx, ctx->jumptime * 60, tr("Jump Ahead"),
12105                /*timeIsOffset*/true,
12106                /*honorCutlist*/true);
12107 }
12108 
12109 void TV::DoJumpRWND(PlayerContext *ctx)
12110 {
12111     if (GetState(ctx) == kState_WatchingDVD)
12112         DVDJumpBack(ctx);
12113     else if (GetNumChapters(ctx) > 0)
12114         DoJumpChapter(ctx, -1);
12115     else
12116         DoSeek(ctx, -ctx->jumptime * 60, tr("Jump Back"),
12117                /*timeIsOffset*/true,
12118                /*honorCutlist*/true);
12119 }
12120 
12121 /*  \fn TV::DVDJumpBack(PlayerContext*)
12122     \brief jump to the previous dvd title or chapter
12123 */
12124 void TV::DVDJumpBack(PlayerContext *ctx)
12125 {
12126     DVDRingBuffer *dvdrb = dynamic_cast<DVDRingBuffer*>(ctx->buffer);
12127     if (!ctx->HasPlayer() || !dvdrb)
12128         return;
12129 
12130     if (ctx->buffer->IsInDiscMenuOrStillFrame())
12131     {
12132         UpdateOSDSeekMessage(ctx, tr("Skip Back Not Allowed"), kOSDTimeout_Med);
12133     }
12134     else if (!dvdrb->StartOfTitle())
12135     {
12136         DoJumpChapter(ctx, -1);
12137     }
12138     else
12139     {
12140         uint titleLength = dvdrb->GetTotalTimeOfTitle();
12141         uint chapterLength = dvdrb->GetChapterLength();
12142         if ((titleLength == chapterLength) && chapterLength > 300)
12143         {
12144             DoSeek(ctx, -ctx->jumptime * 60, tr("Jump Back"),
12145                    /*timeIsOffset*/true,
12146                    /*honorCutlist*/true);
12147         }
12148         else
12149         {
12150             ctx->LockDeletePlayer(__FILE__, __LINE__);
12151             if (ctx->player)
12152                 ctx->player->GoToDVDProgram(0);
12153             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
12154 
12155             UpdateOSDSeekMessage(ctx, tr("Previous Title"), kOSDTimeout_Med);
12156         }
12157     }
12158 }
12159 
12160 /* \fn TV::DVDJumpForward(PlayerContext*)
12161  * \brief jump to the next dvd title or chapter
12162  */
12163 void TV::DVDJumpForward(PlayerContext *ctx)
12164 {
12165     DVDRingBuffer *dvdrb = dynamic_cast<DVDRingBuffer*>(ctx->buffer);
12166     if (!ctx->HasPlayer() || !dvdrb)
12167         return;
12168 
12169     bool in_still = dvdrb->IsInStillFrame();
12170     bool in_menu  = dvdrb->IsInMenu();
12171     if (in_still && !dvdrb->NumMenuButtons())
12172     {
12173         dvdrb->SkipStillFrame();
12174         UpdateOSDSeekMessage(ctx, tr("Skip Still Frame"), kOSDTimeout_Med);
12175     }
12176     else if (!dvdrb->EndOfTitle() && !in_still && !in_menu)
12177     {
12178         DoJumpChapter(ctx, 9999);
12179     }
12180     else if (!in_still && !in_menu)
12181     {
12182         uint titleLength = dvdrb->GetTotalTimeOfTitle();
12183         uint chapterLength = dvdrb->GetChapterLength();
12184         uint currentTime = dvdrb->GetCurrentTime();
12185         if ((titleLength == chapterLength) &&
12186              (currentTime < (chapterLength - (ctx->jumptime * 60))) &&
12187              chapterLength > 300)
12188         {
12189             DoSeek(ctx, ctx->jumptime * 60, tr("Jump Ahead"),
12190                    /*timeIsOffset*/true,
12191                    /*honorCutlist*/true);
12192         }
12193         else
12194         {
12195             ctx->LockDeletePlayer(__FILE__, __LINE__);
12196             if (ctx->player)
12197                 ctx->player->GoToDVDProgram(1);
12198             ctx->UnlockDeletePlayer(__FILE__, __LINE__);
12199 
12200             UpdateOSDSeekMessage(ctx, tr("Next Title"), kOSDTimeout_Med);
12201         }
12202     }
12203 }
12204 
12205 /* \fn TV::IsBookmarkAllowed(const PlayerContext*) const
12206  * \brief Returns true if bookmarks are allowed for the current player.
12207  */
12208 bool TV::IsBookmarkAllowed(const PlayerContext *ctx) const
12209 {
12210     ctx->LockPlayingInfo(__FILE__, __LINE__);
12211 
12212     // Allow bookmark of "Record current LiveTV program"
12213     if (StateIsLiveTV(GetState(ctx)) && ctx->playingInfo &&
12214         (ctx->playingInfo->QueryAutoExpire() == kLiveTVAutoExpire))
12215     {
12216         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12217         return false;
12218     }
12219 
12220     if (StateIsLiveTV(GetState(ctx)) && !ctx->playingInfo)
12221     {
12222         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12223         return false;
12224     }
12225 
12226     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12227 
12228     return ctx->buffer && ctx->buffer->IsBookmarkAllowed();
12229 }
12230 
12231 /* \fn TV::IsDeleteAllowed(const PlayerContext*) const
12232  * \brief Returns true if the delete menu option should be offered.
12233  */
12234 bool TV::IsDeleteAllowed(const PlayerContext *ctx) const
12235 {
12236     bool allowed = false;
12237 
12238     if (!StateIsLiveTV(GetState(ctx)))
12239     {
12240         ctx->LockPlayingInfo(__FILE__, __LINE__);
12241         ProgramInfo *curProgram = ctx->playingInfo;
12242         allowed = curProgram && curProgram->QueryIsDeleteCandidate(true);
12243         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12244     }
12245 
12246     return allowed;
12247 }
12248 
12249 void TV::ShowOSDStopWatchingRecording(PlayerContext *ctx)
12250 {
12251     ClearOSD(ctx);
12252 
12253     if ((ctx != GetPlayer(ctx, 0)))
12254         return;
12255 
12256     if (!ContextIsPaused(ctx, __FILE__, __LINE__))
12257         DoTogglePause(ctx, false);
12258 
12259     QString message;
12260     QString videotype = QString::null;
12261     QStringList options;
12262 
12263     if (StateIsLiveTV(GetState(ctx)))
12264         videotype = tr("Live TV");
12265     else if (ctx->buffer->IsDVD())
12266         videotype = tr("this DVD");
12267 
12268     ctx->LockPlayingInfo(__FILE__, __LINE__);
12269     if (videotype.isEmpty() && ctx->playingInfo->IsVideo())
12270         videotype = tr("this Video");
12271     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12272 
12273     if (videotype.isEmpty())
12274         videotype = tr("this recording");
12275 
12276     OSD *osd = GetOSDLock(ctx);
12277     if (osd)
12278     {
12279         osd->DialogShow(OSD_DLG_VIDEOEXIT,
12280                         tr("You are exiting %1").arg(videotype));
12281 
12282         if (IsBookmarkAllowed(ctx))
12283         {
12284             osd->DialogAddButton(tr("Save this position and go to the menu"),
12285                                  "DIALOG_VIDEOEXIT_SAVEPOSITIONANDEXIT_0");
12286             osd->DialogAddButton(tr("Do not save, just exit to the menu"),
12287                                  ACTION_STOP);
12288         }
12289         else
12290             osd->DialogAddButton(tr("Exit %1").arg(videotype),
12291                                  ACTION_STOP);
12292 
12293         if (IsDeleteAllowed(ctx))
12294             osd->DialogAddButton(tr("Delete this recording"),
12295                                  "DIALOG_VIDEOEXIT_CONFIRMDELETE_0");
12296 
12297         osd->DialogAddButton(tr("Keep watching"),
12298                              "DIALOG_VIDEOEXIT_KEEPWATCHING_0");
12299         osd->DialogBack("", "DIALOG_VIDEOEXIT_KEEPWATCHING_0", true);
12300     }
12301     ReturnOSDLock(ctx, osd);
12302 
12303     QMutexLocker locker(&timerIdLock);
12304     if (videoExitDialogTimerId)
12305         KillTimer(videoExitDialogTimerId);
12306     videoExitDialogTimerId = StartTimer(kVideoExitDialogTimeout, __LINE__);
12307 }
12308 
12309 void TV::ShowOSDPromptDeleteRecording(PlayerContext *ctx, QString title,
12310                                       bool force)
12311 {
12312     ctx->LockPlayingInfo(__FILE__, __LINE__);
12313 
12314     if (ctx->ff_rew_state ||
12315         StateIsLiveTV(ctx->GetState()) ||
12316         exitPlayerTimerId)
12317     {
12318         // this should only occur when the cat walks on the keyboard.
12319         LOG(VB_GENERAL, LOG_ERR, "It is unsafe to delete at the moment");
12320         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12321         return;
12322     }
12323 
12324     if ((ctx != GetPlayer(ctx, 0)))
12325     {
12326         // just to avoid confusion...
12327         LOG(VB_GENERAL, LOG_ERR, "Only the main program may be deleted");
12328         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12329         return;
12330     }
12331 
12332     bool paused = ContextIsPaused(ctx, __FILE__, __LINE__);
12333     if (!ctx->playingInfo->QueryIsDeleteCandidate(true))
12334     {
12335         LOG(VB_GENERAL, LOG_ERR,
12336             "This program cannot be deleted at this time.");
12337         ProgramInfo pginfo(*ctx->playingInfo);
12338         ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12339 
12340         OSD *osd = GetOSDLock(ctx);
12341         if (osd && !osd->DialogVisible())
12342         {
12343             QString message = QObject::tr("Cannot delete program ") +
12344                 QString("%1 ")
12345                 .arg(pginfo.GetTitle());
12346 
12347             if (!pginfo.GetSubtitle().isEmpty())
12348                 message += QString("\"%1\" ").arg(pginfo.GetSubtitle());
12349 
12350             if (!pginfo.IsRecording())
12351             {
12352                 message += QObject::tr("because it is not a recording.");
12353             }
12354             else
12355             {
12356                 message += QObject::tr("because it is in use by");
12357                 QStringList byWho;
12358                 pginfo.QueryIsInUse(byWho);
12359                 for (uint i = 0; i+2 < (uint)byWho.size(); i+=3)
12360                 {
12361                     if (byWho[i+1] == gCoreContext->GetHostName() &&
12362                         byWho[i+0].contains(kPlayerInUseID))
12363                         continue;
12364                     if (byWho[i+0].contains(kRecorderInUseID))
12365                         continue;
12366                     message += " " + byWho[i+2];
12367                 }
12368             }
12369             osd->DialogShow(OSD_DLG_DELETE, message);
12370             QString action = "DIALOG_DELETE_OK_0";
12371             osd->DialogAddButton(tr("OK"), action);
12372             osd->DialogBack("", action, true);
12373         }
12374         ReturnOSDLock(ctx, osd);
12375         // If the delete prompt is to be displayed at the end of a
12376         // recording that ends in a final cut region, it will get into
12377         // a loop of popping up the OK button while the cut region
12378         // plays.  Avoid this.
12379         if (ctx->player->IsNearEnd() && !paused)
12380             SetExitPlayer(true, true);
12381 
12382         return;
12383     }
12384     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12385 
12386     ClearOSD(ctx);
12387 
12388     if (!paused)
12389         DoTogglePause(ctx, false);
12390 
12391     InfoMap infoMap;
12392     ctx->GetPlayingInfoMap(infoMap);
12393     QString message = QString("%1\n%2\n%3")
12394         .arg(title).arg(infoMap["title"]).arg(infoMap["timedate"]);
12395 
12396     OSD *osd = GetOSDLock(ctx);
12397     if (osd && (!osd->DialogVisible() || force))
12398     {
12399         osd->DialogShow(OSD_DLG_VIDEOEXIT, message);
12400         if (title == "End Of Recording")
12401         {
12402             osd->DialogAddButton(tr("Delete it, but allow it to re-record"),
12403                                  "DIALOG_VIDEOEXIT_DELETEANDRERECORD_0");
12404             osd->DialogAddButton(tr("Delete it"),
12405                                  "DIALOG_VIDEOEXIT_JUSTDELETE_0");
12406             osd->DialogAddButton(tr("Save it so I can watch it again"),
12407                                  ACTION_STOP, false, true);
12408         }
12409         else
12410         {
12411             osd->DialogAddButton(tr("Yes, and allow re-record"),
12412                                  "DIALOG_VIDEOEXIT_DELETEANDRERECORD_0");
12413             osd->DialogAddButton(tr("Yes, delete it"),
12414                                  "DIALOG_VIDEOEXIT_JUSTDELETE_0");
12415             osd->DialogAddButton(tr("No, keep it"),
12416                                  ACTION_STOP, false, true);
12417             if (!paused)
12418                 osd->DialogBack("", "DIALOG_PLAY_0_0", true);
12419         }
12420 
12421         QMutexLocker locker(&timerIdLock);
12422         if (videoExitDialogTimerId)
12423             KillTimer(videoExitDialogTimerId);
12424         videoExitDialogTimerId = StartTimer(kVideoExitDialogTimeout, __LINE__);
12425     }
12426     ReturnOSDLock(ctx, osd);
12427 }
12428 
12429 bool TV::HandleOSDVideoExit(PlayerContext *ctx, QString action)
12430 {
12431     if (!DialogIsVisible(ctx, OSD_DLG_VIDEOEXIT))
12432         return false;
12433 
12434     bool hide        = true;
12435     bool delete_ok   = IsDeleteAllowed(ctx);
12436     bool bookmark_ok = IsBookmarkAllowed(ctx);
12437 
12438     ctx->LockDeletePlayer(__FILE__, __LINE__);
12439     bool near_end = ctx->player && ctx->player->IsNearEnd();
12440     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
12441 
12442     if (action == "DELETEANDRERECORD" && delete_ok)
12443     {
12444         allowRerecord = true;
12445         requestDelete = true;
12446         SetExitPlayer(true, true);
12447     }
12448     else if (action == "JUSTDELETE" && delete_ok)
12449     {
12450         requestDelete = true;
12451         SetExitPlayer(true, true);
12452     }
12453     else if (action == "CONFIRMDELETE")
12454     {
12455         hide = false;
12456         ShowOSDPromptDeleteRecording(ctx, tr("Are you sure you want to delete:"),
12457                                      true);
12458     }
12459     else if (action == "SAVEPOSITIONANDEXIT" && bookmark_ok)
12460     {
12461         PrepareToExitPlayer(ctx, __LINE__, kBookmarkAlways);
12462         SetExitPlayer(true, true);
12463     }
12464     else if (action == "KEEPWATCHING" && !near_end)
12465     {
12466         DoTogglePause(ctx, true);
12467     }
12468 
12469     return hide;
12470 }
12471 
12472 void TV::SetLastProgram(const ProgramInfo *rcinfo)
12473 {
12474     QMutexLocker locker(&lastProgramLock);
12475 
12476     if (lastProgram)
12477         delete lastProgram;
12478 
12479     if (rcinfo)
12480         lastProgram = new ProgramInfo(*rcinfo);
12481     else
12482         lastProgram = NULL;
12483 }
12484 
12485 ProgramInfo *TV::GetLastProgram(void) const
12486 {
12487     QMutexLocker locker(&lastProgramLock);
12488     if (lastProgram)
12489         return new ProgramInfo(*lastProgram);
12490     return NULL;
12491 }
12492 
12493 QString TV::GetRecordingGroup(int player_idx) const
12494 {
12495     QString ret = QString::null;
12496 
12497     const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
12498     if (ctx)
12499     {
12500         if (StateIsPlaying(GetState(ctx)))
12501         {
12502             ctx->LockPlayingInfo(__FILE__, __LINE__);
12503             if (ctx->playingInfo)
12504                 ret = ctx->playingInfo->GetRecordingGroup();
12505             ctx->UnlockPlayingInfo(__FILE__, __LINE__);
12506         }
12507     }
12508     ReturnPlayerLock(ctx);
12509     return ret;
12510 }
12511 
12512 bool TV::IsSameProgram(int player_idx, const ProgramInfo *rcinfo) const
12513 {
12514     if (!rcinfo)
12515         return false;
12516 
12517     bool ret = false;
12518     const PlayerContext *ctx = GetPlayerReadLock(player_idx, __FILE__, __LINE__);
12519     if (ctx)
12520         ret = ctx->IsSameProgram(*rcinfo);
12521     ReturnPlayerLock(ctx);
12522 
12523     return ret;
12524 }
12525 
12526 void TV::RestoreScreenSaver(const PlayerContext *ctx)
12527 {
12528     if (ctx == GetPlayer(ctx, 0))
12529         GetMythUI()->RestoreScreensaver();
12530 }
12531 
12532 bool TV::ContextIsPaused(PlayerContext *ctx, const char *file, int location)
12533 {
12534     if (!ctx)
12535         return false;
12536     bool paused = false;
12537     ctx->LockDeletePlayer(file, location);
12538     if (ctx->player)
12539         paused = ctx->player->IsPaused();
12540     ctx->UnlockDeletePlayer(file, location);
12541     return paused;
12542 }
12543 
12544 OSD *TV::GetOSDL(const char *file, int location)
12545 {
12546     PlayerContext *actx = GetPlayerReadLock(-1, file, location);
12547 
12548     OSD *osd = GetOSDL(actx, file, location);
12549     if (!osd)
12550         ReturnPlayerLock(actx);
12551 
12552     return osd;
12553 }
12554 
12555 OSD *TV::GetOSDL(const PlayerContext *ctx, const char *file, int location)
12556 {
12557     if (!ctx)
12558         return NULL;
12559 
12560     const PlayerContext *mctx = GetPlayer(ctx, 0);
12561 
12562     mctx->LockDeletePlayer(file, location);
12563     if (mctx->player && ctx->IsPIP())
12564     {
12565         mctx->LockOSD();
12566         OSD *osd = mctx->player->GetOSD();
12567         if (!osd)
12568         {
12569             mctx->UnlockOSD();
12570             mctx->UnlockDeletePlayer(file, location);
12571         }
12572         else
12573             osd_lctx[osd] = mctx;
12574         return osd;
12575     }
12576     mctx->UnlockDeletePlayer(file, location);
12577 
12578     ctx->LockDeletePlayer(file, location);
12579     if (ctx->player && !ctx->IsPIP())
12580     {
12581         ctx->LockOSD();
12582         OSD *osd = ctx->player->GetOSD();
12583         if (!osd)
12584         {
12585             ctx->UnlockOSD();
12586             ctx->UnlockDeletePlayer(file, location);
12587         }
12588         else
12589             osd_lctx[osd] = ctx;
12590         return osd;
12591     }
12592     ctx->UnlockDeletePlayer(file, location);
12593 
12594     return NULL;
12595 }
12596 
12597 void TV::ReturnOSDLock(const PlayerContext *ctx, OSD *&osd)
12598 {
12599     if (!ctx || !osd)
12600         return;
12601 
12602     osd_lctx[osd]->UnlockOSD();
12603     osd_lctx[osd]->UnlockDeletePlayer(__FILE__, __LINE__);
12604 
12605     osd = NULL;
12606 }
12607 
12608 PlayerContext *TV::GetPlayerWriteLock(int which, const char *file, int location)
12609 {
12610     playerLock.lockForWrite();
12611 
12612     if ((which >= (int)player.size()))
12613     {
12614         LOG(VB_GENERAL, LOG_WARNING, LOC +
12615             QString("GetPlayerWriteLock(%1,%2,%3) returning NULL size(%4)")
12616                 .arg(which).arg(file).arg(location).arg(player.size()));
12617         return NULL;
12618     }
12619 
12620     return (which < 0) ? player[playerActive] : player[which];
12621 }
12622 
12623 PlayerContext *TV::GetPlayerReadLock(int which, const char *file, int location)
12624 {
12625     playerLock.lockForRead();
12626 
12627     if ((which >= (int)player.size()))
12628     {
12629         LOG(VB_GENERAL, LOG_WARNING, LOC +
12630             QString("GetPlayerReadLock(%1,%2,%3) returning NULL size(%4)")
12631                 .arg(which).arg(file).arg(location).arg(player.size()));
12632         return NULL;
12633     }
12634 
12635     return (which < 0) ? player[playerActive] : player[which];
12636 }
12637 
12638 const PlayerContext *TV::GetPlayerReadLock(
12639     int which, const char *file, int location) const
12640 {
12641     playerLock.lockForRead();
12642 
12643     if ((which >= (int)player.size()))
12644     {
12645         LOG(VB_GENERAL, LOG_WARNING, LOC +
12646             QString("GetPlayerReadLock(%1,%2,%3) returning NULL size(%4)")
12647                 .arg(which).arg(file).arg(location).arg(player.size()));
12648         return NULL;
12649     }
12650 
12651     return (which < 0) ? player[playerActive] : player[which];
12652 }
12653 
12654 PlayerContext *TV::GetPlayerHaveLock(
12655     PlayerContext *locked_context,
12656     int which, const char *file, int location)
12657 {
12658     if (!locked_context || (which >= (int)player.size()))
12659     {
12660         LOG(VB_GENERAL, LOG_WARNING, LOC +
12661             QString("GetPlayerHaveLock(0x%1,%2,%3,%4) returning NULL size(%5)")
12662                 .arg((uint64_t)locked_context, 0, 16)
12663                 .arg(which).arg(file).arg(location).arg(player.size()));
12664         return NULL;
12665     }
12666 
12667     return (which < 0) ? player[playerActive] : player[which];
12668 }
12669 
12670 const PlayerContext *TV::GetPlayerHaveLock(
12671     const PlayerContext *locked_context,
12672     int which, const char *file, int location) const
12673 {
12674     if (!locked_context || (which >= (int)player.size()))
12675     {
12676         LOG(VB_GENERAL, LOG_WARNING, LOC +
12677             QString("GetPlayerHaveLock(0x%1,%2,%3,%4) returning NULL size(%5)")
12678                 .arg((uint64_t)locked_context, 0, 16)
12679                 .arg(which).arg(file).arg(location).arg(player.size()));
12680         return NULL;
12681     }
12682 
12683     return (which < 0) ? player[playerActive] : player[which];
12684 }
12685 
12686 void TV::ReturnPlayerLock(PlayerContext *&ctx)
12687 {
12688     playerLock.unlock();
12689     ctx = NULL;
12690 }
12691 
12692 void TV::ReturnPlayerLock(const PlayerContext *&ctx) const
12693 {
12694     playerLock.unlock();
12695     ctx = NULL;
12696 }
12697 
12698 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends