Index: libs/libmythtv/libmythtv.pro
===================================================================
--- libs/libmythtv/libmythtv.pro	(revision 26235)
+++ libs/libmythtv/libmythtv.pro	(working copy)
@@ -255,14 +255,14 @@
     HEADERS += tv_play_win.h            deletemap.h
     HEADERS += mythcommflagplayer.h     commbreakmap.h
     HEADERS += BDRingBuffer.h           mythbdplayer.h
-    HEADERS += mythiowrapper.h
+    HEADERS += mythiowrapper.h          tvbrowsehelper.h
     SOURCES += tv_play.cpp              mythplayer.cpp
     SOURCES += mythdvdplayer.cpp        audioplayer.cpp
     SOURCES += DVDRingBuffer.cpp        playercontext.cpp
     SOURCES += tv_play_win.cpp          deletemap.cpp
     SOURCES += mythcommflagplayer.cpp   commbreakmap.cpp
     SOURCES += BDRingBuffer.cpp         mythbdplayer.cpp
-    SOURCES += mythiowrapper.cpp
+    SOURCES += mythiowrapper.cpp        tvbrowsehelper.cpp
 
     # Text subtitle parser
     HEADERS += textsubtitleparser.h     xine_demux_sputext.h
Index: libs/libmythtv/tv_play.h
===================================================================
--- libs/libmythtv/tv_play.h	(revision 26235)
+++ libs/libmythtv/tv_play.h	(working copy)
@@ -58,6 +58,7 @@
 class TV;
 class OSDListTreeItemEnteredEvent;
 class OSDListTreeItemSelectedEvent;
+class TVBrowseHelper;
 struct osdInfo;
 
 typedef QMap<QString,InfoMap>    DDValueMap;
@@ -78,7 +79,7 @@
 //            -> timerIdLock
 //            -> mainLoopCondLock
 //            -> stateChangeCondLock
-//            -> browseLock
+//            -> channelGroupLock
 //
 // When holding one of these locks, you may lock any lock of  the locks to
 // the right of the current lock, but may not lock any lock to the left of
@@ -169,6 +170,7 @@
     friend class PlaybackBox;
     friend class GuideGrid;
     friend class TvPlayWindow;
+    friend class TVBrowseHelper;
 
     Q_OBJECT
   public:
@@ -192,6 +194,7 @@
     void ProcessKeypress(PlayerContext*, QKeyEvent *e);
     void ProcessNetworkControlCommand(PlayerContext *, const QString &command);
     void customEvent(QEvent *e);
+    bool event(QEvent *e);
     bool HandleTrackAction(PlayerContext*, const QString &action);
 
     // LiveTV commands
@@ -263,10 +266,6 @@
                             const QStringList &actions,
                             bool isDVD, bool isDVDStillFrame);
 
-    void GetNextProgram(RemoteEncoder *enc,
-                        BrowseDirection direction, InfoMap &infoMap) const;
-    void GetNextProgram(BrowseDirection direction, InfoMap &infoMap) const;
-
     // static functions
     static void InitKeys(void);
     static void ResetKeys(void);
@@ -285,7 +284,6 @@
     void HandleUDPNotifyEvent(void);
 
     // Channel Groups
-    void SaveChannelGroup(void);
     void UpdateChannelList(int groupID);
 
   public slots:
@@ -515,12 +513,7 @@
 
     void ToggleAutoExpire(PlayerContext*);
 
-    void BrowseStart(PlayerContext*);
-    void BrowseEnd(PlayerContext*, bool change_channel);
-    void BrowseDispInfo(PlayerContext*, BrowseDirection direction);
-    void BrowseChannel(PlayerContext*, const QString &channum);
     bool BrowseHandleAction(PlayerContext*, const QStringList &actions);
-    uint BrowseAllGetChanId(const QString &chan) const;
 
     void ToggleRecord(PlayerContext*);
 
@@ -652,11 +645,8 @@
     // Configuration variables from database
     QString baseFilters;
     QString db_channel_format;
-    QString db_time_format;
-    QString db_short_date_format;
     uint    db_idle_timeout;
     uint    db_udpnotify_port;
-    uint    db_browse_max_forward;
     int     db_playback_exit_prompt;
     uint    db_autoexpire_default;
     bool    db_auto_set_watched;
@@ -670,7 +660,9 @@
     bool    db_use_fixed_size;
     bool    db_browse_always;
     bool    db_browse_all_tuners;
-    DBChanList db_browse_all_channels;
+    bool    db_use_channel_groups;
+    bool    db_remember_last_channel_group;
+    ChannelGroupList db_channel_groups;
 
     bool    arrowAccel;
 
@@ -763,11 +755,7 @@
     QDateTime lastLockSeenTime;
 
     // Channel browsing state variables
-    bool       browsemode;
-    QString    browsechannum;
-    uint       browsechanid;
-    QString    browsestarttime;
-    mutable QMutex browseLock; // protects db_browse_all_channels
+    TVBrowseHelper *browsehelper;
 
     // Program Info for currently playing video
     // (or next video if InChangeState() is true)
@@ -834,10 +822,9 @@
 #endif // PLAY_FROM_RECORDER
 
     // Channel group stuff
-    int channel_group_id;
-    uint browse_changrp;
-    ChannelGroupList m_changrplist;
-    DBChanList m_channellist;
+    mutable QMutex channelGroupLock;
+    int            channelGroupId;
+    DBChanList     channelGroupChannelList;
 
     // Network Control stuff
     MythDeque<QString> networkControlCommands;
Index: libs/libmythtv/tvbrowsehelper.h
===================================================================
--- libs/libmythtv/tvbrowsehelper.h	(revision 0)
+++ libs/libmythtv/tvbrowsehelper.h	(revision 0)
@@ -0,0 +1,119 @@
+// -*- Mode: c++ -*-
+
+#ifndef _TV_BROWSE_HELPER_H_
+#define _TV_BROWSE_HELPER_H_
+
+#include <QWaitCondition>
+#include <QThread>
+#include <QString>
+
+#include "dbchannelinfo.h" // for DBChanList
+#include "programtypes.h"  // for InfoMap
+#include "tv.h"            // for BrowseDirection
+
+class PlayerContext;
+class RemoteEncoder;
+class TV;
+
+class BrowseInfo
+{
+  public:
+    BrowseInfo(BrowseDirection dir) :
+        m_dir(dir), m_chanid(0)
+    {
+    }
+    BrowseInfo(BrowseDirection dir,
+               const QString &channum,
+               uint           chanid,
+               const QString &starttime) :
+        m_dir(dir),  m_channum(channum),
+        m_chanid(0), m_starttime(starttime)
+    {
+    }
+
+    BrowseDirection m_dir;
+    QString         m_channum;
+    uint            m_chanid;
+    QString         m_starttime;
+};
+
+
+class TVBrowseHelper : public QThread
+{
+  public:
+    TVBrowseHelper(TV      *tv,
+                   QString  time_format,
+                   QString  short_date_format,
+                   uint     browse_max_forward,
+                   bool     browse_all_tuners,
+                   bool     use_channel_groups,
+                   QString  db_channel_ordering);
+
+    virtual ~TVBrowseHelper()
+    {
+        Stop();
+        Wait();
+    }
+
+    virtual void deleteLater()
+    {
+        Stop();
+        QThread::deleteLater();
+    }
+
+    void Stop()
+    {
+        QMutexLocker locker(&m_lock);
+        m_list.clear();
+        m_run = false;
+        m_wait.wakeAll();
+    }
+
+    void Wait() { QThread::wait(); }
+
+    bool BrowseStart(PlayerContext *ctx, bool skip_browse = false);
+    void BrowseEnd(PlayerContext *ctx, bool change_channel);
+    void BrowseDispInfo(PlayerContext *ctx, BrowseInfo &bi);
+
+    void BrowseDispInfo(PlayerContext *ctx, BrowseDirection direction)
+    {
+        BrowseInfo bi(direction);
+        if (BROWSE_SAME != direction)
+            BrowseDispInfo(ctx, bi);
+    }
+
+    void BrowseChannel(PlayerContext *ctx, const QString &channum)
+    {
+        BrowseInfo bi(BROWSE_SAME, channum, 0, "");
+        BrowseDispInfo(ctx, bi);
+    }
+
+    BrowseInfo GetBrowsedInfo(void) const;
+    bool IsBrowsing(void) const;
+    uint BrowseAllGetChanId(const QString &channum) const;
+
+  protected:
+    void GetNextProgram(BrowseDirection direction, InfoMap &infoMap) const;
+    void GetNextProgramDB(BrowseDirection direction, InfoMap &infoMap) const;
+
+    virtual void run();
+
+    TV               *m_tv;
+    DBChanList        db_browse_all_channels;
+    QString           db_time_format;
+    QString           db_short_date_format;
+    uint              db_browse_max_forward;
+    bool              db_browse_all_tuners;
+    bool              db_use_channel_groups;
+
+    mutable QMutex    m_lock; // protects variables below
+    PlayerContext    *m_ctx;
+    QString           m_channum;
+    uint              m_chanid;
+    QString           m_starttime;
+    bool              m_run;
+    QWaitCondition    m_wait;
+    QList<BrowseInfo> m_list;
+};
+
+#endif // _TV_BROWSE_HELPER_H_
Index: libs/libmythtv/tvbrowsehelper.cpp
===================================================================
--- libs/libmythtv/tvbrowsehelper.cpp	(revision 0)
+++ libs/libmythtv/tvbrowsehelper.cpp	(revision 0)
@@ -0,0 +1,493 @@
+#include "mythcorecontext.h"
+
+#include <QCoreApplication>
+
+#include "tvbrowsehelper.h"
+#include "playercontext.h"
+#include "remoteencoder.h"
+#include "recordinginfo.h"
+#include "tv_play.h"
+
+#define GetPlayer(X,Y) GetPlayerHaveLock(X, Y, __FILE__ , __LINE__)
+#define GetOSDLock(X) GetOSDL(X, __FILE__, __LINE__)
+
+static void format_time(int seconds, QString &tMin, QString &tHrsMin)
+{
+    int minutes     = seconds / 60;
+    int hours       = minutes / 60;
+    int min         = minutes % 60;
+
+    tMin = TV::tr("%n minute(s)", "", minutes);
+    tHrsMin.sprintf("%d:%02d", hours, min);
+}
+
+TVBrowseHelper::TVBrowseHelper(
+    TV      *tv,
+    QString  time_format,
+    QString  short_date_format,
+    uint     browse_max_forward,
+    bool     browse_all_tuners,
+    bool     use_channel_groups,
+    QString  db_channel_ordering) :
+    m_tv(tv),
+    db_time_format(time_format),
+    db_short_date_format(short_date_format),
+    db_browse_max_forward(browse_max_forward),
+    db_browse_all_tuners(browse_all_tuners),
+    db_use_channel_groups(use_channel_groups),
+    m_ctx(NULL),
+    m_chanid(0),
+    m_run(true)
+{
+    if (db_browse_all_tuners)
+    {
+        db_browse_all_channels = ChannelUtil::GetChannels(
+            0, true, "channum, callsign");
+        ChannelUtil::SortChannels(
+            db_browse_all_channels, db_channel_ordering, true);
+    }
+
+    start();
+}
+
+
+/// \brief Begins channel browsing.
+/// \note This may only be called from the UI thread.
+bool TVBrowseHelper::BrowseStart(PlayerContext *ctx, bool skip_browse)
+{
+    VERBOSE(VB_IMPORTANT, "BrowseStart()");
+
+    if (!gCoreContext->IsUIThread())
+        return false;
+
+    QMutexLocker locker(&m_lock);
+
+    if (m_ctx)
+        return m_ctx == ctx;
+
+    if (ctx->paused)
+        return false;
+
+    m_tv->ClearOSD(ctx);
+
+    ctx->LockPlayingInfo(__FILE__, __LINE__);
+    if (ctx->playingInfo)
+    {
+        m_ctx = ctx;
+        BrowseInfo bi(BROWSE_SAME,
+                      ctx->playingInfo->GetChanNum(),
+                      ctx->playingInfo->GetChanID(),
+                      ctx->playingInfo->GetScheduledStartTime(ISODate));
+        ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+
+        if (!skip_browse)
+        {
+            locker.unlock();
+            BrowseDispInfo(ctx, bi);
+        }
+        return true;
+    }
+    else
+    {
+        ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+        return false;
+    }
+}
+
+/** \brief Ends channel browsing.
+ *         Changing the channel if change_channel is true.
+ *  \note This may only be called from the UI thread.
+ *  \param ctx PlayerContext to end browsing on
+ *  \param change_channel iff true we call ChangeChannel()
+ */
+void TVBrowseHelper::BrowseEnd(PlayerContext *ctx, bool change_channel)
+{
+    VERBOSE(VB_IMPORTANT, "BrowseEnd()");
+
+    if (!gCoreContext->IsUIThread())
+        return;
+
+    QMutexLocker locker(&m_lock);
+
+    if (ctx && m_ctx != ctx)
+        return;
+
+    if (!m_ctx)
+        return;
+
+    {
+        QMutexLocker locker(&m_tv->timerIdLock);
+        if (m_tv->browseTimerId)
+        {
+            m_tv->KillTimer(m_tv->browseTimerId);
+            m_tv->browseTimerId = 0;
+        }
+    }
+
+    {
+        QMutexLocker locker(&m_lock);
+        m_list.clear();
+        m_wait.wakeAll();
+    }
+
+    OSD *osd = m_tv->GetOSDLock(ctx);
+    if (osd)
+        osd->HideWindow("browse_info");
+    m_tv->ReturnOSDLock(ctx, osd);
+
+    if (change_channel)
+        m_tv->ChangeChannel(ctx, 0, m_channum);
+
+    m_ctx = NULL;
+}
+
+void TVBrowseHelper::BrowseDispInfo(PlayerContext *ctx, BrowseInfo &bi)
+{
+    VERBOSE(VB_IMPORTANT, "BrowseDispInfo()");
+
+    if (!gCoreContext->IsUIThread())
+        return;
+
+    if (!BrowseStart(ctx, true))
+        return;
+
+    {
+        QMutexLocker locker(&m_tv->timerIdLock);
+        if (m_tv->browseTimerId)
+        {
+            m_tv->KillTimer(m_tv->browseTimerId);
+            m_tv->browseTimerId =
+                m_tv->StartTimer(TV::kBrowseTimeout, __LINE__);
+        }
+    }
+
+    QMutexLocker locker(&m_lock);
+    if (BROWSE_SAME == bi.m_dir)
+        m_list.clear();
+    m_list.push_back(bi);
+    m_wait.wakeAll();
+}
+
+BrowseInfo TVBrowseHelper::GetBrowsedInfo(void) const
+{
+    QMutexLocker locker(&m_lock);
+    BrowseInfo bi(BROWSE_SAME);
+    if (m_ctx != NULL)
+    {
+        bi.m_channum   = m_channum;
+        bi.m_chanid    = m_chanid;
+        bi.m_starttime = m_starttime;
+    }
+    return bi;
+}
+
+/**
+ *  \note This may only be called from the UI thread.
+ */
+bool TVBrowseHelper::IsBrowsing(void) const
+{
+    if (!gCoreContext->IsUIThread())
+        return true;
+
+    return m_ctx != NULL;
+}
+
+/// Returns chanid of random channel with with matching channum
+uint TVBrowseHelper::BrowseAllGetChanId(const QString &channum) const
+{
+    DBChanList::const_iterator it = db_browse_all_channels.begin();
+    for (; it != db_browse_all_channels.end(); ++it)
+    {
+        if ((*it).channum == channum)
+            return (*it).chanid;
+    }
+    return 0;
+}
+
+/**
+ *  \brief Fetches information on the desired program from the backend.
+ *  \param enc RemoteEncoder to query, if null query the actx->recorder.
+ *  \param direction BrowseDirection to get information on.
+ *  \param infoMap InfoMap to fill in with returned data
+ */
+void TVBrowseHelper::GetNextProgram(
+    BrowseDirection direction, InfoMap &infoMap) const
+{
+    if (!m_ctx->recorder)
+        return;
+
+    QString title, subtitle, desc, category, endtime, callsign, iconpath;
+    QDateTime begts, endts;
+
+    QString starttime = infoMap["dbstarttime"];
+    QString chanid    = infoMap["chanid"];
+    QString channum   = infoMap["channum"];
+    QString seriesid  = infoMap["seriesid"];
+    QString programid = infoMap["programid"];
+
+    m_ctx->recorder->GetNextProgram(
+        direction,
+        title,     subtitle,  desc,      category,
+        starttime, endtime,   callsign,  iconpath,
+        channum,   chanid,    seriesid,  programid);
+
+    if (!starttime.isEmpty())
+        begts = QDateTime::fromString(starttime, Qt::ISODate);
+    else
+        begts = QDateTime::fromString(infoMap["dbstarttime"], Qt::ISODate);
+
+    infoMap["starttime"] = begts.toString(db_time_format);
+    infoMap["startdate"] = begts.toString(db_short_date_format);
+
+    infoMap["endtime"] = infoMap["enddate"] = "";
+    if (!endtime.isEmpty())
+    {
+        endts = QDateTime::fromString(endtime, Qt::ISODate);
+        infoMap["endtime"] = endts.toString(db_time_format);
+        infoMap["enddate"] = endts.toString(db_short_date_format);
+    }
+
+    infoMap["lenmins"] = tr("%n minute(s)", "", 0);
+    infoMap["lentime"] = "0:00";
+    if (begts.isValid() && endts.isValid())
+    {
+        QString lenM, lenHM;
+        format_time(begts.secsTo(endts), lenM, lenHM);
+        infoMap["lenmins"] = lenM;
+        infoMap["lentime"] = lenHM;
+    }
+
+    infoMap["dbstarttime"] = starttime;
+    infoMap["dbendtime"]   = endtime;
+    infoMap["title"]       = title;
+    infoMap["subtitle"]    = subtitle;
+    infoMap["description"] = desc;
+    infoMap["category"]    = category;
+    infoMap["callsign"]    = callsign;
+    infoMap["channum"]     = channum;
+    infoMap["chanid"]      = chanid;
+    infoMap["iconpath"]    = iconpath;
+    infoMap["seriesid"]    = seriesid;
+    infoMap["programid"]   = programid;
+}
+
+void TVBrowseHelper::GetNextProgramDB(
+    BrowseDirection direction, InfoMap &infoMap) const
+{
+    uint chanid = infoMap["chanid"].toUInt();
+    if (!chanid)
+        chanid = BrowseAllGetChanId(infoMap["channum"]);
+
+    int chandir = -1;
+    switch (direction)
+    {
+        case BROWSE_UP:       chandir = CHANNEL_DIRECTION_UP;       break;
+        case BROWSE_DOWN:     chandir = CHANNEL_DIRECTION_DOWN;     break;
+        case BROWSE_FAVORITE: chandir = CHANNEL_DIRECTION_FAVORITE; break;
+    }
+    if (direction != BROWSE_INVALID)
+    {
+        chanid = ChannelUtil::GetNextChannel(
+            db_browse_all_channels, chanid, 0, chandir);
+    }
+
+    infoMap["chanid"] = QString::number(chanid);
+
+    DBChanList::const_iterator it = db_browse_all_channels.begin();
+    for (; it != db_browse_all_channels.end(); ++it)
+    {
+        if ((*it).chanid == chanid)
+        {
+            QString tmp = (*it).channum;
+            tmp.detach();
+            infoMap["channum"] = tmp;
+            break;
+        }
+    }
+
+    QDateTime nowtime = QDateTime::currentDateTime();
+    QDateTime latesttime = nowtime.addSecs(6*60*60);
+    QDateTime browsetime = QDateTime::fromString(
+        infoMap["dbstarttime"], Qt::ISODate);
+
+    MSqlBindings bindings;
+    bindings[":CHANID"] = chanid;
+    bindings[":NOWTS"] = nowtime.toString("yyyy-MM-ddThh:mm:ss");
+    bindings[":LATESTTS"] = latesttime.toString("yyyy-MM-ddThh:mm:ss");
+    bindings[":BROWSETS"] = browsetime.toString("yyyy-MM-ddThh:mm:ss");
+    bindings[":BROWSETS2"] = browsetime.toString("yyyy-MM-ddThh:mm:ss");
+
+    QString querystr = " WHERE program.chanid = :CHANID ";
+    switch (direction)
+    {
+        case BROWSE_LEFT:
+            querystr += " AND program.endtime <= :BROWSETS "
+                " AND program.endtime > :NOWTS ";
+            break;
+
+        case BROWSE_RIGHT:
+            querystr += " AND program.starttime > :BROWSETS "
+                " AND program.starttime < :LATESTTS ";
+            break;
+
+        default:
+            querystr += " AND program.starttime <= :BROWSETS "
+                " AND program.endtime > :BROWSETS2 ";
+    };
+
+    ProgramList progList;
+    ProgramList dummySched;
+    LoadFromProgram(progList, querystr, bindings, dummySched, false);
+
+    if (progList.empty())
+    {
+        infoMap["dbstarttime"] = "";
+        return;
+    }
+
+    const ProgramInfo *prog = (direction == BROWSE_LEFT) ?
+        progList[progList.size() - 1] : progList[0];
+
+    infoMap["dbstarttime"] = prog->GetScheduledStartTime(ISODate);
+}
+
+void TVBrowseHelper::run()
+{
+    QMutexLocker locker(&m_lock);
+    while (true)
+    {
+        while (m_list.empty() && m_run)
+            m_wait.wait(&m_lock);
+
+        if (!m_run)
+            break;
+
+        BrowseInfo bi = m_list.front();
+        m_list.pop_front();
+
+        PlayerContext *ctx = m_ctx;
+
+        if (BROWSE_SAME == bi.m_dir)
+        {
+            m_channum   = bi.m_channum;
+            m_chanid    = bi.m_chanid;
+            m_starttime = bi.m_starttime;
+        }
+
+        BrowseDirection direction = bi.m_dir;
+
+        QDateTime lasttime = QDateTime::fromString(
+            m_starttime, Qt::ISODate);
+        QDateTime curtime  = QDateTime::currentDateTime();
+        if (lasttime < curtime)
+            m_starttime = curtime.toString(Qt::ISODate);
+
+        QDateTime maxtime  = curtime.addSecs(db_browse_max_forward);
+        if ((lasttime > maxtime) && (direction == BROWSE_RIGHT))
+            continue;
+
+        m_lock.unlock();
+
+        // if browsing channel groups is enabled or
+        // direction if BROWSE_FAVORITES
+        // Then pick the next channel in the channel group list to browse
+        // If channel group is ALL CHANNELS (-1), then bypass picking from
+        // the channel group list
+        if ((db_use_channel_groups || (direction == BROWSE_FAVORITE)) &&
+            (direction != BROWSE_RIGHT) && (direction != BROWSE_LEFT) &&
+            (direction != BROWSE_SAME))
+        {
+            m_tv->channelGroupLock.lock();
+            if (m_tv->channelGroupId > -1)
+            {
+                int dir = direction;
+                if ((direction == BROWSE_UP) || (direction == BROWSE_FAVORITE))
+                    dir = CHANNEL_DIRECTION_UP;
+                else if (direction == BROWSE_DOWN)
+                    dir = CHANNEL_DIRECTION_DOWN;
+
+                uint chanid = ChannelUtil::GetNextChannel(
+                    m_tv->channelGroupChannelList, m_chanid, 0, dir);
+                direction = BROWSE_SAME;
+
+                m_tv->channelGroupLock.unlock();
+
+                m_lock.lock();
+                m_chanid  = chanid;
+                m_channum = QString::null;
+                m_lock.unlock();
+            }
+            else
+                m_tv->channelGroupLock.unlock();
+        }
+
+        if (direction == BROWSE_FAVORITE)
+            direction = BROWSE_UP;
+
+        InfoMap infoMap;
+        infoMap["dbstarttime"] = m_starttime;
+        infoMap["channum"]     = m_channum;
+        infoMap["chanid"]      = QString::number(m_chanid);
+
+        m_tv->GetPlayerReadLock(0,__FILE__,__LINE__);
+        bool still_there = false;
+        for (uint i = 0; i < m_tv->player.size() && !still_there; i++)
+            still_there |= (ctx == m_tv->player[0]);
+        if (still_there)
+        {
+            if (!db_browse_all_tuners)
+            {
+                GetNextProgram(direction, infoMap);
+            }
+            else
+            {
+                GetNextProgramDB(direction, infoMap);
+                while (!m_tv->IsTunable(ctx, infoMap["chanid"].toUInt()) &&
+                       (infoMap["channum"] != m_channum))
+                {
+                    GetNextProgramDB(direction, infoMap);
+                }
+            }
+        }
+        m_tv->ReturnPlayerLock(ctx);
+
+        m_lock.lock();
+        if (!m_ctx && !still_there)
+            continue;
+
+        VERBOSE(VB_IMPORTANT, QString("browsechanid: %1 -> %2")
+                .arg(m_chanid).arg(infoMap["chanid"].toUInt()));
+
+        m_channum = infoMap["channum"];
+        m_chanid  = infoMap["chanid"].toUInt();
+
+        if (((direction == BROWSE_LEFT) || (direction == BROWSE_RIGHT)) &&
+            !infoMap["dbstarttime"].isEmpty())
+        {
+            m_starttime = infoMap["dbstarttime"];
+        }
+
+        if (!m_list.empty())
+        {
+            // send partial info to UI for appearance of responsiveness
+            QCoreApplication::postEvent(
+                this, new UpdateBrowseInfoEvent(infoMap));
+            continue;
+        }
+        m_lock.unlock();
+
+        // pull in additional data from the DB...
+        QDateTime startts = QDateTime::fromString(
+            m_starttime, Qt::ISODate);
+        RecordingInfo recinfo(m_chanid, startts, false);
+        recinfo.ToMap(infoMap);
+        infoMap["iconpath"] = ChannelUtil::GetIcon(recinfo.GetChanID());
+
+        m_lock.lock();
+        if (m_ctx)
+        {
+            QCoreApplication::postEvent(
+                this, new UpdateBrowseInfoEvent(infoMap));
+        }
+    }
+}
Index: libs/libmythtv/tv_play.cpp
===================================================================
--- libs/libmythtv/tv_play.cpp	(revision 26235)
+++ libs/libmythtv/tv_play.cpp	(working copy)
@@ -58,6 +58,7 @@
 #include "videometadatautil.h"
 #include "mythdialogbox.h"
 #include "mythdirs.h"
+#include "tvbrowsehelper.h"
 
 #if ! HAVE_ROUND
 #define round(x) ((int) ((x) + 0.5))
@@ -421,8 +422,6 @@
     bool allowrerecord = tv->getAllowRerecord();
     bool deleterecording = tv->requestDelete;
 
-    tv->SaveChannelGroup();
-
     delete tv;
 
     if (curProgram)
@@ -857,9 +856,7 @@
     : // Configuration variables from database
       baseFilters(""),
       db_channel_format("<num> <sign>"),
-      db_time_format("h:mm AP"), db_short_date_format("M/d"),
       db_idle_timeout(0),           db_udpnotify_port(0),
-      db_browse_max_forward(14400),
       db_playback_exit_prompt(0),   db_autoexpire_default(0),
       db_auto_set_watched(false),   db_end_of_rec_exit_prompt(false),
       db_jump_prefer_osd(true),     db_use_gui_size_for_tv(false),
@@ -867,6 +864,7 @@
       db_run_jobs_on_remote(false), db_continue_embedded(false),
       db_use_fixed_size(true),      db_browse_always(false),
       db_browse_all_tuners(false),
+      db_use_channel_groups(false), db_remember_last_channel_group(false),
 
       arrowAccel(false),
       tryUnflaggedSkip(false),
@@ -902,9 +900,8 @@
       // Channel changing state variables
       queuedChanNum(""),
       lockTimerOn(false),
-      // channel browsing state variables
-      browsemode(false),
-      browsechannum(""), browsechanid(0), browsestarttime(""),
+      // channel browsing
+      browsehelper(NULL),
       // Program Info for currently playing video
       lastProgram(NULL),
       inPlaylist(false), underNetworkControl(false),
@@ -992,8 +989,10 @@
     kv["FFRewReposTime"]           = "100";
     kv["FFRewReverse"]             = "1";
 
-    kv["ChannelGroupDefault"]      = "-1";
     kv["BrowseChannelGroup"]       = "0";
+    kv["ChannelGroupDefault"]      = "-1";
+    kv["ChannelGroupRememberLast"] = "0";
+
     kv["VbiFormat"]                = "";
     kv["DecodeVBIFormat"]          = "";
 
@@ -1003,6 +1002,11 @@
 
     MythDB::getMythDB()->GetSettings(kv);
 
+    QString db_time_format;
+    QString db_short_date_format;
+    QString db_channel_ordering;
+    uint    db_browse_max_forward;
+
     // convert from minutes to ms.
     db_idle_timeout        = kv["LiveTVIdleTimeout"].toInt() * 60 * 1000;
     db_udpnotify_port      = kv["UDPNotifyPort"].toInt();
@@ -1020,48 +1024,48 @@
     db_use_fixed_size      = kv["UseFixedWindowSize"].toInt();
     db_browse_always       = kv["PersistentBrowseMode"].toInt();
     db_browse_all_tuners   = kv["BrowseAllTuners"].toInt();
-    QString channelOrdering= kv["ChannelOrdering"];
+    db_channel_ordering    = kv["ChannelOrdering"];
     baseFilters           += kv["CustomFilters"];
     db_channel_format      = kv["ChannelFormat"];
     db_time_format         = kv["TimeFormat"];
     db_short_date_format   = kv["ShortDateFormat"];
     arrowAccel             = kv["UseArrowAccels"].toInt();
     tryUnflaggedSkip       = kv["TryUnflaggedSkip"].toInt();
-    channel_group_id       = kv["ChannelGroupDefault"].toInt();
-    browse_changrp         = kv["BrowseChannelGroup"].toInt();
     smartForward           = kv["SmartForward"].toInt();
     ff_rew_repos           = kv["FFRewReposTime"].toFloat() * 0.01f;
     ff_rew_reverse         = kv["FFRewReverse"].toInt();
-    channel_group_id       = kv["ChannelGroupDefault"].toInt();
-    browse_changrp         = kv["BrowseChannelGroup"].toInt();
+
+    db_use_channel_groups  = kv["BrowseChannelGroup"].toInt();
+    db_remember_last_channel_group = kv["ChannelGroupRememberLast"].toInt();
+    channelGroupId         = kv["ChannelGroupDefault"].toInt();
+
     QString beVBI          = kv["VbiFormat"];
     QString feVBI          = kv["DecodeVBIFormat"];
 
+    if (db_use_channel_groups)
+    {
+        db_channel_groups  = ChannelGroup::GetChannelGroups();
+        if (channelGroupId > -1)
+        {
+            channelGroupChannelList = ChannelUtil::GetChannels(
+                0, true, "channum, callsign", channelGroupId);
+            ChannelUtil::SortChannels(
+                channelGroupChannelList, "channum", true);
+        }
+    }
+
     for (uint i = 0; i < sizeof(ff_rew_def)/sizeof(ff_rew_def[0]); i++)
         ff_rew_speeds.push_back(kv[QString("FFRewSpeed%1").arg(i)].toInt());
 
     // process it..
+    browsehelper = new TVBrowseHelper(
+        this,
+        db_time_format,          db_short_date_format,
+        db_browse_max_forward,   db_browse_all_tuners,
+        db_use_channel_groups,   db_channel_ordering);
 
-    if (db_browse_all_tuners)
-    {
-        QMutexLocker locker(&browseLock);
-        db_browse_all_channels = ChannelUtil::GetChannels(
-            0, true, "channum, callsign");
-        ChannelUtil::SortChannels(
-            db_browse_all_channels, channelOrdering, true);
-    }
-
     vbimode = VBIMode::Parse(!feVBI.isEmpty() ? feVBI : beVBI);
 
-    if (browse_changrp && (channel_group_id > -1))
-    {
-        m_channellist = ChannelUtil::GetChannels(
-            0, true, "channum, callsign", channel_group_id);
-        ChannelUtil::SortChannels(m_channellist, "channum", true);
-    }
-
-    m_changrplist  = ChannelGroup::GetChannelGroups();
-
     gCoreContext->addListener(this);
 
     QMutexLocker lock(&initFromDBLock);
@@ -1189,6 +1193,9 @@
 {
     VERBOSE(VB_PLAYBACK, "TV::~TV() -- begin");
 
+    if (browsehelper)
+        browsehelper->Stop();
+
     if (udpnotify)
     {
         udpnotify->deleteLater();
@@ -1243,6 +1250,12 @@
         }
     }
 
+    if (browsehelper)
+    {
+        delete browsehelper;
+        browsehelper = NULL;
+    }
+
     PlayerContext *mctx = GetPlayerWriteLock(0, __FILE__, __LINE__);
     while (!player.empty())
     {
@@ -1257,31 +1270,30 @@
 }
 
 /**
- * \brief save channel group setting to database
- */
-void TV::SaveChannelGroup(void)
-{
-    int remember_last_changrp = gCoreContext->GetNumSetting("ChannelGroupRememberLast", 0);
-
-    if (remember_last_changrp)
-       gCoreContext->SaveSetting("ChannelGroupDefault", channel_group_id);
-}
-
-/**
  * \brief update the channel list with channels from the selected channel group
  */
 void TV::UpdateChannelList(int groupID)
 {
-    if (groupID == channel_group_id)
+    if (!db_use_channel_groups)
         return;
 
-    channel_group_id = groupID;
+    QMutexLocker locker(&channelGroupLock);
+    if (groupID == channelGroupId)
+        return;
 
-    if (browse_changrp)
+    DBChanList list;
+    if (groupID != -1)
     {
-        m_channellist = ChannelUtil::GetChannels(0, true, "channum, callsign", channel_group_id);
-        ChannelUtil::SortChannels(m_channellist, "channum", true);
+        list = ChannelUtil::GetChannels(
+            0, true, "channum, callsign", groupID);
+        ChannelUtil::SortChannels(list, "channum", true);
     }
+
+    channelGroupId = groupID;
+    channelGroupChannelList = list;
+
+    if (db_remember_last_channel_group)
+        gCoreContext->SaveSetting("ChannelGroupDefault", channelGroupId);
 }
 
 /**
@@ -1595,7 +1607,7 @@
         OSD *osd = GetOSDLock(ctx);
         if (osd)
         {
-            BrowseEnd(ctx, false);
+            browsehelper->BrowseEnd(ctx, false);
             timeuntil = QDateTime::currentDateTime().secsTo((*it).expiry) * 1000;
             osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
             osd->DialogAddButton(record_watch, "DIALOG_ASKALLOW_WATCH_0",
@@ -1671,7 +1683,7 @@
         OSD *osd = GetOSDLock(ctx);
         if (osd && conflict_count > 1)
         {
-            BrowseEnd(ctx, false);
+            browsehelper->BrowseEnd(ctx, false);
             osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
             osd->DialogAddButton(let_recordm, "DIALOG_ASKALLOW_EXIT_0",
                                  false, true);
@@ -1680,7 +1692,7 @@
         }
         else if (osd)
         {
-            BrowseEnd(ctx, false);
+            browsehelper->BrowseEnd(ctx, false);
             osd->DialogShow(OSD_DLG_ASKALLOW, message, timeuntil);
             osd->DialogAddButton(let_record1, "DIALOG_ASKALLOW_EXIT_0",
                                  false, !has_rec);
@@ -2857,7 +2869,7 @@
     if (timer_id == browseTimerId)
     {
         PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
-        BrowseEnd(actx, false);
+        browsehelper->BrowseEnd(actx, false);
         ReturnPlayerLock(actx);
         handled = true;
     }
@@ -3333,60 +3345,56 @@
     ReturnPlayerLock(actx);
 }
 
+/// This selectively blocks KeyPress and Resize events
 bool TV::eventFilter(QObject *o, QEvent *e)
 {
-    const MythMainWindow *mainWindow = GetMythMainWindow();
-    if (mainWindow == o)
+    // We want to intercept all resize events sent to the main window
+    if ((e->type() == QEvent::Resize))
+        return (GetMythMainWindow()!=o)?false:event(e);
+
+    // We want to intercept all KeyPress events unless ignoreKeyPresses is set
+    if (QEvent::KeyPress == e->type())
+        return ignoreKeyPresses?false:event(e);
+
+    return QThread::eventFilter(o,e);
+}
+
+/// This handles all standard events
+bool TV::event(QEvent *e)
+{
+    if (QEvent::Resize == e->type())
     {
-        if (e->type() == QEvent::Resize)
-        {
-            PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
-            mctx->LockDeletePlayer(__FILE__, __LINE__);
-            if (mctx->player)
-                mctx->player->WindowResized(((const QResizeEvent*) e)->size());
-            mctx->UnlockDeletePlayer(__FILE__, __LINE__);
-            ReturnPlayerLock(mctx);
-        }
+        PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
+        mctx->LockDeletePlayer(__FILE__, __LINE__);
+        if (mctx->player)
+            mctx->player->WindowResized(((const QResizeEvent*) e)->size());
+        mctx->UnlockDeletePlayer(__FILE__, __LINE__);
+        ReturnPlayerLock(mctx);
+        return true;
     }
 
-    if (e->type() == MythEvent::MythEventMessage)
+    if (QEvent::KeyPress == e->type())
     {
-        customEvent(e);
+        QKeyEvent *k = new QKeyEvent(*(QKeyEvent *)e);
+        QMutexLocker locker(&timerIdLock);
+        keyList.enqueue(k);
+        if (!keyListTimerId)
+            keyListTimerId = StartTimer(1, __LINE__);
         return true;
     }
 
     switch (e->type())
     {
-        case QEvent::KeyPress:
-        {
-            // we ignore keypresses if the epg is running for example
-            if (!ignoreKeyPresses)
-            {
-                QKeyEvent *k = new QKeyEvent(*(QKeyEvent *)e);
-                QMutexLocker locker(&timerIdLock);
-                keyList.enqueue(k);
-                if (!keyListTimerId)
-                    keyListTimerId = StartTimer(1, __LINE__);
-
-                return true;
-             }
-             else
-             {
-                return false;
-             }
-        }
-
         case QEvent::Paint:
         case QEvent::UpdateRequest:
         case QEvent::Enter:
-        {
             DrawUnusedRects();
-            return false;
-        }
-
+            return true;
         default:
-            return false;
+            break;
     }
+
+    return QThread::event(e);
 }
 
 bool TV::HandleTrackAction(PlayerContext *ctx, const QString &action)
@@ -3405,7 +3413,7 @@
         handled = true;
         ctx->player->ToggleCaptions(kTrackTypeTextSubtitle);
     }
-    else if (action == "TOGGLECC" && !browsemode)
+    else if (action == "TOGGLECC" && !browsehelper->IsBrowsing())
     {
         handled = true;
         if (ccInputMode)
@@ -3534,7 +3542,7 @@
         bool pause = has_action("PAUSE",  actions);
         bool play  = has_action("PLAY",   actions);
 
-        if ((!esc || browsemode) && !pause && !play)
+        if ((!esc || browsehelper->IsBrowsing()) && !pause && !play)
             return;
     }
 
@@ -3694,31 +3702,31 @@
 
 bool TV::BrowseHandleAction(PlayerContext *ctx, const QStringList &actions)
 {
-    if (!browsemode)
+    if (!browsehelper->IsBrowsing())
         return false;
 
     bool handled = true;
 
     if (has_action("UP", actions) || has_action("CHANNELUP", actions))
-        BrowseDispInfo(ctx, BROWSE_UP);
+        browsehelper->BrowseDispInfo(ctx, BROWSE_UP);
     else if (has_action("DOWN", actions) || has_action("CHANNELDOWN", actions))
-        BrowseDispInfo(ctx, BROWSE_DOWN);
+        browsehelper->BrowseDispInfo(ctx, BROWSE_DOWN);
     else if (has_action("LEFT", actions))
-        BrowseDispInfo(ctx, BROWSE_LEFT);
+        browsehelper->BrowseDispInfo(ctx, BROWSE_LEFT);
     else if (has_action("RIGHT", actions))
-        BrowseDispInfo(ctx, BROWSE_RIGHT);
+        browsehelper->BrowseDispInfo(ctx, BROWSE_RIGHT);
     else if (has_action("NEXTFAV", actions))
-        BrowseDispInfo(ctx, BROWSE_FAVORITE);
+        browsehelper->BrowseDispInfo(ctx, BROWSE_FAVORITE);
     else if (has_action("SELECT", actions))
     {
-        BrowseEnd(ctx, true);
+        browsehelper->BrowseEnd(ctx, true);
     }
     else if (has_action("CLEAROSD",     actions) ||
              has_action("ESCAPE",       actions) ||
              has_action("BACK",         actions) ||
              has_action("TOGGLEBROWSE", actions))
     {
-        BrowseEnd(ctx, false);
+        browsehelper->BrowseEnd(ctx, false);
     }
     else if (has_action("TOGGLERECORD", actions))
         ToggleRecord(ctx);
@@ -4309,7 +4317,7 @@
     else if (has_action("TOGGLEBROWSE", actions))
     {
         if (islivetv)
-            BrowseStart(ctx);
+            browsehelper->BrowseStart(ctx);
         else if (!isDVD)
             ShowOSDMenu(ctx);
         else
@@ -4408,7 +4416,7 @@
         if (islivetv)
         {
             if (db_browse_always)
-                BrowseDispInfo(ctx, BROWSE_UP);
+                browsehelper->BrowseDispInfo(ctx, BROWSE_UP);
             else
                 ChangeChannel(ctx, CHANNEL_DIRECTION_UP);
         }
@@ -4424,7 +4432,7 @@
         if (islivetv)
         {
             if (db_browse_always)
-                BrowseDispInfo(ctx, BROWSE_DOWN);
+                browsehelper->BrowseDispInfo(ctx, BROWSE_DOWN);
             else
                 ChangeChannel(ctx, CHANNEL_DIRECTION_DOWN);
         }
@@ -4701,7 +4709,7 @@
                 vol = ctx->player->AdjustVolume(vol);
                 ctx->UnlockDeletePlayer(__FILE__, __LINE__);
 
-                if (!browsemode && !editmode)
+                if (!browsehelper->IsBrowsing() && !editmode)
                 {
                     UpdateOSDStatus(ctx, tr("Adjust Volume"), tr("Volume"),
                                     QString::number(vol),
@@ -6600,7 +6608,7 @@
     // in browse mode because in browse mode space/enter exit browse
     // mode and change to the currently browsed channel.
     if (StateIsLiveTV(GetState(ctx)) && !ccInputMode && !asInputMode &&
-        browsemode)
+        browsehelper->IsBrowsing())
     {
         commitSmart = ProcessSmartChannel(ctx, inputStr);
     }
@@ -6726,15 +6734,18 @@
     {
         QString channum = GetQueuedChanNum();
         QString chaninput = GetQueuedInput();
-        if (browsemode)
+        if (browsehelper->IsBrowsing())
         {
             commited = true;
             if (channum.isEmpty())
+                channum = browsehelper->GetBrowsedInfo().m_channum;
+
+            if ((ctx->recorder && ctx->recorder->CheckChannel(channum)) ||
+                (db_browse_all_tuners &&
+                 browsehelper->BrowseAllGetChanId(channum)))
             {
-                channum = browsechannum;
-                channum.detach();
+                browsehelper->BrowseChannel(ctx, channum);
             }
-            BrowseChannel(ctx, channum);
             HideOSDWindow(ctx, "osd_input");
         }
         else if (GetQueuedChanID() || !channum.isEmpty())
@@ -6750,32 +6761,40 @@
 
 void TV::ChangeChannel(PlayerContext *ctx, int direction)
 {
-    if ((browse_changrp || (direction == CHANNEL_DIRECTION_FAVORITE)) &&
-        (channel_group_id > -1))
+    if (db_use_channel_groups || (direction == CHANNEL_DIRECTION_FAVORITE))
     {
-       uint    chanid;
+        QMutexLocker locker(&channelGroupLock);
+        if (channelGroupId > -1)
+        {
+            locker.unlock();
+            ctx->LockPlayingInfo(__FILE__, __LINE__);
+            if (!ctx->playingInfo)
+            {
+                VERBOSE(VB_IMPORTANT,
+                        LOC_ERR + "ChangeChannel(): "
+                        "no active ctx playingInfo.");
+                ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+                ReturnPlayerLock(ctx);
+                return;
+            }
+            // Collect channel info
+            const ProgramInfo pginfo(*ctx->playingInfo);
+            uint old_chanid = pginfo.GetChanID();
+            ctx->UnlockPlayingInfo(__FILE__, __LINE__);
 
-       ctx->LockPlayingInfo(__FILE__, __LINE__);
-       if (!ctx->playingInfo)
-       {
-           VERBOSE(VB_IMPORTANT,
-                   LOC_ERR + "ChangeChannel(): no active ctx playingInfo.");
-           ctx->UnlockPlayingInfo(__FILE__, __LINE__);
-           ReturnPlayerLock(ctx);
-           return;
-       }
-
-       // Collect channel info
-       const ProgramInfo pginfo(*ctx->playingInfo);
-       uint    old_chanid  = pginfo.GetChanID();
-       ctx->UnlockPlayingInfo(__FILE__, __LINE__);
-
-       chanid = ChannelUtil::GetNextChannel(m_channellist, old_chanid, 0, direction);
-
-       ChangeChannel(ctx, chanid, "");
-       return;
+            locker.relock();
+            if (channelGroupId > -1)
+            {
+                uint chanid = ChannelUtil::GetNextChannel(
+                    channelGroupChannelList, old_chanid, 0, direction);
+                if (chanid)
+                    ChangeChannel(ctx, chanid, "");
+                return;
+            }
+        }
     }
-    else if (direction == CHANNEL_DIRECTION_FAVORITE)
+
+    if (direction == CHANNEL_DIRECTION_FAVORITE)
         direction = CHANNEL_DIRECTION_UP;
 
     QString oldinputname = ctx->recorder->GetInput();
@@ -7013,16 +7032,8 @@
     }
     ReturnOSDLock(ctx, osd);
 
-    if (browsemode)
-    {
-        QMutexLocker locker(&timerIdLock);
-        if (browseTimerId)
-        {
-            KillTimer(browseTimerId);
-            browseTimerId = 0;
-        }
-        browsemode = false;
-    }
+    if (browsehelper->IsBrowsing())
+        browsehelper->BrowseEnd(NULL, false);
 
     return res;
 }
@@ -7189,7 +7200,7 @@
 void TV::UpdateOSDSignal(const PlayerContext *ctx, const QStringList &strlist)
 {
     OSD *osd = GetOSDLock(ctx);
-    if (!osd || browsemode || !queuedChanNum.isEmpty())
+    if (!osd || browsehelper->IsBrowsing() || !queuedChanNum.isEmpty())
     {
         if (&ctx->lastSignalMsg != &strlist)
         {
@@ -7493,165 +7504,6 @@
 }
 
 
-/**
- *  \brief Fetches information on the desired program from the backend.
- *  \param enc RemoteEncoder to query, if null query the actx->recorder.
- *  \param direction BrowseDirection to get information on.
- *  \param infoMap InfoMap to fill in with returned data
- */
-void TV::GetNextProgram(RemoteEncoder *enc, BrowseDirection direction,
-                        InfoMap &infoMap) const
-{
-    QString title, subtitle, desc, category, endtime, callsign, iconpath;
-    QDateTime begts, endts;
-
-    QString starttime = infoMap["dbstarttime"];
-    QString chanid    = infoMap["chanid"];
-    QString channum   = infoMap["channum"];
-    QString seriesid  = infoMap["seriesid"];
-    QString programid = infoMap["programid"];
-
-    const PlayerContext *actx = NULL;
-    if (!enc)
-    {
-        actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
-        enc  = actx->recorder;
-    }
-
-    enc->GetNextProgram(direction,
-                        title,     subtitle,  desc,      category,
-                        starttime, endtime,   callsign,  iconpath,
-                        channum,   chanid,    seriesid,  programid);
-
-    if (actx)
-    {
-        enc = NULL; // once we've returned the lock, using enc is unsafe
-        ReturnPlayerLock(actx);
-    }
-
-    if (!starttime.isEmpty())
-        begts = QDateTime::fromString(starttime, Qt::ISODate);
-    else
-        begts = QDateTime::fromString(infoMap["dbstarttime"], Qt::ISODate);
-
-    infoMap["starttime"] = begts.toString(db_time_format);
-    infoMap["startdate"] = begts.toString(db_short_date_format);
-
-    infoMap["endtime"] = infoMap["enddate"] = "";
-    if (!endtime.isEmpty())
-    {
-        endts = QDateTime::fromString(endtime, Qt::ISODate);
-        infoMap["endtime"] = endts.toString(db_time_format);
-        infoMap["enddate"] = endts.toString(db_short_date_format);
-    }
-
-    infoMap["lenmins"] = tr("%n minute(s)", "", 0);
-    infoMap["lentime"] = "0:00";
-    if (begts.isValid() && endts.isValid())
-    {
-        QString lenM, lenHM;
-        format_time(begts.secsTo(endts), lenM, lenHM);
-        infoMap["lenmins"] = lenM;
-        infoMap["lentime"] = lenHM;
-    }
-
-    infoMap["dbstarttime"] = starttime;
-    infoMap["dbendtime"]   = endtime;
-    infoMap["title"]       = title;
-    infoMap["subtitle"]    = subtitle;
-    infoMap["description"] = desc;
-    infoMap["category"]    = category;
-    infoMap["callsign"]    = callsign;
-    infoMap["channum"]     = channum;
-    infoMap["chanid"]      = chanid;
-    infoMap["iconpath"]    = iconpath;
-    infoMap["seriesid"]    = seriesid;
-    infoMap["programid"]   = programid;
-}
-
-void TV::GetNextProgram(BrowseDirection direction, InfoMap &infoMap) const
-{
-    uint chanid = infoMap["chanid"].toUInt();
-    if (!chanid)
-        chanid = BrowseAllGetChanId(infoMap["channum"]);
-
-    int chandir = -1;
-    switch (direction)
-    {
-        case BROWSE_UP:       chandir = CHANNEL_DIRECTION_UP;       break;
-        case BROWSE_DOWN:     chandir = CHANNEL_DIRECTION_DOWN;     break;
-        case BROWSE_FAVORITE: chandir = CHANNEL_DIRECTION_FAVORITE; break;
-    }
-    if (direction != BROWSE_INVALID)
-    {
-        QMutexLocker locker(&browseLock);
-        chanid = ChannelUtil::GetNextChannel(
-            db_browse_all_channels, chanid, 0, chandir);
-    }
-
-    infoMap["chanid"] = QString::number(chanid);
-
-    {
-        QMutexLocker locker(&browseLock);
-        DBChanList::const_iterator it = db_browse_all_channels.begin();
-        for (; it != db_browse_all_channels.end(); ++it)
-        {
-            if ((*it).chanid == chanid)
-            {
-                QString tmp = (*it).channum;
-                tmp.detach();
-                infoMap["channum"] = tmp;
-                break;
-            }
-        }
-    }
-
-    QDateTime nowtime = QDateTime::currentDateTime();
-    QDateTime latesttime = nowtime.addSecs(6*60*60);
-    QDateTime browsetime = QDateTime::fromString(
-        infoMap["dbstarttime"], Qt::ISODate);
-
-    MSqlBindings bindings;
-    bindings[":CHANID"] = chanid;
-    bindings[":NOWTS"] = nowtime.toString("yyyy-MM-ddThh:mm:ss");
-    bindings[":LATESTTS"] = latesttime.toString("yyyy-MM-ddThh:mm:ss");
-    bindings[":BROWSETS"] = browsetime.toString("yyyy-MM-ddThh:mm:ss");
-    bindings[":BROWSETS2"] = browsetime.toString("yyyy-MM-ddThh:mm:ss");
-
-    QString querystr = " WHERE program.chanid = :CHANID ";
-    switch (direction)
-    {
-        case BROWSE_LEFT:
-            querystr += " AND program.endtime <= :BROWSETS "
-                " AND program.endtime > :NOWTS ";
-            break;
-
-        case BROWSE_RIGHT:
-            querystr += " AND program.starttime > :BROWSETS "
-                " AND program.starttime < :LATESTTS ";
-            break;
-
-        default:
-            querystr += " AND program.starttime <= :BROWSETS "
-                " AND program.endtime > :BROWSETS2 ";
-    };
-
-    ProgramList progList;
-    ProgramList dummySched;
-    LoadFromProgram(progList, querystr, bindings, dummySched, false);
-
-    if (progList.empty())
-    {
-        infoMap["dbstarttime"] = "";
-        return;
-    }
-
-    const ProgramInfo *prog = (direction == BROWSE_LEFT) ?
-        progList[progList.size() - 1] : progList[0];
-
-    infoMap["dbstarttime"] = prog->GetScheduledStartTime(ISODate);
-}
-
 bool TV::IsTunable(const PlayerContext *ctx, uint chanid, bool use_cache)
 {
     VERBOSE(VB_PLAYBACK, QString("IsTunable(%1)").arg(chanid));
@@ -7871,7 +7723,6 @@
     const ProgramInfo pginfo(*actx->playingInfo);
     uint    chanid  = pginfo.GetChanID();
     QString channum = pginfo.GetChanNum();
-    int changrpid   = channel_group_id;
     actx->UnlockPlayingInfo(__FILE__, __LINE__);
 
     ClearOSD(actx);
@@ -7928,8 +7779,13 @@
     {
         case kScheduleProgramGuide:
         {
+            channelGroupLock.lock();
+            int changrpid = channelGroupId;
+            channelGroupLock.unlock();
+
             isEmbedded = (isLiveTV && !pause_active && allowEmbedding);
-            RunProgramGuidePtr(chanid, channum, this, isEmbedded, true, changrpid);
+            RunProgramGuidePtr(chanid, channum, this,
+                               isEmbedded, true, changrpid);
             ignoreKeyPresses = true;
             break;
         }
@@ -7992,7 +7848,7 @@
     uint curvol = ctx->player->AdjustVolume((up) ? +2 : -2);
     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
 
-    if (!browsemode)
+    if (!browsehelper->IsBrowsing())
     {
         UpdateOSDStatus(ctx, tr("Adjust Volume"), tr("Volume"),
                         QString::number(curvol),
@@ -8050,7 +7906,7 @@
         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
     }
 
-    if (!browsemode)
+    if (!browsehelper->IsBrowsing())
     {
         if (!allowEdit)
         {
@@ -8080,7 +7936,7 @@
     else
         text = tr("Upmixer Off");
 
-    if (!browsemode)
+    if (!browsehelper->IsBrowsing())
         SetOSDMessage(ctx, text);
 }
 
@@ -8120,7 +7976,7 @@
     }
     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
 
-    if (!browsemode)
+    if (!browsehelper->IsBrowsing())
     {
         QString text;
         int val = (int)newval;
@@ -8199,7 +8055,7 @@
 
     text = tr("Sleep ") + " " + sleep_times[sleep_index].dispString;
 
-    if (!browsemode)
+    if (!browsehelper->IsBrowsing())
         SetOSDMessage(ctx, text);
 }
 
@@ -8376,8 +8232,24 @@
     ctx->UnlockDeletePlayer(__FILE__, __LINE__);
 }
 
+/// This handles all custom events
 void TV::customEvent(QEvent *e)
 {
+    if (e->type() == MythEvent::kUpdateBrowseInfoEventType)
+    {
+        UpdateBrowseInfoEvent *b = (UpdateBrowseInfoEvent*)e;
+        PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
+        OSD *osd = GetOSDLock(mctx);
+        if (osd)
+        {
+            osd->SetText("browse_info", b->im, kOSDTimeout_None);
+            osd->SetExpiry("browse_info", kOSDTimeout_None);
+        }
+        ReturnOSDLock(mctx, osd);
+        ReturnPlayerLock(mctx);
+        return;
+    }
+
     if (e->type() == DialogCompletionEvent::kEventType)
     {
         DialogCompletionEvent *dce = (DialogCompletionEvent *)e;
@@ -8772,204 +8644,17 @@
     }
 }
 
-/** \fn TV::BrowseStart(PlayerContext*)
- *  \brief Begins channel browsing.
- */
-void TV::BrowseStart(PlayerContext *ctx)
-{
-    VERBOSE(VB_IMPORTANT, "BrowseStart()");
-
-    if (ctx->paused)
-        return;
-
-    OSD *osd = GetOSDLock(ctx);
-    if (osd && osd->IsWindowVisible("browse_info"))
-    {
-        ReturnOSDLock(ctx, osd);
-        return;
-    }
-    ReturnOSDLock(ctx, osd);
-
-    ClearOSD(ctx);
-
-    ctx->LockPlayingInfo(__FILE__, __LINE__);
-    if (ctx->playingInfo)
-    {
-        browsemode      = true;
-        browsechannum   = ctx->playingInfo->GetChanNum();
-        browsechannum.detach();
-        browsechanid    = ctx->playingInfo->GetChanID();
-        browsestarttime = ctx->playingInfo->GetScheduledStartTime(ISODate);
-        ctx->UnlockPlayingInfo(__FILE__, __LINE__);
-
-        BrowseDispInfo(ctx, BROWSE_SAME);
-
-        QMutexLocker locker(&timerIdLock);
-        if (browseTimerId)
-            KillTimer(browseTimerId);
-        browseTimerId = StartTimer(kBrowseTimeout, __LINE__);
-    }
-    else
-    {
-        ctx->UnlockPlayingInfo(__FILE__, __LINE__);
-    }
-}
-
-/** \fn TV::BrowseEnd(PlayerContext*, bool)
- *  \brief Ends channel browsing. Changing the channel if change is true.
- *  \param change_channel iff true we call ChangeChannel()
- */
-void TV::BrowseEnd(PlayerContext *ctx, bool change_channel)
-{
-    VERBOSE(VB_IMPORTANT, "BrowseEnd()");
-    {
-        QMutexLocker locker(&timerIdLock);
-        if (browseTimerId)
-        {
-            KillTimer(browseTimerId);
-            browseTimerId = 0;
-        }
-    }
-
-    if (!browsemode)
-        return;
-
-    HideOSDWindow(ctx, "browse_info");
-
-    if (change_channel)
-        ChangeChannel(ctx, 0, browsechannum);
-
-    browsemode = false;
-}
-
-/** \brief Fetches browse info from backend and sends it to the OSD.
- *  \param direction BrowseDirection to get information on.
- */
-void TV::BrowseDispInfo(PlayerContext *ctx, BrowseDirection direction)
-{
-    VERBOSE(VB_IMPORTANT, "BrowseDispInfo()");
-
-    if (!browsemode)
-        BrowseStart(ctx);
-
-    // if browsing channel groups is enabled or direction if BROWSE_FAVORITES
-    // Then pick the next channel in the channel group list to browse
-    // If channel group is ALL CHANNELS (-1), then bypass picking from
-    // the channel group list
-    if ((browse_changrp || (direction == BROWSE_FAVORITE)) &&
-        (channel_group_id > -1) && (direction != BROWSE_SAME) &&
-        (direction != BROWSE_RIGHT) && (direction != BROWSE_LEFT))
-    {
-        uint chanid;
-        int  dir;
-
-        if ( (direction == BROWSE_UP) || (direction == BROWSE_FAVORITE) )
-            dir = CHANNEL_DIRECTION_UP;
-        else if (direction == BROWSE_DOWN)
-            dir = CHANNEL_DIRECTION_DOWN;
-        else // this should never happen, but just in case
-            dir = direction;
-
-        chanid = ChannelUtil::GetNextChannel(
-            m_channellist, browsechanid, 0, dir);
-        VERBOSE(VB_IMPORTANT, QString("Get channel: %1").arg(chanid));
-        browsechanid  = chanid;
-        browsechannum = QString::null;
-        direction     = BROWSE_SAME;
-    }
-    else if ((channel_group_id == -1) && (direction == BROWSE_FAVORITE))
-        direction = BROWSE_UP;
-
-    OSD *osd = GetOSDLock(ctx);
-    if (ctx->paused || !osd)
-    {
-        ReturnOSDLock(ctx, osd);
-        return;
-    }
-
-    {
-        QMutexLocker locker(&timerIdLock);
-        if (browseTimerId)
-            KillTimer(browseTimerId);
-        browseTimerId = StartTimer(kBrowseTimeout, __LINE__);
-    }
-
-    QDateTime lasttime = QDateTime::fromString(browsestarttime, Qt::ISODate);
-    QDateTime curtime  = QDateTime::currentDateTime();
-    if (lasttime < curtime)
-        browsestarttime = curtime.toString(Qt::ISODate);
-
-    QDateTime maxtime  = curtime.addSecs(db_browse_max_forward);
-    if ((lasttime > maxtime) && (direction == BROWSE_RIGHT))
-    {
-        ReturnOSDLock(ctx, osd);
-        return;
-    }
-
-    InfoMap infoMap;
-    infoMap["dbstarttime"] = browsestarttime;
-    infoMap["channum"]     = browsechannum;
-    infoMap["chanid"]      = QString::number(browsechanid);
-
-    if (ctx->recorder && !db_browse_all_tuners)
-    {
-        GetNextProgram(ctx->recorder, direction, infoMap);
-    }
-    else
-    {
-        GetNextProgram(direction, infoMap);
-        while (!IsTunable(ctx, infoMap["chanid"].toUInt()) &&
-               (infoMap["channum"] != browsechannum))
-        {
-            GetNextProgram(direction, infoMap);
-        }
-    }
-
-    VERBOSE(VB_IMPORTANT, QString("browsechanid: %1 -> %2")
-            .arg(browsechanid).arg(infoMap["chanid"].toUInt()));
-
-    browsechannum = infoMap["channum"];
-    browsechanid  = infoMap["chanid"].toUInt();
-
-    if (((direction == BROWSE_LEFT) || (direction == BROWSE_RIGHT)) &&
-        !infoMap["dbstarttime"].isEmpty())
-    {
-        browsestarttime = infoMap["dbstarttime"];
-    }
-
-    // pull in additional data from the DB...
-    QDateTime startts = QDateTime::fromString(browsestarttime, Qt::ISODate);
-    RecordingInfo recinfo(browsechanid, startts, false);
-    recinfo.ToMap(infoMap);
-    infoMap["iconpath"] = ChannelUtil::GetIcon(recinfo.GetChanID());
-
-    osd->SetText("browse_info", infoMap, kOSDTimeout_None);
-    osd->SetExpiry("browse_info", kOSDTimeout_None);
-    ReturnOSDLock(ctx, osd);
-}
-
-uint TV::BrowseAllGetChanId(const QString &chan) const
-{
-    QMutexLocker locker(&browseLock);
-    DBChanList::const_iterator it = db_browse_all_channels.begin();
-    for (; it != db_browse_all_channels.end(); ++it)
-    {
-        if ((*it).channum == chan)
-            return (*it).chanid;
-    }
-    return 0;
-}
-
 void TV::ToggleRecord(PlayerContext *ctx)
 {
-    if (browsemode)
+    BrowseInfo bi = browsehelper->GetBrowsedInfo();
+    if (bi.m_chanid)
     {
         InfoMap infoMap;
         QDateTime startts = QDateTime::fromString(
-            browsestarttime, Qt::ISODate);
+            bi.m_starttime, Qt::ISODate);
 
         RecordingInfo::LoadStatus status;
-        RecordingInfo recinfo(browsechanid, startts, false, 0, &status);
+        RecordingInfo recinfo(bi.m_chanid, startts, false, 0, &status);
         if (RecordingInfo::kFoundProgram == status)
             recinfo.ToggleRecord();
         recinfo.ToMap(infoMap);
@@ -9040,20 +8725,6 @@
     SetOSDMessage(ctx, msg);
 }
 
-void TV::BrowseChannel(PlayerContext *ctx, const QString &chan)
-{
-    if (!ctx->recorder || !ctx->recorder->CheckChannel(chan))
-    {
-        if (!db_browse_all_tuners || !BrowseAllGetChanId(chan))
-            return;
-    }
-
-    browsechannum = chan;
-    browsechannum.detach();
-    browsechanid = 0;
-    BrowseDispInfo(ctx, BROWSE_SAME);
-}
-
 void TV::HandleOSDClosed(int osdType)
 {
     switch (osdType)
@@ -9988,63 +9659,64 @@
     }
     else if (action == "GUIDE")
         EditSchedule(actx, kScheduleProgramGuide);
-    else if (action.left(10) == "CHANGROUP_")
+    else if (action.left(10) == "CHANGROUP_" && db_use_channel_groups)
     {
         if (action == "CHANGROUP_ALL_CHANNELS")
-            channel_group_id = -1;
+        {
+            UpdateChannelList(-1);
+        }
         else
         {
             action.remove("CHANGROUP_");
 
-            channel_group_id = action.toInt();
+            UpdateChannelList(action.toInt());
 
-            if (browse_changrp)
+            QMutexLocker locker(&channelGroupLock);
+            const DBChanList &list = channelGroupChannelList;
+
+            // make sure the current channel is from the selected group
+            // or tune to the first in the group
+            if (actx->tvchain)
             {
-                m_channellist = ChannelUtil::GetChannels(0, true, "channum, callsign", channel_group_id);
-                ChannelUtil::SortChannels(m_channellist, "channum", true);
+                QString cur_channum = actx->tvchain->GetChannelName(-1);
+                QString new_channum = cur_channum;
 
-                // make sure the current channel is from the selected group
-                // or tune to the first in the group
-                if (actx->tvchain)
+                DBChanList::const_iterator it = list.begin();
+                for (; it != list.end(); ++it)
                 {
-                    QString cur_channum = actx->tvchain->GetChannelName(-1);
-                    QString new_channum = cur_channum;
-
-                    DBChanList::const_iterator it = m_channellist.begin();
-                    for (; it != m_channellist.end(); ++it)
+                    if ((*it).channum == cur_channum)
                     {
-                        if ((*it).channum == cur_channum)
-                        {
-                            break;
-                        }
+                        break;
                     }
+                }
 
-                    if (it == m_channellist.end())
-                    {
-                        // current channel not found so switch to the
-                        // first channel in the group
-                        it = m_channellist.begin();
-                        if (it != m_channellist.end())
-                            new_channum = (*it).channum;
-                    }
+                if (it == list.end())
+                {
+                    // current channel not found so switch to the
+                    // first channel in the group
+                    it = list.begin();
+                    if (it != list.end())
+                        new_channum = (*it).channum;
+                }
 
-                    VERBOSE(VB_IMPORTANT, LOC + QString("Channel Group: '%1'->'%2'")
-                            .arg(cur_channum).arg(new_channum));
+                VERBOSE(VB_IMPORTANT, LOC +
+                        QString("Channel Group: '%1'->'%2'")
+                        .arg(cur_channum).arg(new_channum));
 
-                    // Only change channel if new channel != current channel
-                    if (cur_channum != new_channum && !new_channum.isEmpty())
-                    {
-                        QMutexLocker locker(&timerIdLock);
-                        queuedInput   = new_channum; queuedInput.detach();
-                        queuedChanNum = new_channum; queuedChanNum.detach();
-                        queuedChanID  = 0;
-                        if (!queueInputTimerId)
-                            queueInputTimerId = StartTimer(10, __LINE__);
-                    }
-
-                    // Turn off OSD Channel Num so the channel changes right away
-                    HideOSDWindow(actx, "osd_input");
+                // Only change channel if new channel != current channel
+                if (cur_channum != new_channum && !new_channum.isEmpty())
+                {
+                    QMutexLocker locker(&timerIdLock);
+                    queuedInput   = new_channum; queuedInput.detach();
+                    queuedChanNum = new_channum; queuedChanNum.detach();
+                    queuedChanID  = 0;
+                    if (!queueInputTimerId)
+                        queueInputTimerId = StartTimer(10, __LINE__);
                 }
+
+                // Turn off OSD Channel Num so the channel
+                // changes right away
+                HideOSDWindow(actx, "osd_input");
             }
         }
     }
@@ -10066,7 +9738,7 @@
     else if (StateIsLiveTV(GetState(actx)))
     {
         if (action == "TOGGLEBROWSE")
-            BrowseStart(actx);
+            browsehelper->BrowseStart(actx);
         else if (action == "PREVCHAN")
             PopPreviousChannel(actx, true);
         else if (action.left(14) == "SWITCHTOINPUT_")
@@ -10930,7 +10602,7 @@
         osd->DialogAddButton(tr("Sleep"),
                              "DIALOG_MENU_SLEEP_0", true,
                               selected == "SLEEP");
-        if (browse_changrp)
+        if (db_use_channel_groups)
         {
             osd->DialogAddButton(tr("Channel Groups"),
                                  "DIALOG_MENU_CHANNELGROUP_0",
@@ -10972,10 +10644,14 @@
         currenttext = tr("Channel Groups");
         osd->DialogAddButton(tr("All Channels"), "CHANGROUP_ALL_CHANNELS");
         ChannelGroupList::const_iterator it;
-        for (it = m_changrplist.begin(); it != m_changrplist.end(); ++it)
+        for (it = db_channel_groups.begin();
+             it != db_channel_groups.end(); ++it)
+        {
+            QMutexLocker locker(&channelGroupLock);
             osd->DialogAddButton(it->name,
                                  QString("CHANGROUP_%1").arg(it->grpid),
-                                 false, (int)(it->grpid) == channel_group_id);
+                                 false, (int)(it->grpid) == channelGroupId);
+        }
     }
     else if (category == "PIP" && (allowPIP || allowPBP))
     {
Index: libs/libmyth/programinfo.cpp
===================================================================
--- libs/libmyth/programinfo.cpp	(revision 26235)
+++ libs/libmyth/programinfo.cpp	(working copy)
@@ -3971,6 +3971,8 @@
 QStringList ProgramInfo::LoadFromScheduler(
     const QString &tmptable, int recordid)
 {
+    assert(!gCoreContext->IsUIThread());
+
     QStringList slist;
     if (gCoreContext->IsBackend())
     {
Index: libs/libmythdb/mythevent.h
===================================================================
--- libs/libmythdb/mythevent.h	(revision 26235)
+++ libs/libmythdb/mythevent.h	(working copy)
@@ -1,9 +1,10 @@
 #ifndef MYTHEVENT_H_
 #define MYTHEVENT_H_
 
-#include <QString>
 #include <QStringList>
 #include <QEvent>
+#include <QHash>
+
 #include "mythexp.h"
 
 /** \class MythEvent
@@ -58,6 +59,7 @@
     static Type kPopDisableDrawingEventType;
     static Type kLockInputDevicesEventType;
     static Type kUnlockInputDevicesEventType;
+    static Type kUpdateBrowseInfoEventType;
 
   private:
     QString message;
@@ -78,4 +80,12 @@
     int keycode;
 };
 
+class MPUBLIC UpdateBrowseInfoEvent : public QEvent
+{
+  public:
+    UpdateBrowseInfoEvent(const QHash<QString,QString> &infoMap) :
+        QEvent(MythEvent::kUpdateBrowseInfoEventType), im(infoMap) {}
+    QHash<QString,QString> im;
+};
+
 #endif /* MYTHEVENT_H */
Index: libs/libmythdb/mythevent.cpp
===================================================================
--- libs/libmythdb/mythevent.cpp	(revision 26235)
+++ libs/libmythdb/mythevent.cpp	(working copy)
@@ -18,5 +18,7 @@
     (QEvent::Type) QEvent::registerEventType();
 QEvent::Type MythEvent::kUnlockInputDevicesEventType =
     (QEvent::Type) QEvent::registerEventType();
+QEvent::Type MythEvent::kUpdateBrowseInfoEventType =
+    (QEvent::Type) QEvent::registerEventType();
 QEvent::Type ExternalKeycodeEvent::kEventType =
     (QEvent::Type) QEvent::registerEventType();
