Ticket #12809: 0001-Use-lastPlayPos-instead-of-bookmark.patch

File 0001-Use-lastPlayPos-instead-of-bookmark.patch, 81.4 KB (added by Roger Siddons, 4 years ago)
  • mythtv/libs/libmyth/programinfo.cpp

    From 5bafa67ba68e6c37f7e4074262176ada205ae41d Mon Sep 17 00:00:00 2001
    From: Roger Siddons <rsiddons@mythtv.org>
    Date: Fri, 6 May 2016 16:12:19 +0100
    Subject: [PATCH 1/3] =?UTF-8?q?Use=20lastPlayPos=20instead=20of=20bookmark?=
     =?UTF-8?q?=E2=80=8B?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Enable 'Play from last play position'
    
    38443b8e disabled playing recordings from last play position mark.
    This patch re-enables it for recordings.
    
    Add MConcurrent
    
    Provides a simple version of QtConcurrent::run() that uses MThreadPool rather
    than QThreadPool. Useful for starting background threads in 1 line.
    
    Given a class method of:
    
      void Class::fn(arg1, arg2...)
    
    you can run it in a different thread using:
    
      MConcurrent::run("thread name", &Class instance, &Class::fn, arg1, arg2...)
    
    Refer to QtConcurrent::run for further details
    
    Restrictions:
    1. Accepts 0-5 arguments
    2. Only class methods are supported (most typical in Myth)
    3. Only non-const classes & methods are supported (most typical in Myth)
    4. The method must have return type of void (QFuture is not easily ported to
       MThreadPool. Use signals/events instead)
    
    Add UI progress indicator to Watch Recordings
    
    Adds theme widget 'progresspercent' to watchrecordings window to show
    percentage of recording that has been watched.
    
    MythUI: Implement progressbar on buttonlist items
    
    Allows buttonlists to contain a progressbar.
    
    Show play position as a progress bar in Watch recordings
    
    Allows recording list to show part-watched recordings using a progressbar.
    Demo uses Mythcenter-wide theme
    
    Move automatic bookmark updates to last play position.
    
    Convert bookmarks to a user aid only. They will never be automatically updated/removed by playback.
    Last play position mark is now used instead.
    
    Settings "Clear bookmark on playback" & "Action on playback exit"/"Save position and exit" are
    removed (from UI only) as they are now redundant.
    
    Last play position is never updated within 30 secs of playback start.
    Thereafter it records position on playback exit (as well as the existing periodic 30 sec update).
    At end of recording it is reset.
    
    Note: 'Automatically Mark watched' is now also actioned by end-of-playback dialog (it wasn't previously)
    It also will not trigger for first 30 secs of playback.
    
    Default recording playback from last play position
    
    Playback of recordings now starts from:
    
    - last play position, if present
    - bookmark, if present (so user can override progstart)
    - program start mark, if present
    - beginning of file
    
    Menu options allow user to start from last play position, bookmark or 'beginning'
    (prog start/file start) where applicable.
    
    Add menu options to Clear bookmark and Clear last play position for recordings
    
    Allow user to easily reset last play position and bookmark.
    
    Start video playback as per recordings
    
    Video playback starts from:
    
    - last played position, if present
    - bookmark, if present
    - file start
    
    Menu options allow user to explicitly select option and reset last played position & bookmark
    
    Plays from last played position, then bookmark, then file start
    Add menu options to Play from bookmark & Play from beginning
    Add menu options to Clear Bookmark & Clear last played position
    
    Preview generator uses last played position
    
    Previews are now generated from:
    
    - last played position, if present
    - bookmark, if present,
    - prog start mark, if present
    - existing offset, which us unchanged (it does not use the progstart mark.)
    
    Add progress bar to Upcoming Recordings.
    
    Progress of an active recording is shown by a progress bar and
    'progresspercent' theme widget. Both are based on rec start/end and current time.
    
    diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
    index d845a28..866d077 100644
    a b ProgramInfo::ProgramInfo(void) : 
    230230
    231231    // everything below this line is not serialized
    232232    availableStatus(asAvailable),
     233    progressPercent(0),
    233234    spread(-1),
    234235    startCol(-1),
    235236    sortTitle(),
    ProgramInfo::ProgramInfo(const ProgramInfo &other) : 
    314315
    315316    // everything below this line is not serialized
    316317    availableStatus(other.availableStatus),
     318    progressPercent(other.progressPercent),
    317319    spread(other.spread),
    318320    startCol(other.startCol),
    319321    sortTitle(other.sortTitle),
    ProgramInfo::ProgramInfo( 
    502504
    503505    // everything below this line is not serialized
    504506    availableStatus(asAvailable),
     507    progressPercent(0),
    505508    spread(-1),
    506509    startCol(-1),
    507510    sortTitle(),
    ProgramInfo::ProgramInfo( 
    620623
    621624    // everything below this line is not serialized
    622625    availableStatus(asAvailable),
     626    progressPercent(0),
    623627    spread(-1),
    624628    startCol(-1),
    625629    sortTitle(),
    ProgramInfo::ProgramInfo( 
    751755
    752756    // everything below this line is not serialized
    753757    availableStatus(asAvailable),
     758    progressPercent(0),
    754759    spread(-1),
    755760    startCol(-1),
    756761    sortTitle(),
    ProgramInfo::ProgramInfo( 
    905910
    906911    // everything below this line is not serialized
    907912    availableStatus(asAvailable),
     913    progressPercent(0),
    908914    spread(-1),
    909915    startCol(-1),
    910916    sortTitle(),
    void ProgramInfo::clear(void) 
    12591265    spread = -1;
    12601266    startCol = -1;
    12611267    availableStatus = asAvailable;
     1268    progressPercent = 0;
    12621269
    12631270    // Private
    12641271    inUseForWhat.clear();
    void ProgramInfo::ToMap(InfoMap &progMap, 
    17021709        progMap["lentime"] = QObject::tr("%n hour(s)","", hours);
    17031710    }
    17041711
     1712    progMap["progresspercent"] =
     1713            GetProgressPercent() > 0 ? QString::number(GetProgressPercent()) : "";
     1714
    17051715    progMap["rectypechar"] = toQChar(GetRecordingRuleType());
    17061716    progMap["rectype"] = ::toString(GetRecordingRuleType());
    17071717    QString tmp_rec = progMap["rectype"];
    void ProgramInfo::SaveBookmark(uint64_t frame) 
    26652675
    26662676    set_flag(programflags, FL_BOOKMARK, is_valid);
    26672677
     2678    UpdateMarkTimeStamp(is_valid);
     2679    SendUpdateEvent();
     2680}
     2681
     2682void ProgramInfo::SaveLastPlayPos(uint64_t frame, bool notify)
     2683{
     2684    LOG(VB_PLAYBACK, LOG_DEBUG,
     2685        QString("LastPlayPos frame=%1").arg(frame));
     2686    ClearMarkupMap(MARK_UTIL_LASTPLAYPOS);
     2687
     2688    if (frame > 0)
     2689    {
     2690        frm_dir_map_t lastPlayPosMap;
     2691        lastPlayPosMap[frame] = MARK_UTIL_LASTPLAYPOS;
     2692        SaveMarkupMap(lastPlayPosMap, MARK_UTIL_LASTPLAYPOS);
     2693    }
     2694
     2695    UpdateMarkTimeStamp(IsBookmarkSet());
     2696
     2697    if (notify)
     2698        SendUpdateEvent();
     2699}
     2700
     2701void ProgramInfo::UpdateMarkTimeStamp(bool bookmarked)
     2702{
    26682703    if (IsRecording())
    26692704    {
    26702705        MSqlQuery query(MSqlQuery::InitCon());
    void ProgramInfo::SaveBookmark(uint64_t frame) 
    26722707            "UPDATE recorded "
    26732708            "SET bookmarkupdate = CURRENT_TIMESTAMP, "
    26742709            "    bookmark       = :BOOKMARKFLAG "
    2675             "WHERE chanid    = :CHANID AND "
    2676             "      starttime = :STARTTIME");
     2710            "WHERE recordedid = :RECORDEDID");
    26772711
    2678         query.bindValue(":BOOKMARKFLAG", is_valid);
    2679         query.bindValue(":CHANID",       chanid);
    2680         query.bindValue(":STARTTIME",    recstartts);
     2712        query.bindValue(":BOOKMARKFLAG", bookmarked);
     2713        query.bindValue(":RECORDEDID",   recordedid);
    26812714
    26822715        if (!query.exec())
    26832716            MythDB::DBError("bookmark flag update", query);
    2684 
    2685         SendUpdateEvent();
    26862717    }
    26872718}
    26882719
    uint64_t ProgramInfo::QueryProgStart(void) const 
    27692800    return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
    27702801}
    27712802
     2803uint64_t ProgramInfo::QueryStartMark(void) const
     2804{
     2805    uint64_t start = 0;
     2806    if ((start = QueryLastPlayPos()) > 0)
     2807        LOG(VB_PLAYBACK, LOG_INFO, QString("Using last position @ %1").arg(start));
     2808    else if ((start = QueryBookmark()) > 0)
     2809        LOG(VB_PLAYBACK, LOG_INFO, QString("Using bookmark @ %1").arg(start));
     2810    else if (HasCutlist())
     2811        // Disable progstart if the program has a cutlist.
     2812        LOG(VB_PLAYBACK, LOG_INFO, "Ignoring progstart as cutlist exists");
     2813    else if ((start = QueryProgStart()) > 0)
     2814        LOG(VB_PLAYBACK, LOG_INFO, QString("Using progstart @ %1").arg(start));
     2815    else
     2816        LOG(VB_PLAYBACK, LOG_INFO, "Using file start");
     2817    return start;
     2818}
     2819
    27722820/** \brief Gets any lastplaypos position in database,
    27732821 *         unless the ignore lastplaypos flag is set.
    27742822 *
  • mythtv/libs/libmyth/programinfo.h

    diff --git a/mythtv/libs/libmyth/programinfo.h b/mythtv/libs/libmyth/programinfo.h
    index 2a1cb20..ca7aa91 100644
    a b class MPUBLIC ProgramInfo 
    6969  public:
    7070    enum CategoryType { kCategoryNone, kCategoryMovie, kCategorySeries,
    7171                        kCategorySports, kCategoryTVShow };
    72                        
     72
    7373    /// Null constructor
    7474    ProgramInfo(void);
    7575    /// Copy constructor
    class MPUBLIC ProgramInfo 
    548548    void SetPositionMapDBReplacement(PMapDBReplacement *pmap)
    549549        { positionMapDBReplacement = pmap; }
    550550
     551    uint GetProgressPercent() const        { return progressPercent; }
     552    void SetProgressPercent(uint progress) { progressPercent = progress; }
     553
    551554    // Slow DB gets
    552555    QString     QueryBasename(void) const;
    553556//  uint64_t    QueryFilesize(void) const; // TODO Remove
    class MPUBLIC ProgramInfo 
    556559    uint64_t    QueryBookmark(void) const;
    557560    uint64_t    QueryProgStart(void) const;
    558561    uint64_t    QueryLastPlayPos(void) const;
     562    uint64_t    QueryStartMark(void) const;
    559563    CategoryType QueryCategoryType(void) const;
    560564    QStringList QueryDVDBookmark(const QString &serialid) const;
    561565    QStringList QueryBDBookmark(const QString &serialid) const;
    class MPUBLIC ProgramInfo 
    581585
    582586    // Slow DB sets
    583587    virtual void SaveFilesize(uint64_t fsize); /// TODO Move to RecordingInfo
     588    void SaveLastPlayPos(uint64_t frame, bool notify = true);
    584589    void SaveBookmark(uint64_t frame);
    585590    void SaveDVDBookmark(const QStringList &fields) const;
    586591    void SaveBDBookmark(const QStringList &fields) const;
    class MPUBLIC ProgramInfo 
    702707    bool FromStringList(QStringList::const_iterator &it,
    703708                        QStringList::const_iterator  end);
    704709
     710    void UpdateMarkTimeStamp(bool bookmarked);
     711
    705712    static void QueryMarkupMap(
    706713        const QString &video_pathname,
    707714        frm_dir_map_t&, MarkTypes type, bool merge = false);
    class MPUBLIC ProgramInfo 
    782789
    783790// everything below this line is not serialized
    784791    uint8_t availableStatus; // only used for playbackbox.cpp
     792    uint  progressPercent; // only used by UI
     793
    785794  public:
    786795    void SetAvailableStatus(AvailableStatusType status, const QString &where);
    787796    AvailableStatusType GetAvailableStatus(void) const
  • mythtv/libs/libmythbase/libmythbase.pro

    diff --git a/mythtv/libs/libmythbase/libmythbase.pro b/mythtv/libs/libmythbase/libmythbase.pro
    index 228617a..3e44184 100644
    a b INSTALLS = target 
    1010QMAKE_CLEAN += $(TARGET) $(TARGETA) $(TARGETD) $(TARGET0) $(TARGET1) $(TARGET2)
    1111
    1212# Input
    13 HEADERS += mthread.h mthreadpool.h
     13HEADERS += mthread.h mthreadpool.h mconcurrent.h
    1414HEADERS += mythsocket.h mythsocket_cb.h
    1515HEADERS += mythbaseexp.h mythdbcon.h mythdb.h mythdbparams.h oldsettings.h
    1616HEADERS += verbosedefs.h mythversion.h compat.h mythconfig.h
  • new file mythtv/libs/libmythbase/mconcurrent.h

    diff --git a/mythtv/libs/libmythbase/mconcurrent.h b/mythtv/libs/libmythbase/mconcurrent.h
    new file mode 100644
    index 0000000..5990763
    - +  
     1#ifndef MCONCURRENT_H
     2#define MCONCURRENT_H
     3
     4#include "mthreadpool.h"
     5#include "logging.h"
     6
     7
     8/// Provides a simple version of QtConcurrent::run() that uses MThreadPool rather
     9/// than QThreadPool. Useful for starting background threads in 1 line.
     10///
     11/// Given a class method of:
     12///
     13///   void Class::fn(arg1, arg2...)
     14///
     15/// you can run it in a different thread using:
     16///
     17///   MConcurrent::run("thread name", &Class instance, &Class::fn, arg1, arg2...)
     18///
     19/// Refer to QtConcurrent::run for further details
     20///
     21/// Restrictions:
     22/// 1. Accepts 0-5 arguments
     23/// 2. Only class methods are supported (most typical in Myth)
     24/// 3. Only non-const classes & methods are supported (most typical in Myth)
     25/// 4. The method must have return type of void (QFuture is not easily ported to
     26/// MThreadPool. Use signals/events instead)
     27///
     28namespace MConcurrent {
     29
     30class RunFunctionTask : public QRunnable
     31{
     32public:
     33    void start(QString name)
     34    {
     35        MThreadPool::globalInstance()->start(this, name, /*m_priority*/ 0);
     36    }
     37
     38    virtual void runFunctor() = 0;
     39
     40    void run()
     41    {
     42        try
     43        {
     44            this->runFunctor();
     45        }
     46        catch (...)
     47        {
     48            LOG(VB_GENERAL, LOG_ERR, "An exception occurred");
     49        }
     50    }
     51};
     52
     53template <typename Class>
     54class VoidStoredMemberFunctionPointerCall0 : public RunFunctionTask
     55{
     56public:
     57    VoidStoredMemberFunctionPointerCall0(void (Class::*_fn)() , Class *_object)
     58    : fn(_fn), object(_object) { }
     59
     60    void runFunctor() { (object->*fn)(); }
     61private:
     62    void (Class::*fn)();
     63    Class *object;
     64};
     65
     66template <typename Class, typename Param1, typename Arg1>
     67class VoidStoredMemberFunctionPointerCall1 : public RunFunctionTask
     68{
     69public:
     70    VoidStoredMemberFunctionPointerCall1(void (Class::*_fn)(Param1) , Class *_object, const Arg1 &_arg1)
     71    : fn(_fn), object(_object), arg1(_arg1){ }
     72
     73    void runFunctor() { (object->*fn)(arg1); }
     74private:
     75    void (Class::*fn)(Param1);
     76    Class *object;
     77    Arg1 arg1;
     78};
     79
     80template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2>
     81class VoidStoredMemberFunctionPointerCall2 : public RunFunctionTask
     82{
     83public:
     84    VoidStoredMemberFunctionPointerCall2(void (Class::*_fn)(Param1, Param2) , Class *_object, const Arg1 &_arg1, const Arg2 &_arg2)
     85    : fn(_fn), object(_object), arg1(_arg1), arg2(_arg2){ }
     86
     87    void runFunctor() { (object->*fn)(arg1, arg2); }
     88private:
     89    void (Class::*fn)(Param1, Param2);
     90    Class *object;
     91    Arg1 arg1; Arg2 arg2;
     92};
     93
     94template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2, typename Param3, typename Arg3>
     95class VoidStoredMemberFunctionPointerCall3 : public RunFunctionTask
     96{
     97public:
     98    VoidStoredMemberFunctionPointerCall3(void (Class::*_fn)(Param1, Param2, Param3) , Class *_object, const Arg1 &_arg1, const Arg2 &_arg2, const Arg3 &_arg3)
     99    : fn(_fn), object(_object), arg1(_arg1), arg2(_arg2), arg3(_arg3){ }
     100
     101    void runFunctor() { (object->*fn)(arg1, arg2, arg3); }
     102private:
     103    void (Class::*fn)(Param1, Param2, Param3);
     104    Class *object;
     105    Arg1 arg1; Arg2 arg2; Arg3 arg3;
     106};
     107
     108template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2, typename Param3, typename Arg3, typename Param4, typename Arg4>
     109class VoidStoredMemberFunctionPointerCall4 : public RunFunctionTask
     110{
     111public:
     112    VoidStoredMemberFunctionPointerCall4(void (Class::*_fn)(Param1, Param2, Param3, Param4) , Class *_object, const Arg1 &_arg1, const Arg2 &_arg2, const Arg3 &_arg3, const Arg4 &_arg4)
     113    : fn(_fn), object(_object), arg1(_arg1), arg2(_arg2), arg3(_arg3), arg4(_arg4){ }
     114
     115    void runFunctor() { (object->*fn)(arg1, arg2, arg3, arg4); }
     116private:
     117    void (Class::*fn)(Param1, Param2, Param3, Param4);
     118    Class *object;
     119    Arg1 arg1; Arg2 arg2; Arg3 arg3; Arg4 arg4;
     120};
     121
     122template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2, typename Param3, typename Arg3, typename Param4, typename Arg4, typename Param5, typename Arg5>
     123class VoidStoredMemberFunctionPointerCall5 : public RunFunctionTask
     124{
     125public:
     126    VoidStoredMemberFunctionPointerCall5(void (Class::*_fn)(Param1, Param2, Param3, Param4, Param5) , Class *_object, const Arg1 &_arg1, const Arg2 &_arg2, const Arg3 &_arg3, const Arg4 &_arg4, const Arg5 &_arg5)
     127    : fn(_fn), object(_object), arg1(_arg1), arg2(_arg2), arg3(_arg3), arg4(_arg4), arg5(_arg5){ }
     128
     129    void runFunctor() { (object->*fn)(arg1, arg2, arg3, arg4, arg5); }
     130private:
     131    void (Class::*fn)(Param1, Param2, Param3, Param4, Param5);
     132    Class *object;
     133    Arg1 arg1; Arg2 arg2; Arg3 arg3; Arg4 arg4; Arg5 arg5;
     134};
     135
     136template <typename Class>
     137void run(const QString &name, Class *object, void (Class::*fn)())
     138{
     139    (new VoidStoredMemberFunctionPointerCall0<Class>(fn, object))->start(name);
     140}
     141
     142template <typename Class, typename Param1, typename Arg1>
     143void run(const QString &name, Class *object, void (Class::*fn)(Param1), const Arg1 &arg1)
     144{
     145    (new VoidStoredMemberFunctionPointerCall1<Class, Param1, Arg1>(fn, object, arg1))->start(name);
     146}
     147
     148template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2>
     149void run(const QString &name, Class *object, void (Class::*fn)(Param1, Param2), const Arg1 &arg1, const Arg2 &arg2)
     150{
     151    (new VoidStoredMemberFunctionPointerCall2<Class, Param1, Arg1, Param2, Arg2>(fn, object, arg1, arg2))->start(name);
     152}
     153
     154template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2, typename Param3, typename Arg3>
     155void run(const QString &name, Class *object, void (Class::*fn)(Param1, Param2, Param3), const Arg1 &arg1, const Arg2 &arg2, const Arg3 &arg3)
     156{
     157    (new VoidStoredMemberFunctionPointerCall3<Class, Param1, Arg1, Param2, Arg2, Param3, Arg3>(fn, object, arg1, arg2, arg3))->start(name);
     158}
     159
     160template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2, typename Param3, typename Arg3, typename Param4, typename Arg4>
     161void run(const QString &name, Class *object, void (Class::*fn)(Param1, Param2, Param3, Param4), const Arg1 &arg1, const Arg2 &arg2, const Arg3 &arg3, const Arg4 &arg4)
     162{
     163    (new VoidStoredMemberFunctionPointerCall4<Class, Param1, Arg1, Param2, Arg2, Param3, Arg3, Param4, Arg4>(fn, object, arg1, arg2, arg3, arg4))->start(name);
     164}
     165
     166template <typename Class, typename Param1, typename Arg1, typename Param2, typename Arg2, typename Param3, typename Arg3, typename Param4, typename Arg4, typename Param5, typename Arg5>
     167void run(const QString &name, Class *object, void (Class::*fn)(Param1, Param2, Param3, Param4, Param5), const Arg1 &arg1, const Arg2 &arg2, const Arg3 &arg3, const Arg4 &arg4, const Arg4 &arg5)
     168{
     169    (new VoidStoredMemberFunctionPointerCall5<Class, Param1, Arg1, Param2, Arg2, Param3, Arg3, Param4, Arg4, Param5, Arg5>(fn, object, arg1, arg2, arg3, arg4, arg5))->start(name);
     170}
     171
     172
     173} //namespace QtConcurrent
     174
     175#endif // MCONCURRENT_H
     176
  • mythtv/libs/libmythtv/mythplayer.cpp

    diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
    index f953e11..c0bbab0 100644
    a b MythPlayer::MythPlayer(PlayerFlags flags) 
    238238    captionsEnabledbyDefault = gCoreContext->GetNumSetting("DefaultCCMode");
    239239    decode_extra_audio = gCoreContext->GetNumSetting("DecodeExtraAudio", 0);
    240240    itvEnabled         = gCoreContext->GetNumSetting("EnableMHEG", 0);
    241     clearSavedPosition = gCoreContext->GetNumSetting("ClearSavedPosition", 1);
    242241    endExitPrompt      = gCoreContext->GetNumSetting("EndOfRecordingExitPrompt");
    243242    pip_default_loc    = (PIPLocation)gCoreContext->GetNumSetting("PIPLocation", kPIPTopLeft);
    244243
    void MythPlayer::InitialSeek(void) 
    29012900    if (bookmarkseek > 30)
    29022901    {
    29032902        DoJumpToFrame(bookmarkseek, kInaccuracyNone);
    2904         if (clearSavedPosition && !player_ctx->IsPIP())
    2905             SetBookmark(true);
    29062903    }
    29072904}
    29082905
    uint64_t MythPlayer::GetBookmark(void) 
    36663663        player_ctx->LockPlayingInfo(__FILE__, __LINE__);
    36673664        if (const ProgramInfo *pi = player_ctx->playingInfo)
    36683665        {
    3669             bookmark = pi->QueryBookmark();
    3670             // Disable progstart if the program has a cutlist.
    3671             if (bookmark == 0 && !pi->HasCutlist())
    3672                 bookmark = pi->QueryProgStart();
    3673             if (bookmark == 0)
    3674                 bookmark = pi->QueryLastPlayPos();
     3666            bookmark = pi->QueryStartMark();
    36753667        }
    36763668        player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    36773669    }
  • mythtv/libs/libmythtv/mythplayer.h

    diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h
    index bfdac87..667c6d4 100644
    a b class MTV_PUBLIC MythPlayer 
    684684
    685685    // Bookmark stuff
    686686    uint64_t bookmarkseek;
    687     int      clearSavedPosition;
    688687    int      endExitPrompt;
    689688
    690689    // Seek
  • mythtv/libs/libmythtv/previewgenerator.cpp

    diff --git a/mythtv/libs/libmythtv/previewgenerator.cpp b/mythtv/libs/libmythtv/previewgenerator.cpp
    index c138f5e..a2ec613 100644
    a b bool PreviewGenerator::SavePreview(const QString &filename, 
    618618bool PreviewGenerator::LocalPreviewRun(void)
    619619{
    620620    m_programInfo.MarkAsInUse(true, kPreviewGeneratorInUseID);
    621     m_programInfo.SetIgnoreProgStart(true);
    622     m_programInfo.SetAllowLastPlayPos(false);
     621    m_programInfo.SetIgnoreProgStart(false);
     622    m_programInfo.SetIgnoreBookmark(false);
     623    m_programInfo.SetAllowLastPlayPos(true);
    623624
    624625    float aspect = 0;
    625626    int   width, height, sz;
    bool PreviewGenerator::LocalPreviewRun(void) 
    632633
    633634    if (captime < 0)
    634635    {
    635         captime = m_programInfo.QueryBookmark();
    636         if (captime > 0)
     636        uint64_t markFrame = m_programInfo.QueryStartMark();
     637        LOG(VB_GENERAL, LOG_INFO,
     638            QString("Preview from start mark (frame %1)").arg(markFrame));
     639        if (markFrame > 0)
    637640        {
    638641            m_timeInSeconds = false;
    639             LOG(VB_GENERAL, LOG_INFO,
    640                 QString("Preview from bookmark (frame %1)").arg(captime));
     642            captime         = markFrame;
    641643        }
    642         else
    643             captime = -1;
    644644    }
    645645
    646646    if (captime <= 0)
  • mythtv/libs/libmythtv/tv_play.cpp

    diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
    index 9b9511b..dbc5d39 100644
    a b using namespace std; 
    3535#include "compat.h"
    3636#include "mythdirs.h"
    3737#include "mythmedia.h"
     38#include "mconcurrent.h"
    3839
    3940// libmyth
    4041#include "programinfo.h"
    TV::TV(void) 
    10131014      db_playback_exit_prompt(0),   db_autoexpire_default(0),
    10141015      db_auto_set_watched(false),   db_end_of_rec_exit_prompt(false),
    10151016      db_jump_prefer_osd(true),     db_use_gui_size_for_tv(false),
    1016       db_clear_saved_position(false),
    10171017      db_toggle_bookmark(false),
    10181018      db_run_jobs_on_remote(false), db_continue_embedded(false),
    10191019      db_use_fixed_size(true),      db_browse_always(false),
    TV::TV(void) 
    10381038      requestDelete(false),  allowRerecord(false),
    10391039      doSmartForward(false),
    10401040      queuedTranscode(false),
     1041      savePosOnExit(false),
    10411042      adjustingPicture(kAdjustingPicture_None),
    10421043      adjustingPictureAttribute(kPictureAttribute_None),
    10431044      askAllowLock(QMutex::Recursive),
    void TV::InitFromDB(void) 
    11211122    kv["EndOfRecordingExitPrompt"] = "0";
    11221123    kv["JumpToProgramOSD"]         = "1";
    11231124    kv["GuiSizeForTV"]             = "0";
    1124     kv["ClearSavedPosition"]       = "1";
    11251125    kv["AltClearSavedPosition"]    = "1";
    11261126    kv["JobsRunOnRecordHost"]      = "0";
    11271127    kv["ContinueEmbeddedTVPlay"]   = "0";
    void TV::InitFromDB(void) 
    11721172    db_end_of_rec_exit_prompt = kv["EndOfRecordingExitPrompt"].toInt();
    11731173    db_jump_prefer_osd     = kv["JumpToProgramOSD"].toInt();
    11741174    db_use_gui_size_for_tv = kv["GuiSizeForTV"].toInt();
    1175     db_clear_saved_position= kv["ClearSavedPosition"].toInt();
    11761175    db_toggle_bookmark     = kv["AltClearSavedPosition"].toInt();
    11771176    db_run_jobs_on_remote  = kv["JobsRunOnRecordHost"].toInt();
    11781177    db_continue_embedded   = kv["ContinueEmbeddedTVPlay"].toInt();
    bool TV::Init(bool createWindow) 
    13581357    speedChangeTimerId   = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
    13591358    saveLastPlayPosTimerId = StartTimer(kSaveLastPlayPosTimeout, __LINE__);
    13601359
     1360    savePosOnExit = false;
     1361
    13611362    LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
    13621363    return true;
    13631364}
    void TV::PrepToSwitchToRecordedProgram(PlayerContext *ctx, 
    33483349    SetExitPlayer(true, true);
    33493350}
    33503351
    3351 void TV::PrepareToExitPlayer(PlayerContext *ctx, int line, BookmarkAction bookmark)
     3352void TV::PrepareToExitPlayer(PlayerContext *ctx, int line)
    33523353{
    3353     bool bm_allowed = IsBookmarkAllowed(ctx);
    33543354    ctx->LockDeletePlayer(__FILE__, line);
    3355     if (ctx->player)
     3355    if (savePosOnExit && ctx->player && ctx->playingInfo)
    33563356    {
    3357         if (bm_allowed)
    3358         {
    3359             // If we're exiting in the middle of the recording, we
    3360             // automatically save a bookmark when "Action on playback
    3361             // exit" is set to "Save position and exit".
    3362             bool allow_set_before_end =
    3363                 (bookmark == kBookmarkAlways ||
    3364                  (bookmark == kBookmarkAuto &&
    3365                   db_playback_exit_prompt == 2));
    3366             // If we're exiting at the end of the recording, we
    3367             // automatically clear the bookmark when "Action on
    3368             // playback exit" is set to "Save position and exit" and
    3369             // "Clear bookmark on playback" is set to true.
    3370             bool allow_clear_at_end =
    3371                 (bookmark == kBookmarkAlways ||
    3372                  (bookmark == kBookmarkAuto &&
    3373                   db_playback_exit_prompt == 2 &&
    3374                   db_clear_saved_position));
    3375             // Whether to set/clear a bookmark depends on whether we're
    3376             // exiting at the end of a recording.
    3377             bool at_end = (ctx->player->IsNearEnd() || getEndOfRecording());
    3378             // Don't consider ourselves at the end if the recording is
    3379             // in-progress.
    3380             at_end &= !StateIsRecording(GetState(ctx));
    3381             bool clear_lastplaypos = false;
    3382             if (at_end && allow_clear_at_end)
    3383             {
    3384                 SetBookmark(ctx, true);
    3385                 // Tidy up the lastplaypos mark only when we clear the
    3386                 // bookmark due to exiting at the end.
    3387                 clear_lastplaypos = true;
    3388             }
    3389             else if (!at_end && allow_set_before_end)
    3390             {
    3391                 SetBookmark(ctx, false);
    3392             }
    3393             if (clear_lastplaypos && ctx->playingInfo)
    3394                 ctx->playingInfo->ClearMarkupMap(MARK_UTIL_LASTPLAYPOS);
    3395         }
     3357        // Clear last play position when we're at the end of a recording.
     3358        // unless the recording is in-progress.
     3359        bool at_end = !StateIsRecording(GetState(ctx)) &&
     3360                (getEndOfRecording() || ctx->player->IsNearEnd());
     3361
     3362        // Clear/Save play position without notification
     3363        // The change must be broadcast when file is no longer in use
     3364        // to update previews, ie. with the MarkNotInUse notification
     3365        uint64_t frame = at_end ? 0 : ctx->player->GetFramesPlayed();
     3366        ctx->playingInfo->SaveLastPlayPos(frame, false);
     3367
    33963368        if (db_auto_set_watched)
    33973369            ctx->player->SetWatched();
    33983370    }
    bool TV::ActivePostQHandleAction(PlayerContext *ctx, const QStringList &actions) 
    50795051    {
    50805052        NormalSpeed(ctx);
    50815053        StopFFRew(ctx);
    5082         SetBookmark(ctx);
     5054        PrepareToExitPlayer(ctx, __LINE__);
    50835055        ShowOSDPromptDeleteRecording(ctx, tr("Are you sure you want to delete:"));
    50845056    }
    50855057    else if (has_action(ACTION_JUMPTODVDROOTMENU, actions) && isdisc)
    void TV::ProcessNetworkControlCommand(PlayerContext *ctx, 
    53015273    }
    53025274    else if (tokens.size() == 2 && tokens[1] == "STOP")
    53035275    {
    5304         SetBookmark(ctx);
    5305         ctx->LockDeletePlayer(__FILE__, __LINE__);
    5306         if (ctx->player && db_auto_set_watched)
    5307             ctx->player->SetWatched();
    5308         ctx->UnlockDeletePlayer(__FILE__, __LINE__);
     5276        PrepareToExitPlayer(ctx, __LINE__);
    53095277        SetExitPlayer(true, true);
    53105278    }
    53115279    else if (tokens.size() >= 3 && tokens[1] == "SEEK" && ctx->HasPlayer())
    void TV::customEvent(QEvent *e) 
    95029470            for (uint i = 0; mctx && (i < player.size()); i++)
    95039471            {
    95049472                PlayerContext *ctx = GetPlayer(mctx, i);
    9505                 PrepareToExitPlayer(ctx, __LINE__, kBookmarkAuto);
     9473                PrepareToExitPlayer(ctx, __LINE__);
    95069474            }
    95079475
    95089476            SetExitPlayer(true, true);
    void TV::ShowOSDStopWatchingRecording(PlayerContext *ctx) 
    1325513223        osd->DialogShow(OSD_DLG_VIDEOEXIT,
    1325613224                        tr("You are exiting %1").arg(videotype));
    1325713225
    13258         if (IsBookmarkAllowed(ctx))
    13259         {
    13260             osd->DialogAddButton(tr("Save this position and go to the menu"),
    13261                                  "DIALOG_VIDEOEXIT_SAVEPOSITIONANDEXIT_0");
    13262             osd->DialogAddButton(tr("Do not save, just exit to the menu"),
    13263                                  ACTION_STOP);
    13264         }
    13265         else
    13266             osd->DialogAddButton(tr("Exit %1").arg(videotype),
    13267                                  ACTION_STOP);
     13226        osd->DialogAddButton(tr("Exit %1").arg(videotype),
     13227                             ACTION_STOP);
    1326813228
    1326913229        if (IsDeleteAllowed(ctx))
    1327013230            osd->DialogAddButton(tr("Delete this recording"),
    bool TV::HandleOSDVideoExit(PlayerContext *ctx, QString action) 
    1340913369
    1341013370    bool hide        = true;
    1341113371    bool delete_ok   = IsDeleteAllowed(ctx);
    13412     bool bookmark_ok = IsBookmarkAllowed(ctx);
    1341313372
    1341413373    ctx->LockDeletePlayer(__FILE__, __LINE__);
    1341513374    bool near_end = ctx->player && ctx->player->IsNearEnd();
    bool TV::HandleOSDVideoExit(PlayerContext *ctx, QString action) 
    1341913378    {
    1342013379        allowRerecord = true;
    1342113380        requestDelete = true;
     13381        PrepareToExitPlayer(ctx, __LINE__);
    1342213382        SetExitPlayer(true, true);
    1342313383    }
    1342413384    else if (action == "JUSTDELETE" && delete_ok)
    1342513385    {
    1342613386        requestDelete = true;
     13387        PrepareToExitPlayer(ctx, __LINE__);
    1342713388        SetExitPlayer(true, true);
    1342813389    }
    1342913390    else if (action == "CONFIRMDELETE")
    bool TV::HandleOSDVideoExit(PlayerContext *ctx, QString action) 
    1343213393        ShowOSDPromptDeleteRecording(ctx, tr("Are you sure you want to delete:"),
    1343313394                                     true);
    1343413395    }
    13435     else if (action == "SAVEPOSITIONANDEXIT" && bookmark_ok)
    13436     {
    13437         PrepareToExitPlayer(ctx, __LINE__, kBookmarkAlways);
    13438         SetExitPlayer(true, true);
    13439     }
    1344013396    else if (action == "KEEPWATCHING" && !near_end)
    1344113397    {
    1344213398        DoTogglePause(ctx, true);
    bool TV::HandleOSDVideoExit(PlayerContext *ctx, QString action) 
    1344713403
    1344813404void TV::HandleSaveLastPlayPosEvent(void)
    1344913405{
    13450     // Helper class to save the latest playback position (in a background thread
    13451     // to avoid playback glitches).  The ctor makes a copy of the ProgramInfo
    13452     // struct to avoid race conditions if playback ends and deletes objects
    13453     // before or while the background thread runs.
    13454     class PositionSaver : public QRunnable
    13455     {
    13456     public:
    13457         PositionSaver(const ProgramInfo &pginfo, uint64_t frame) :
    13458             m_pginfo(pginfo), m_frame(frame) {}
    13459         virtual void run(void)
    13460         {
    13461             LOG(VB_PLAYBACK, LOG_DEBUG,
    13462                 QString("PositionSaver frame=%1").arg(m_frame));
    13463             frm_dir_map_t lastPlayPosMap;
    13464             lastPlayPosMap[m_frame] = MARK_UTIL_LASTPLAYPOS;
    13465             m_pginfo.ClearMarkupMap(MARK_UTIL_LASTPLAYPOS);
    13466             m_pginfo.SaveMarkupMap(lastPlayPosMap, MARK_UTIL_LASTPLAYPOS);
    13467         }
    13468     private:
    13469         const ProgramInfo m_pginfo;
    13470         const uint64_t m_frame;
    13471     };
    13472 
    1347313406    PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
    1347413407    for (uint i = 0; mctx && i < player.size(); ++i)
    1347513408    {
    void TV::HandleSaveLastPlayPosEvent(void) 
    1347813411        bool playing = ctx->player && !ctx->player->IsPaused();
    1347913412        if (playing) // Don't bother saving lastplaypos while paused
    1348013413        {
     13414            // Save the latest playback position in a background thread
     13415            // to avoid playback glitches.
    1348113416            uint64_t framesPlayed = ctx->player->GetFramesPlayed();
    13482             MThreadPool::globalInstance()->
    13483                 start(new PositionSaver(*ctx->playingInfo, framesPlayed),
    13484                       "PositionSaver");
     13417            MConcurrent::run("PositionSaver", ctx->playingInfo,
     13418                             &ProgramInfo::SaveLastPlayPos, framesPlayed, true);
    1348513419        }
    1348613420        ctx->UnlockDeletePlayer(__FILE__, __LINE__);
    1348713421    }
    void TV::HandleSaveLastPlayPosEvent(void) 
    1349013424    QMutexLocker locker(&timerIdLock);
    1349113425    KillTimer(saveLastPlayPosTimerId);
    1349213426    saveLastPlayPosTimerId = StartTimer(kSaveLastPlayPosTimeout, __LINE__);
     13427
     13428    savePosOnExit = true;
    1349313429}
    1349413430
    1349513431void TV::SetLastProgram(const ProgramInfo *rcinfo)
  • mythtv/libs/libmythtv/tv_play.h

    diff --git a/mythtv/libs/libmythtv/tv_play.h b/mythtv/libs/libmythtv/tv_play.h
    index 0678cbb..b026781 100644
    a b class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    366366    bool Init(bool createWindow = true);
    367367    void InitFromDB(void);
    368368    QList<QKeyEvent> ConvertScreenPressKeyMap(const QString& keyList);
    369    
     369
    370370    // Top level playback methods
    371371    bool LiveTV(bool showDialogs, const ChannelInfoList &selection);
    372372    int  Playback(const ProgramInfo &rcinfo);
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    375375    // Private event handling
    376376    bool ProcessKeypressOrGesture(PlayerContext*, QEvent *e);
    377377    bool TranslateKeyPressOrGesture(const QString &context, QEvent *e,
    378                                     QStringList &actions, bool isLiveTV, 
     378                                    QStringList &actions, bool isLiveTV,
    379379                                    bool allowJumps = true);
    380380    bool TranslateGesture(const QString &context, MythGestureEvent *e,
    381381                          QStringList &actions, bool isLiveTV);
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    483483        kBookmarkNever,
    484484        kBookmarkAuto // set iff db_playback_exit_prompt==2
    485485    };
    486     void PrepareToExitPlayer(PlayerContext*, int line,
    487                              BookmarkAction bookmark = kBookmarkAuto);
     486    void PrepareToExitPlayer(PlayerContext*, int line);
    488487    void SetExitPlayer(bool set_it, bool wants_to);
    489488
    490489    bool RequestNextRecorder(PlayerContext *, bool,
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    795794    bool    db_jump_prefer_osd;
    796795    bool    db_use_gui_size_for_tv;
    797796    bool    db_start_in_guide;
    798     bool    db_clear_saved_position;
    799797    bool    db_toggle_bookmark;
    800798    bool    db_run_jobs_on_remote;
    801799    bool    db_continue_embedded;
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    839837    bool allowRerecord;  ///< User wants to rerecord the last video if deleted
    840838    bool doSmartForward;
    841839    bool queuedTranscode;
     840    bool savePosOnExit;  ///< False until first timer event
    842841    /// Picture attribute type to modify.
    843842    PictureAdjustType adjustingPicture;
    844843    /// Picture attribute to modify (on arrow left or right)
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    899898    static const int screenPressRegionCount = 12;
    900899    QList<QKeyEvent>    screenPressKeyMapPlayback;
    901900    QList<QKeyEvent>    screenPressKeyMapLiveTV;
    902    
     901
    903902    // Channel changing timeout notification variables
    904903    QTime   lockTimer;
    905904    bool    lockTimerOn;
  • mythtv/libs/libmythui/mythuibuttonlist.cpp

    diff --git a/mythtv/libs/libmythui/mythuibuttonlist.cpp b/mythtv/libs/libmythui/mythuibuttonlist.cpp
    index 9c25ff4..1e013d0 100644
    a b  
    2121#include "mythuigroup.h"
    2222#include "mythuiimage.h"
    2323#include "mythgesture.h"
     24#include "mythuiprogressbar.h"
    2425
    2526#define LOC     QString("MythUIButtonList(%1): ").arg(objectName())
    2627
    MythUIButtonListItem::MythUIButtonListItem(MythUIButtonList *lbtype, 
    31333134    m_showArrow = showArrow;
    31343135    m_data      = 0;
    31353136    m_isVisible = false;
     3137    m_progress  = 0;
     3138    m_progressStart = 0;
     3139    m_progressTotal = 0;
    31363140
    31373141    if (state >= NotChecked)
    31383142        m_checkable = true;
    MythUIButtonListItem::MythUIButtonListItem(MythUIButtonList *lbtype, 
    31583162    m_state     = CantCheck;
    31593163    m_showArrow = false;
    31603164    m_isVisible = false;
     3165    m_progress  = 0;
     3166    m_progressStart = 0;
     3167    m_progressTotal = 0;
    31613168
    31623169    if (m_parent)
    31633170        m_parent->InsertItem(this, listPosition);
    QString MythUIButtonListItem::GetImageFilename(const QString &name) const 
    34133420    return QString();
    34143421}
    34153422
     3423void MythUIButtonListItem::SetProgress(int start, int total, int used)
     3424{
     3425    m_progress      = used;
     3426    m_progressStart = start;
     3427    m_progressTotal = total;
     3428
     3429    if (m_parent && m_isVisible)
     3430        m_parent->Update();
     3431}
     3432
    34163433void MythUIButtonListItem::DisplayState(const QString &state,
    34173434                                        const QString &name)
    34183435{
    void MythUIButtonListItem::SetToRealButton(MythUIStateType *button, bool selecte 
    37073724    // There is no need to check the return value here, since we already
    37083725    // checked that the state exists with GetState() earlier
    37093726    button->DisplayState(state);
     3727
     3728    MythUIProgressBar *progress = dynamic_cast<MythUIProgressBar *>
     3729                                  (buttonstate->GetChild("buttonprogress"));
     3730    if (progress)
     3731        progress->Set(m_progressStart, m_progressTotal, m_progress);
    37103732}
    37113733
    37123734//---------------------------------------------------------
  • mythtv/libs/libmythui/mythuibuttonlist.h

    diff --git a/mythtv/libs/libmythui/mythuibuttonlist.h b/mythtv/libs/libmythui/mythuibuttonlist.h
    index 3838fc6..2efc533 100644
    a b class MUI_PUBLIC MythUIButtonListItem 
    8181    void SetImageFromMap(const InfoMap &imageMap);
    8282    QString GetImageFilename(const QString &name="") const;
    8383
     84    void SetProgress(int start, int total, int used);
     85   
    8486    void DisplayState(const QString &state, const QString &name);
    8587    void SetStatesFromMap(const InfoMap &stateMap);
    8688
    class MUI_PUBLIC MythUIButtonListItem 
    113115    QVariant        m_data;
    114116    bool            m_showArrow;
    115117    bool            m_isVisible;
     118    int             m_progress;
     119    int             m_progressStart;
     120    int             m_progressTotal;
    116121
    117122    QMap<QString, TextProperties> m_strings;
    118123    QMap<QString, MythImage*> m_images;
  • mythtv/libs/libmythui/mythuiprogressbar.cpp

    diff --git a/mythtv/libs/libmythui/mythuiprogressbar.cpp b/mythtv/libs/libmythui/mythuiprogressbar.cpp
    index 4a7fdf4..2942c47 100644
    a b bool MythUIProgressBar::ParseElement( 
    5858    return true;
    5959}
    6060
     61void MythUIProgressBar::Set(int start, int total, int used)
     62{
     63    if (used != m_current || start != m_start || total != m_total)
     64    {
     65        m_start = start;
     66        m_total = total;
     67        SetUsed(used);
     68    }
     69}
     70
    6171void MythUIProgressBar::SetStart(int value)
    6272{
    6373    m_start = value;
  • mythtv/libs/libmythui/mythuiprogressbar.h

    diff --git a/mythtv/libs/libmythui/mythuiprogressbar.h b/mythtv/libs/libmythui/mythuiprogressbar.h
    index fea6eb5..dfa4ac7 100644
    a b class MUI_PUBLIC MythUIProgressBar : public MythUIType 
    2020    enum LayoutType { LayoutVertical, LayoutHorizontal };
    2121    enum EffectType { EffectReveal, EffectSlide, EffectAnimate };
    2222
     23    void Set(int start, int total, int used);
    2324    void SetStart(int);
    2425    void SetUsed(int);
    2526    void SetTotal(int);
  • mythtv/programs/mythfrontend/globalsettings.cpp

    diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp
    index f07e4fe..4a8a5b3 100644
    a b static HostCheckBox *BrowseAllTuners() 
    16561656    return gc;
    16571657}
    16581658
    1659 static HostCheckBox *ClearSavedPosition()
    1660 {
    1661     HostCheckBox *gc = new HostCheckBox("ClearSavedPosition");
    1662 
    1663     gc->setLabel(PlaybackSettings::tr("Clear bookmark on playback"));
    1664 
    1665     gc->setValue(true);
    1666 
    1667     gc->setHelpText(PlaybackSettings::tr("If enabled, automatically clear the "
    1668                                          "bookmark on a recording when the "
    1669                                          "recording is played back. If "
    1670                                          "disabled, you can mark the "
    1671                                          "beginning with rewind then save "
    1672                                          "position."));
    1673     return gc;
    1674 }
    1675 
    16761659static HostCheckBox *AltClearSavedPosition()
    16771660{
    16781661    HostCheckBox *gc = new HostCheckBox("AltClearSavedPosition");
    static HostComboBox *PlaybackExitPrompt() 
    16971680    gc->setLabel(PlaybackSettings::tr("Action on playback exit"));
    16981681
    16991682    gc->addSelection(PlaybackSettings::tr("Just exit"), "0");
    1700     gc->addSelection(PlaybackSettings::tr("Save position and exit"), "2");
    17011683    gc->addSelection(PlaybackSettings::tr("Always prompt (excluding Live TV)"),
    17021684                     "1");
    17031685    gc->addSelection(PlaybackSettings::tr("Always prompt (including Live TV)"),
    static HostComboBox *PlaybackExitPrompt() 
    17071689    gc->setHelpText(PlaybackSettings::tr("If set to prompt, a menu will be "
    17081690                                         "displayed when you exit playback "
    17091691                                         "mode. The options available will "
    1710                                          "allow you to save your position, "
    1711                                          "delete the recording, or continue "
    1712                                          "watching."));
     1692                                         "allow you to delete the recording, "
     1693                                         "continue watching or exit."));
    17131694    return gc;
    17141695}
    17151696
    PlaybackSettings::PlaybackSettings() 
    39243905
    39253906    VerticalConfigurationGroup *column2 =
    39263907        new VerticalConfigurationGroup(false, false, true, true);
    3927     column2->addChild(ClearSavedPosition());
    39283908    column2->addChild(AltClearSavedPosition());
    39293909    column2->addChild(AutomaticSetWatched());
    39303910    column2->addChild(ContinueEmbeddedTVPlay());
  • mythtv/programs/mythfrontend/main.cpp

    diff --git a/mythtv/programs/mythfrontend/main.cpp b/mythtv/programs/mythfrontend/main.cpp
    index 62ba4b2..a6f6f15 100644
    a b namespace 
    194194        Q_DECLARE_TR_FUNCTIONS(BookmarkDialog)
    195195
    196196      public:
    197         BookmarkDialog(ProgramInfo *pginfo, MythScreenStack *parent) :
     197        BookmarkDialog(ProgramInfo *pginfo, MythScreenStack *parent,
     198                       bool bookmarkPresent, bool lastPlayPresent) :
    198199                MythScreenType(parent, "bookmarkdialog"),
    199                 pgi(pginfo)
     200                pgi(pginfo),
     201                bookmarked(bookmarkPresent),
     202                lastPlayed(lastPlayPresent),
     203                btnPlayBookmark(tr("Play from bookmark")),
     204                btnClearBookmark(tr("Clear bookmark")),
     205                btnPlayBegin(tr("Play from beginning")),
     206                btnPlayLast(tr("Play from last played position")),
     207                btnClearLastPlay(tr("Clear last played position"))
    200208        {
    201209        }
    202210
    203211        bool Create()
    204212        {
    205213            QString msg = tr("DVD/Video contains a bookmark");
    206             QString btn0msg = tr("Play from bookmark");
    207             QString btn1msg = tr("Play from beginning");
    208214
    209215            MythDialogBox *popup = new MythDialogBox(msg, GetScreenStack(), "bookmarkdialog");
    210216            if (!popup->Create())
    namespace 
    216222            GetScreenStack()->AddScreen(popup);
    217223
    218224            popup->SetReturnEvent(this, "bookmarkdialog");
    219             popup->AddButton(btn0msg);
    220             popup->AddButton(btn1msg);
     225            if (bookmarked)
     226            {
     227                popup->AddButton(btnPlayBookmark);
     228                popup->AddButton(btnClearBookmark);
     229            }
     230
     231            popup->AddButton(btnPlayBegin);
     232
     233            if (lastPlayed)
     234            {
     235                popup->AddButton(btnPlayLast);
     236                popup->AddButton(btnClearLastPlay);
     237            }
    221238            return true;
    222239        }
    223240
    namespace 
    226243            if (event->type() == DialogCompletionEvent::kEventType)
    227244            {
    228245                DialogCompletionEvent *dce = (DialogCompletionEvent*)(event);
    229                 int buttonnum = dce->GetResult();
     246                QString buttonText = dce->GetResultText();
    230247
    231248                if (dce->GetId() == "bookmarkdialog")
    232249                {
    233                     uint flags = kStartTVNoFlags;
    234                     if (buttonnum == 1)
     250                    if (buttonText == btnPlayBookmark)
    235251                    {
    236                         flags |= kStartTVIgnoreBookmark;
     252                        TV::StartTV(pgi, kStartTVNoFlags );
    237253                    }
    238                     else if (buttonnum != 0)
     254                    else if (buttonText == btnPlayBegin)
    239255                    {
    240                         delete pgi;
    241                         return;
     256                        TV::StartTV(pgi, kStartTVNoFlags | kStartTVIgnoreBookmark);
     257                    }
     258                    else if (buttonText == btnPlayLast)
     259                    {
     260                        TV::StartTV(pgi, kStartTVNoFlags | kStartTVAllowLastPlayPos);
     261                    }
     262                    else if (buttonText == btnClearBookmark)
     263                    {
     264                        pgi->SaveBookmark(0);
     265                    }
     266                    else if (buttonText == btnClearLastPlay)
     267                    {
     268                        pgi->SaveLastPlayPos(0);
    242269                    }
    243 
    244                     TV::StartTV(pgi, flags);
    245270
    246271                    delete pgi;
    247272                }
    namespace 
    250275
    251276      private:
    252277        ProgramInfo* pgi;
     278        bool bookmarked, lastPlayed;
     279        QString btnPlayBookmark, btnClearBookmark;
     280        QString btnPlayBegin;
     281        QString btnPlayLast, btnClearLastPlay;
    253282    };
    254283
    255284    void cleanup()
    static int internal_play_media(const QString &mrl, const QString &plot, 
    11741203    pginfo->SetProgramInfoType(pginfo->DiscoverProgramInfoType());
    11751204
    11761205    bool bookmarkPresent = false;
     1206    bool lastPlayPresent = false;
    11771207
    11781208    if (pginfo->IsVideoDVD())
    11791209    {
    static int internal_play_media(const QString &mrl, const QString &plot, 
    12241254            return res;
    12251255        }
    12261256    }
    1227     else if (pginfo->IsVideo())
    1228         bookmarkPresent = (pginfo->QueryBookmark() > 0);
     1257    else if (useBookmark && pginfo->IsVideo())
     1258    {
     1259        pginfo->SetAllowLastPlayPos(true);
     1260        pginfo->SetIgnoreBookmark(false);
     1261        bookmarkPresent = pginfo->QueryBookmark() > 0;
     1262        lastPlayPresent = pginfo->QueryLastPlayPos() > 0;
     1263    }
    12291264
    1230     if (useBookmark && bookmarkPresent)
     1265    if (useBookmark && (bookmarkPresent || lastPlayPresent))
    12311266    {
    12321267        MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack();
    1233         BookmarkDialog *bookmarkdialog = new BookmarkDialog(pginfo, mainStack);
     1268        BookmarkDialog *bookmarkdialog = new BookmarkDialog(pginfo, mainStack,
     1269                                                            bookmarkPresent,
     1270                                                            lastPlayPresent);
    12341271        if (!bookmarkdialog->Create())
    12351272        {
    12361273            delete bookmarkdialog;
    static bool WasAutomaticStart(void) 
    16551692}
    16561693
    16571694// from https://www.raspberrypi.org/forums/viewtopic.php?f=33&t=16897
    1658 // The old way of revoking root with setuid(getuid()) 
     1695// The old way of revoking root with setuid(getuid())
    16591696// causes system hang in certain cases on raspberry pi
    16601697
    16611698static int revokeRoot (void)
  • mythtv/programs/mythfrontend/playbackbox.cpp

    diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp
    index 34815cc..fe71685 100644
    a b  
    4040#include "mythdb.h"
    4141#include "mythdate.h"
    4242#include "tv.h"
     43#include "mconcurrent.h"
    4344
    4445#ifdef _MSC_VER
    4546#  include "compat.h"                   // for random
    bool PlaybackBox::Create() 
    562563    connect(m_recordingList, SIGNAL(itemSelected(MythUIButtonListItem*)),
    563564            SLOT(ItemSelected(MythUIButtonListItem*)));
    564565    connect(m_recordingList, SIGNAL(itemClicked(MythUIButtonListItem*)),
    565             SLOT(PlayFromBookmarkOrProgStart(MythUIButtonListItem*)));
     566            SLOT(PlayFromAnyMark(MythUIButtonListItem*)));
    566567    connect(m_recordingList, SIGNAL(itemVisible(MythUIButtonListItem*)),
    567568            SLOT(ItemVisible(MythUIButtonListItem*)));
    568569    connect(m_recordingList, SIGNAL(itemLoaded(MythUIButtonListItem*)),
    void PlaybackBox::ItemVisible(MythUIButtonListItem *item) 
    10111012    // Flagging status (queued, running, no, yes)
    10121013    item->DisplayState(extract_commflag_state(*pginfo), "commflagged");
    10131014
     1015    item->SetProgress(0, 100, pginfo->GetProgressPercent());
     1016
    10141017    MythUIButtonListItem *sel_item = item->parent()->GetItemCurrent();
    10151018    if ((item != sel_item) && item->GetImageFilename("preview").isEmpty() &&
    10161019        (asAvailable == pginfo->GetAvailableStatus()))
    void PlaybackBox::playSelectedPlaylist(bool _random) 
    22222225        this, new MythEvent("PLAY_PLAYLIST"));
    22232226}
    22242227
    2225 void PlaybackBox::PlayFromBookmarkOrProgStart(MythUIButtonListItem *item)
     2228void PlaybackBox::PlayFromAnyMark(MythUIButtonListItem *item)
    22262229{
    22272230    if (!item)
    22282231        item = m_recordingList->GetItemCurrent();
    void PlaybackBox::PlayFromBookmarkOrProgStart(MythUIButtonListItem *item) 
    22342237
    22352238    const bool ignoreBookmark = false;
    22362239    const bool ignoreProgStart = false;
    2237     const bool ignoreLastPlayPos = true;
     2240    const bool ignoreLastPlayPos = false;
    22382241    const bool underNetworkControl = false;
    22392242    if (pginfo)
    22402243        PlayX(*pginfo, ignoreBookmark, ignoreProgStart, ignoreLastPlayPos,
    void PlaybackBox::PlayX(const ProgramInfo &pginfo, 
    23232326    Close();
    23242327}
    23252328
     2329void PlaybackBox::ClearBookmark()
     2330{
     2331    ProgramInfo *pginfo = GetCurrentProgram();
     2332    if (pginfo)
     2333        pginfo->SaveBookmark(0);
     2334}
     2335
     2336void PlaybackBox::ClearLastPlayPos()
     2337{
     2338    ProgramInfo *pginfo = GetCurrentProgram();
     2339    if (pginfo)
     2340        pginfo->SaveLastPlayPos(0);
     2341}
     2342
    23262343void PlaybackBox::StopSelected(void)
    23272344{
    23282345    ProgramInfo *pginfo = GetCurrentProgram();
    void PlaybackBox::selected(MythUIButtonListItem *item) 
    24012418    if (!item)
    24022419        return;
    24032420
    2404     PlayFromBookmarkOrProgStart(item);
     2421    PlayFromAnyMark(item);
    24052422}
    24062423
    24072424void PlaybackBox::popupClosed(QString which, int result)
    bool PlaybackBox::Play( 
    25362553        QCoreApplication::postEvent(
    25372554            this, new MythEvent("PLAY_PLAYLIST"));
    25382555    }
    2539     else
    2540     {
    2541         // User may have saved or deleted a bookmark
    2542         // requiring update of bookmark icon..
    2543         ProgramInfo *pginfo = m_programInfoCache.GetRecordingInfo(tvrec.GetRecordingID());
    2544         if (pginfo)
    2545             UpdateUIListItem(pginfo, true);
    2546     }
    25472556
    25482557    if (m_needUpdate)
    25492558        ScheduleUpdateUIList();
    MythMenu* PlaybackBox::createPlayFromMenu() 
    29933002    MythMenu *menu = new MythMenu(title, this, "slotmenu");
    29943003
    29953004    if (pginfo->IsBookmarkSet())
     3005    {
    29963006        menu->AddItem(tr("Play from bookmark"), SLOT(PlayFromBookmark()));
     3007        menu->AddItem(tr("Clear bookmark"), SLOT(ClearBookmark()));
     3008    }
    29973009    menu->AddItem(tr("Play from beginning"), SLOT(PlayFromBeginning()));
    29983010    if (pginfo->QueryLastPlayPos())
     3011    {
    29993012        menu->AddItem(tr("Play from last played position"),
    30003013                      SLOT(PlayFromLastPlayPos()));
     3014        menu->AddItem(tr("Clear last played position"),
     3015                      SLOT(ClearLastPlayPos()));
     3016    }
    30013017
    30023018    return menu;
    30033019}
    void PlaybackBox::ShowActionPopup(const ProgramInfo &pginfo) 
    32373253            m_popupMenu->AddItem(tr("Play from..."), NULL, createPlayFromMenu());
    32383254        else
    32393255            m_popupMenu->AddItem(tr("Play"),
    3240                                  SLOT(PlayFromBookmarkOrProgStart()));
     3256                                 SLOT(PlayFromAnyMark()));
    32413257    }
    32423258
    32433259    if (!m_player)
    bool PlaybackBox::keyPressEvent(QKeyEvent *event) 
    39663982            if (action == "DELETE")
    39673983                deleteSelected(m_recordingList->GetItemCurrent());
    39683984            else if (action == ACTION_PLAYBACK)
    3969                 PlayFromBookmarkOrProgStart();
     3985                PlayFromAnyMark();
    39703986            else if (action == "DETAILS" || action == "INFO")
    39713987                ShowDetails();
    39723988            else if (action == "CUSTOMEDIT")
    void PlaybackBox::customEvent(QEvent *event) 
    40224038            {
    40234039                ProgramInfo evinfo(me->ExtraDataList());
    40244040                if (evinfo.HasPathname() || evinfo.GetChanID())
    4025                     HandleUpdateProgramInfoEvent(evinfo);
     4041                {
     4042                    uint32_t flags = m_programInfoCache.Update(evinfo);
     4043                    if (flags != PIC_NO_ACTION)
     4044                        HandleUpdateItemEvent(evinfo.GetRecordingID(), flags);
     4045                }
    40264046            }
    40274047            else if (recordingID && (tokens[1] == "ADD"))
    40284048            {
    void PlaybackBox::customEvent(QEvent *event) 
    40734093        else if (message.startsWith("UPDATE_FILE_SIZE"))
    40744094        {
    40754095            QStringList tokens = message.simplified().split(" ");
    4076             bool ok = false;
    4077             uint recordingID = 0;
    4078             uint64_t filesize = 0ULL;
    40794096            if (tokens.size() >= 3)
    40804097            {
    4081                 recordingID = tokens[1].toUInt();
    4082                 filesize   = tokens[2].toLongLong(&ok);
    4083             }
    4084             if (recordingID && ok)
    4085             {
    4086 
    4087                 HandleUpdateProgramInfoFileSizeEvent(recordingID, filesize);
     4098                bool ok = false;
     4099                uint recordingID  = tokens[1].toUInt();
     4100                uint64_t filesize = tokens[2].toLongLong(&ok);
     4101                if (ok)
     4102                {
     4103                    // Delegate to background thread
     4104                    MConcurrent::run("UpdateFileSize", &m_programInfoCache,
     4105                                     &ProgramInfoCache::UpdateFileSize,
     4106                                     recordingID, filesize, PIC_NONE);
     4107                }
    40884108            }
    40894109        }
    40904110        else if (message == "UPDATE_UI_LIST")
    void PlaybackBox::customEvent(QEvent *event) 
    40974117                m_helper.ForceFreeSpaceUpdate();
    40984118            }
    40994119        }
     4120        else if (message.startsWith("UPDATE_UI_ITEM"))
     4121        {
     4122            QStringList tokens = message.simplified().split(" ");
     4123            if (tokens.size() < 3)
     4124                return;
     4125
     4126            uint recordingID  = tokens[1].toUInt();
     4127            UpdateState flags = static_cast<UpdateState>(tokens[2].toUInt());
     4128
     4129            if (flags != PIC_NO_ACTION)
     4130                HandleUpdateItemEvent(recordingID, flags);
     4131        }
    41004132        else if (message == "UPDATE_USAGE_UI")
    41014133        {
    41024134            UpdateUsageUI();
    void PlaybackBox::HandleRecordingAddEvent(const ProgramInfo &evinfo) 
    44154447    ScheduleUpdateUIList();
    44164448}
    44174449
    4418 void PlaybackBox::HandleUpdateProgramInfoEvent(const ProgramInfo &evinfo)
     4450void PlaybackBox::HandleUpdateItemEvent(uint recordingId, uint flags)
    44194451{
    4420     QString old_recgroup = m_programInfoCache.GetRecGroup(
    4421         evinfo.GetRecordingID());
    4422 
    4423     if (!m_programInfoCache.Update(evinfo))
    4424         return;
    4425 
    4426     // If the recording group has changed, reload lists from the recently
    4427     // updated cache; if not, only update UI for the updated item
    4428     if (evinfo.GetRecordingGroup() == old_recgroup)
     4452    // Changing recording group full reload
     4453    if (flags & PIC_RECGROUP_CHANGED)
    44294454    {
    4430         ProgramInfo *dst = FindProgramInUILists(evinfo);
    4431         if (dst)
    4432             UpdateUIListItem(dst, true);
    4433         return;
     4455        ScheduleUpdateUIList();
     4456    }
     4457    else
     4458    {
     4459        ProgramInfo *pginfo = FindProgramInUILists(recordingId);
     4460        if (pginfo)
     4461        {
     4462            bool genPreview = (flags & PIC_MARK_CHANGED);
     4463            UpdateUIListItem(pginfo, genPreview);
     4464        }
    44344465    }
    4435 
    4436     ScheduleUpdateUIList();
    4437 }
    4438 
    4439 void PlaybackBox::HandleUpdateProgramInfoFileSizeEvent(uint recordingID,
    4440                                                        uint64_t filesize)
    4441 {
    4442     m_programInfoCache.UpdateFileSize(recordingID, filesize);
    4443 
    4444     ProgramInfo *dst = FindProgramInUILists(recordingID);
    4445     if (dst)
    4446         UpdateUIListItem(dst, false);
    44474466}
    44484467
    44494468void PlaybackBox::ScheduleUpdateUIList(void)
  • mythtv/programs/mythfrontend/playbackbox.h

    diff --git a/mythtv/programs/mythfrontend/playbackbox.h b/mythtv/programs/mythfrontend/playbackbox.h
    index 6b7e144..280a516 100644
    a b class PlaybackBox : public ScheduleCommon 
    141141    void ItemVisible(MythUIButtonListItem *item);
    142142    void ItemLoaded(MythUIButtonListItem *item);
    143143    void selected(MythUIButtonListItem *item);
    144     void PlayFromBookmarkOrProgStart(MythUIButtonListItem *item = NULL);
     144    void PlayFromAnyMark(MythUIButtonListItem *item = NULL);
    145145    void PlayFromBookmark(MythUIButtonListItem *item = NULL);
    146146    void PlayFromBeginning(MythUIButtonListItem *item = NULL);
    147147    void PlayFromLastPlayPos(MythUIButtonListItem *item = NULL);
    148148    void deleteSelected(MythUIButtonListItem *item);
    149 
     149    void ClearBookmark();
     150    void ClearLastPlayPos();
    150151    void SwitchList(void);
    151152
    152153    void ShowGroupPopup(void);
    class PlaybackBox : public ScheduleCommon 
    321322    void HandlePreviewEvent(const QStringList &list);
    322323    void HandleRecordingRemoveEvent(uint recordingID);
    323324    void HandleRecordingAddEvent(const ProgramInfo &evinfo);
    324     void HandleUpdateProgramInfoEvent(const ProgramInfo &evinfo);
    325     void HandleUpdateProgramInfoFileSizeEvent(uint recordingID, uint64_t filesize);
     325    void HandleUpdateItemEvent(uint recordingId, uint flags);
    326326
    327327    void ScheduleUpdateUIList(void);
    328328    void ShowMenu(void);
  • mythtv/programs/mythfrontend/programinfocache.cpp

    diff --git a/mythtv/programs/mythfrontend/programinfocache.cpp b/mythtv/programs/mythfrontend/programinfocache.cpp
    index 920190f..5a8c59f 100644
    a b  
    1010#include "programinfo.h"
    1111#include "remoteutil.h"
    1212#include "mythevent.h"
     13#include "mythdb.h"
     14#include "mconcurrent.h"
    1315
    1416#include <QCoreApplication>
    1517#include <QRunnable>
    void ProgramInfoCache::ScheduleLoad(const bool updateUI) 
    7375    }
    7476}
    7577
     78void ProgramInfoCache::CalculateProgress(ProgramInfo &pg, int pos)
     79{
     80    uint lastPlayPercent = 0;
     81    if (pos > 0)
     82    {
     83        int total = 0;
     84
     85        switch (pg.GetRecordingStatus())
     86        {
     87        case RecStatus::Recorded:
     88            total = pg.QueryTotalFrames();
     89            break;
     90        case RecStatus::Recording:
     91            // Active recordings won't have total frames set yet.
     92            total = pg.QueryLastFrameInPosMap();
     93            break;
     94        default:
     95            break;
     96        }
     97
     98        lastPlayPercent = (total > pos) ? (100 * pos) / total : 0;
     99
     100        LOG(VB_GUI, LOG_DEBUG, QString("%1 %2  %3/%4 = %5%")
     101            .arg(pg.GetRecordingID()).arg(pg.GetTitle())
     102            .arg(pos).arg(total).arg(lastPlayPercent));
     103    }
     104    pg.SetProgressPercent(lastPlayPercent);
     105}
     106
    76107void ProgramInfoCache::Load(const bool updateUI)
    77108{
    78109    QMutexLocker locker(&m_lock);
    void ProgramInfoCache::Load(const bool updateUI) 
    84115    // we sort the list later anyway.
    85116    vector<ProgramInfo*> *tmp = RemoteGetRecordedList(0);
    86117    /**/
     118
     119    // Calculate play positions for UI
     120    if (tmp)
     121    {
     122        // Played progress
     123        typedef QPair<uint, QDateTime> ProgId;
     124        QHash<ProgId, uint> lastPlayFrames;
     125
     126        // Get all lastplaypos marks in a single lookup
     127        MSqlQuery query(MSqlQuery::InitCon());
     128        query.prepare("SELECT chanid, starttime, mark "
     129                      "FROM recordedmarkup "
     130                      "WHERE type = :TYPE ");
     131        query.bindValue(":TYPE", MARK_UTIL_LASTPLAYPOS);
     132
     133        if (query.exec())
     134        {
     135            while (query.next())
     136            {
     137                ProgId id = qMakePair(query.value(0).toUInt(),
     138                                      MythDate::as_utc(query.value(1).toDateTime()));
     139                lastPlayFrames[id] = query.value(2).toUInt();
     140            }
     141
     142            // Determine progress of each prog
     143            foreach (ProgramInfo* pg, *tmp)
     144            {
     145                // Enable last play pos for all recordings
     146                pg->SetAllowLastPlayPos(true);
     147
     148                ProgId id = qMakePair(pg->GetChanID(),
     149                                      pg->GetRecordingStartTime());
     150                CalculateProgress(*pg, lastPlayFrames.value(id));
     151            }
     152        }
     153        else
     154            MythDB::DBError("Watched progress", query);
     155    }
     156
    87157    locker.relock();
    88158
    89159    free_vec(m_next_cache);
    void ProgramInfoCache::Refresh(void) 
    157227
    158228/** \brief Updates a ProgramInfo in the cache.
    159229 *  \note This must only be called from the UI thread.
    160  *  \return True iff the ProgramInfo was in the cache and was updated.
     230 *  \return Flags indicating the result of the update
    161231 */
    162 bool ProgramInfoCache::Update(const ProgramInfo &pginfo)
     232uint32_t ProgramInfoCache::Update(const ProgramInfo& pginfo)
    163233{
    164234    QMutexLocker locker(&m_lock);
    165235
    166     Cache::iterator it = m_cache.find(pginfo.GetRecordingID());
     236    uint recordingId = pginfo.GetRecordingID();
     237    Cache::iterator it = m_cache.find(recordingId);
    167238
    168     if (it != m_cache.end())
    169         (*it)->clone(pginfo, true);
     239    if (it == m_cache.end())
     240        return PIC_NO_ACTION;
    170241
    171     return it != m_cache.end();
    172 }
     242    ProgramInfo& pg = **it;
     243    uint32_t flags = PIC_NONE;
    173244
    174 /** \brief Updates a ProgramInfo in the cache.
    175  *  \note This must only be called from the UI thread.
    176  *  \return True iff the ProgramInfo was in the cache and was updated.
    177  */
    178 bool ProgramInfoCache::UpdateFileSize(uint recordingID, uint64_t filesize)
    179 {
    180     QMutexLocker locker(&m_lock);
     245    if (pginfo.GetBookmarkUpdate() != pg.GetBookmarkUpdate())
     246        flags |= PIC_MARK_CHANGED;
    181247
    182     Cache::iterator it = m_cache.find(recordingID);
     248    if (pginfo.GetRecordingGroup() != pg.GetRecordingGroup())
     249        flags |= PIC_RECGROUP_CHANGED;
    183250
    184     if (it != m_cache.end())
     251    pg.clone(pginfo, true);
     252    pg.SetAllowLastPlayPos(true);
     253
     254    if (flags & PIC_MARK_CHANGED)
    185255    {
    186         (*it)->SetFilesize(filesize);
    187         if (filesize)
    188             (*it)->SetAvailableStatus(asAvailable, "PIC::UpdateFileSize");
     256        // Delegate this update to a background task
     257        MConcurrent::run("UpdateProg", this, &ProgramInfoCache::UpdateFileSize,
     258                         recordingId, 0, flags);
     259        // Ignore this update
     260        flags = PIC_NO_ACTION;
    189261    }
    190262
    191     return it != m_cache.end();
     263    LOG(VB_GUI, LOG_DEBUG, QString("Pg %1 %2 update state %3")
     264        .arg(recordingId).arg(pg.GetTitle()).arg(flags));
     265    return flags;
    192266}
    193267
    194 /** \brief Returns the ProgramInfo::recgroup or an empty string if not found.
    195  *  \note This must only be called from the UI thread.
     268/** \brief Updates file size calculations of a ProgramInfo in the cache.
     269 *  \note This should only be run by a non-UI thread as it contains multiple
     270 *   Db queries
     271 *  \return True iff the ProgramInfo was in the cache and was updated.
    196272 */
    197 QString ProgramInfoCache::GetRecGroup(uint recordingID) const
     273void ProgramInfoCache::UpdateFileSize(uint recordingId, uint64_t filesize,
     274                                      uint32_t flags)
    198275{
    199276    QMutexLocker locker(&m_lock);
    200277
    201     Cache::const_iterator it = m_cache.find(recordingID);
     278    Cache::iterator it = m_cache.find(recordingId);
     279    if (it == m_cache.end())
     280        return;
    202281
    203     QString recgroup;
    204     if (it != m_cache.end())
    205         recgroup = (*it)->GetRecordingGroup();
     282    ProgramInfo& pg = **it;
     283
     284    CalculateProgress(pg, pg.QueryLastPlayPos());
    206285
    207     return recgroup;
     286    if (filesize > 0)
     287    {
     288        // Filesize update
     289        pg.SetFilesize(filesize);
     290        pg.SetAvailableStatus(asAvailable, "PIC::UpdateFileSize");
     291    }
     292    else // Info update
     293    {
     294        // Don't keep regenerating previews of files being played
     295        QString byWhom;
     296        if (pg.QueryIsInUse(byWhom) && byWhom.contains(QObject::tr("Playing")))
     297            flags &= ~PIC_MARK_CHANGED;
     298    }
     299
     300    QString mesg = QString("UPDATE_UI_ITEM %1 %2").arg(recordingId).arg(flags);
     301    QCoreApplication::postEvent(m_listener, new MythEvent(mesg));
     302
     303    LOG(VB_GUI, LOG_DEBUG, mesg);
    208304}
    209305
    210306/** \brief Adds a ProgramInfo to the cache.
    QString ProgramInfoCache::GetRecGroup(uint recordingID) const 
    212308 */
    213309void ProgramInfoCache::Add(const ProgramInfo &pginfo)
    214310{
    215     if (!pginfo.GetRecordingID() || Update(pginfo))
     311    if (!pginfo.GetRecordingID() || Update(pginfo) != PIC_NO_ACTION)
    216312        return;
    217313
    218     m_cache[pginfo.GetRecordingID()] = new ProgramInfo(pginfo);
     314    QMutexLocker locker(&m_lock);
     315
     316    ProgramInfo* pg = new ProgramInfo(pginfo);
     317    pg->SetAllowLastPlayPos(true);
     318    m_cache[pginfo.GetRecordingID()] = pg;
    219319}
    220320
    221321/** \brief Marks a ProgramInfo in the cache for deletion on the next
  • mythtv/programs/mythfrontend/programinfocache.h

    diff --git a/mythtv/programs/mythfrontend/programinfocache.h b/mythtv/programs/mythfrontend/programinfocache.h
    index 3b95a74..4c8d5a4 100644
    a b class ProgramInfoLoader; 
    2020class ProgramInfo;
    2121class QObject;
    2222
     23typedef enum {
     24    PIC_NONE              = 0x00,
     25    PIC_MARK_CHANGED      = 0x01,
     26    PIC_RECGROUP_CHANGED  = 0x02,
     27    PIC_NO_ACTION         = 0x80,
     28} UpdateState;
     29
    2330class ProgramInfoCache
    2431{
    2532    friend class ProgramInfoLoader;
    class ProgramInfoCache 
    3542    void Refresh(void);
    3643    void Add(const ProgramInfo&);
    3744    bool Remove(uint recordingID);
    38     bool Update(const ProgramInfo&);
    39     bool UpdateFileSize(uint recordingID, uint64_t filesize);
    40     QString GetRecGroup(uint recordingID) const;
     45    uint32_t Update(const ProgramInfo& pginfo);
     46    void UpdateFileSize(uint recordingID, uint64_t filesize, uint32_t flags);
    4147    void GetOrdered(vector<ProgramInfo*> &list, bool newest_first = false);
    4248    /// \note This must only be called from the UI thread.
    4349    bool empty(void) const { return m_cache.empty(); }
    class ProgramInfoCache 
    4652  private:
    4753    void Load(const bool updateUI = true);
    4854    void Clear(void);
     55    void CalculateProgress(ProgramInfo &pg, int playPos);
     56    void LoadProgressMarks();
    4957
    5058  private:
    5159    // NOTE: Hash would be faster for lookups and updates, but we need a sorted
  • mythtv/programs/mythfrontend/viewscheduled.cpp

    diff --git a/mythtv/programs/mythfrontend/viewscheduled.cpp b/mythtv/programs/mythfrontend/viewscheduled.cpp
    index 63f7881..81109fe 100644
    a b void ViewScheduled::LoadList(bool useExistingData) 
    282282    if (!useExistingData)
    283283        LoadFromScheduler(m_recList, m_conflictBool);
    284284
    285     ProgramList::iterator pit = m_recList.begin();
    286     QString currentDate;
    287285    m_recgroupList[m_defaultGroup] = ProgramList(false);
    288286    m_recgroupList[m_defaultGroup].setAutoDelete(false);
     287
     288    ProgramList::iterator pit = m_recList.begin();
    289289    while (pit != m_recList.end())
    290290    {
    291291        ProgramInfo *pginfo = *pit;
     292
     293        CalcRecordedPercent(*pginfo);
     294
    292295        const RecStatus::Type recstatus = pginfo->GetRecordingStatus();
    293296        if ((pginfo->GetRecordingEndTime() >= now ||
    294297             pginfo->GetScheduledEndTime() >= now ||
    void ViewScheduled::ChangeGroup(MythUIButtonListItem* item) 
    392395        FillList();
    393396}
    394397
     398void ViewScheduled::UpdateUIListItem(MythUIButtonListItem* item,
     399                                     const ProgramInfo &pginfo)
     400{
     401    QString state;
     402
     403    const RecStatus::Type recstatus = pginfo.GetRecordingStatus();
     404    if (recstatus == RecStatus::Recording      ||
     405        recstatus == RecStatus::Tuning)
     406        state = "running";
     407    else if (recstatus == RecStatus::Conflict  ||
     408             recstatus == RecStatus::Offline   ||
     409             recstatus == RecStatus::TunerBusy ||
     410             recstatus == RecStatus::Failed    ||
     411             recstatus == RecStatus::Failing   ||
     412             recstatus == RecStatus::Aborted   ||
     413             recstatus == RecStatus::Missed)
     414        state = "error";
     415    else if (recstatus == RecStatus::WillRecord)
     416    {
     417        if (m_curinput == 0 || pginfo.GetInputID() == m_curinput)
     418        {
     419            if (pginfo.GetRecordingPriority2() < 0)
     420                state = "warning";
     421            else
     422                state = "normal";
     423        }
     424    }
     425    else if (recstatus == RecStatus::Repeat ||
     426             recstatus == RecStatus::NeverRecord ||
     427             recstatus == RecStatus::DontRecord ||
     428             (recstatus != RecStatus::DontRecord &&
     429              recstatus <= RecStatus::EarlierShowing))
     430        state = "disabled";
     431    else
     432        state = "warning";
     433
     434    InfoMap infoMap;
     435    pginfo.ToMap(infoMap);
     436   
     437    infoMap["progresspercent"] = ProgressString(pginfo);
     438
     439    item->SetTextFromMap(infoMap, state);
     440
     441    if (!infoMap["progresspercent"].isEmpty())
     442        item->SetProgress(0, 100, pginfo.GetProgressPercent());
     443   
     444    QString rating = QString::number(pginfo.GetStars(10));
     445    item->DisplayState(rating, "ratingstate");
     446    item->DisplayState(state, "status");   
     447}
     448
    395449void ViewScheduled::FillList()
    396450{
    397451    m_schedulesList->Reset();
    void ViewScheduled::FillList() 
    415469    ProgramList::iterator pit = plist.begin();
    416470    while (pit != plist.end())
    417471    {
    418         ProgramInfo *pginfo = *pit;
    419         if (!pginfo)
     472        ProgramInfo* pginfo = *pit;
     473        if (pginfo)
    420474        {
    421             ++pit;
    422             continue;
     475            MythUIButtonListItem *item =
     476                    new MythUIButtonListItem(m_schedulesList,"",
     477                                             qVariantFromValue(pginfo));
     478            UpdateUIListItem(item, *pginfo);
    423479        }
    424 
    425         QString state;
    426 
    427         const RecStatus::Type recstatus = pginfo->GetRecordingStatus();
    428         if (recstatus == RecStatus::Recording      ||
    429             recstatus == RecStatus::Tuning)
    430             state = "running";
    431         else if (recstatus == RecStatus::Conflict  ||
    432                  recstatus == RecStatus::Offline   ||
    433                  recstatus == RecStatus::TunerBusy ||
    434                  recstatus == RecStatus::Failed    ||
    435                  recstatus == RecStatus::Failing   ||
    436                  recstatus == RecStatus::Aborted   ||
    437                  recstatus == RecStatus::Missed)
    438             state = "error";
    439         else if (recstatus == RecStatus::WillRecord)
    440         {
    441             if (m_curinput == 0 || pginfo->GetInputID() == m_curinput)
    442             {
    443                 if (pginfo->GetRecordingPriority2() < 0)
    444                     state = "warning";
    445                 else
    446                     state = "normal";
    447             }
    448         }
    449         else if (recstatus == RecStatus::Repeat ||
    450                  recstatus == RecStatus::NeverRecord ||
    451                  recstatus == RecStatus::DontRecord ||
    452                  (recstatus != RecStatus::DontRecord &&
    453                   recstatus <= RecStatus::EarlierShowing))
    454             state = "disabled";
    455         else
    456             state = "warning";
    457 
    458         MythUIButtonListItem *item =
    459                                 new MythUIButtonListItem(m_schedulesList,"",
    460                                                     qVariantFromValue(pginfo));
    461 
    462         InfoMap infoMap;
    463         pginfo->ToMap(infoMap);
    464         item->SetTextFromMap(infoMap, state);
    465 
    466         QString rating = QString::number(pginfo->GetStars(10));
    467         item->DisplayState(rating, "ratingstate");
    468         item->DisplayState(state, "status");
    469 
    470480        ++pit;
    471481    }
    472482
    void ViewScheduled::FillList() 
    510520    }
    511521}
    512522
     523QString ViewScheduled::ProgressString(const ProgramInfo &pg)
     524{
     525    // Only show progress value for active recordings
     526    return pg.GetRecordingStatus() == RecStatus::Recording
     527            ? QString::number(pg.GetProgressPercent()) : "";
     528}
     529
    513530void ViewScheduled::updateInfo(MythUIButtonListItem *item)
    514531{
    515532    if (!item)
    void ViewScheduled::updateInfo(MythUIButtonListItem *item) 
    520537    {
    521538        InfoMap infoMap;
    522539        pginfo->ToMap(infoMap);
     540
     541        infoMap["progresspercent"] = ProgressString(*pginfo);
     542
    523543        SetTextFromMap(infoMap);
    524544
    525545        MythUIStateType *ratingState = dynamic_cast<MythUIStateType*>
    void ViewScheduled::customEvent(QEvent *event) 
    600620        MythEvent *me = (MythEvent *)event;
    601621        QString message = me->Message();
    602622
    603         if (message != "SCHEDULE_CHANGE")
    604             return;
     623        if (message == "SCHEDULE_CHANGE")
     624        {
     625            m_needFill = true;
    605626
    606         m_needFill = true;
     627            if (m_inEvent)
     628                return;
    607629
    608         if (m_inEvent)
    609             return;
     630            m_inEvent = true;
    610631
    611         m_inEvent = true;
     632            LoadList();
    612633
    613         LoadList();
     634            m_inEvent = false;
     635        }
     636        else if (message.startsWith("UPDATE_FILE_SIZE"))
     637        {
     638            QStringList tokens = message.simplified().split(" ");
     639            if (tokens.size() < 3)
     640                return;
    614641
    615         m_inEvent = false;
     642            bool ok;
     643            uint recordingID  = tokens[1].toUInt();
     644            uint64_t filesize = tokens[2].toLongLong(&ok);
     645
     646            // Locate program
     647            ProgramList::iterator pit = m_recList.begin();
     648            while (pit != m_recList.end())
     649            {
     650                ProgramInfo* pginfo = *pit;
     651                if (pginfo && pginfo->GetRecordingID() == recordingID)
     652                {
     653                    // Update size & progress
     654                    pginfo->SetFilesize(filesize);
     655                    uint current = pginfo->GetProgressPercent();
     656                    CalcRecordedPercent(*pginfo);
     657                    if (pginfo->GetProgressPercent() != current)
     658                    {
     659                        // Update display, if it's shown
     660                        MythUIButtonListItem *item =
     661                                m_schedulesList->
     662                                GetItemByData(qVariantFromValue(pginfo));
     663                        if (item)
     664                        {
     665                            UpdateUIListItem(item, *pginfo);
     666
     667                            // Update selected item if necessary
     668                            MythUIButtonListItem *selected =
     669                                    m_schedulesList->GetItemCurrent();
     670                            if (item == selected)
     671                                updateInfo(selected);
     672                        }
     673                    }
     674                    break;
     675                }
     676                ++pit;
     677            }
     678        }
    616679    }
    617680    else if (event->type() == DialogCompletionEvent::kEventType)
    618681    {
    void ViewScheduled::customEvent(QEvent *event) 
    694757    }
    695758}
    696759
     760void ViewScheduled::CalcRecordedPercent(ProgramInfo &pg)
     761{
     762    QDateTime start = pg.GetRecordingStartTime();
     763    int current = start.secsTo(MythDate::current());
     764    uint recordedPercent = 0;
     765    int duration = 0;
     766    if (current > 0)
     767    {
     768        // Recording stops at end of the final minute
     769        duration        = start.secsTo(pg.GetRecordingEndTime()) + 60;
     770        recordedPercent = duration > current ? current * 100 / duration : 100;
     771    }
     772    pg.SetProgressPercent(recordedPercent);
     773    LOG(VB_GUI, LOG_DEBUG, QString("%1  %2/%3 = %4%")
     774        .arg(pg.GetTitle()).arg(current).arg(duration).arg(recordedPercent));
     775}
     776
    697777ProgramInfo *ViewScheduled::GetCurrentProgram(void) const
    698778{
    699779    MythUIButtonListItem *item = m_schedulesList->GetItemCurrent();
  • mythtv/programs/mythfrontend/viewscheduled.h

    diff --git a/mythtv/programs/mythfrontend/viewscheduled.h b/mythtv/programs/mythfrontend/viewscheduled.h
    index 5d2e6a2..ce2fa6c 100644
    a b class ViewScheduled : public ScheduleCommon 
    5959
    6060    void EmbedTVWindow(void);
    6161
     62    void CalcRecordedPercent(ProgramInfo &pg);
     63    void UpdateUIListItem(MythUIButtonListItem* item,
     64                          const ProgramInfo &pginfo);
     65    QString ProgressString(const ProgramInfo &pg);
     66
    6267    bool m_conflictBool;
    6368    QDate m_conflictDate;
    6469
  • mythtv/themes/MythCenter-wide/recordings-ui.xml

    diff --git a/mythtv/themes/MythCenter-wide/recordings-ui.xml b/mythtv/themes/MythCenter-wide/recordings-ui.xml
    index f5729a6..569e204 100644
    a b  
    180180            <statetype name="buttonitem">
    181181                <state name="active">
    182182                <area>0,0,880,30</area>
     183                <progressbar name="buttonprogress">
     184                    <area>0,0,100%,100%</area>
     185                    <layout>horizontal</layout>
     186                    <style>reveal</style>
     187                    <shape name="progressimage">
     188                        <area>0,1,100%,100%-1</area>
     189                        <type>box</type>
     190                        <fill color="#000000" alpha="128"/>
     191                    </shape>
     192                </progressbar>
    183193                <statetype name="status">
    184194                    <position>3,2</position>
    185195                    <state name="disabled">
     
    212222                    <textarea name="titlesubtitle" from="buttontext">
    213223                        <area>32,2,656,28</area>
    214224                        <align>vcenter</align>
     225                        <template>%titlesubtitle%% (|progresspercent|%)%</template>
    215226                    </textarea>
    216                     <textarea name="shortstartdate" from="titlesubtitle">
     227                    <textarea name="shortstartdate" from="buttontext">
    217228                        <area>634,2,120,28</area>
    218229                        <align>right,vcenter</align>
    219230                    </textarea>
     
    249260                    <shape name="selectbar">
    250261                        <area>0,0,100%,30</area>
    251262                    </shape>
    252                     <textarea name="titlesubtitle" from="buttontext">
    253                         <area>32,2,656,28</area>
     263                    <textarea name="fonts" from="buttontext">
    254264                        <font>basesmall_normal_selected</font>
    255265                        <font state="disabled">basesmall_disabled_selected</font>
    256266                        <font state="error">basesmall_error_selected</font>
     
    259269                        <font state="running">basesmall_running_selected</font>
    260270                        <align>vcenter</align>
    261271                    </textarea>
    262                     <textarea name="shortstartdate" from="titlesubtitle">
     272                    <textarea name="titlesubtitle" from="fonts">
     273                        <area>32,2,656,28</area>
     274                        <template>%titlesubtitle%% (|progresspercent|%)%</template>
     275                    </textarea>
     276                    <textarea name="shortstartdate" from="fonts">
    263277                        <area>634,2,120,28</area>
    264278                        <align>right,vcenter</align>
    265279                    </textarea>
    266280                    <textarea name="starttime" from="shortstartdate">
    267281                        <area>760,2,114,28</area>
    268                         <align>right,vcenter</align>
    269282                    </textarea>
    270283                </state>
    271284            </statetype>
     
    382395            <font>baselarge</font>
    383396            <cutdown>yes</cutdown>
    384397            <align>vcenter</align>
     398            <template>%title%% (|progresspercent|%)%</template>
    385399        </textarea>
    386400
    387401        <textarea name="channel" from="basetextarea">
  • mythtv/themes/MythCenter-wide/schedule-ui.xml

    diff --git a/mythtv/themes/MythCenter-wide/schedule-ui.xml b/mythtv/themes/MythCenter-wide/schedule-ui.xml
    index 9b1ff20..a6c00c6 100644
    a b  
    513513            <statetype name="buttonitem">
    514514                <area>0,0,1200,24</area>
    515515                <state name="active">
     516                    <progressbar name="buttonprogress">
     517                        <area>10,2,1200,24</area>
     518                        <layout>horizontal</layout>
     519                        <style>reveal</style>
     520                        <shape name="progressimage">
     521                            <area>0,0,100%,100%</area>
     522                            <type>box</type>
     523                            <fill color="#000000" alpha="128"/>
     524                        </shape>
     525                    </progressbar>
    516526                    <textarea name="shortstarttimedate" from="buttontext">
    517527                        <area>10,2,250,24</area>
    518528                    </textarea>
     
    521531                    </textarea>
    522532                    <textarea name="title" from="shortstarttimedate">
    523533                        <area>480,2,655,24</area>
     534                        <template>%title%% (|progresspercent|%)%</template>
    524535                    </textarea>
    525536                    <textarea name="card" from="shortstarttimedate">
    526537                        <area>1145,2,40,24</area>
     
    542553                    </textarea>
    543554                    <textarea name="title" from="shortstarttimedate">
    544555                        <area>480,2,655,24</area>
     556                        <template>%title%% (|progresspercent|%)%</template>
    545557                    </textarea>
    546558                    <textarea name="card" from="shortstarttimedate">
    547559                        <area>1145,2,40,24</area>
     
    562574        </buttonlist>
    563575
    564576        <textarea name="title" from="basetextarea">
    565             <area>30,454,1200,50</area>
     577            <area>30,454,1000,50</area>
    566578            <font>baselarge</font>
    567579        </textarea>
    568580
     581        <textarea name="progresspercent" from="basetextarea">
     582            <area>1180,454,70,50</area>
     583            <align>right,vcenter</align>
     584            <template>%progresspercent|%%</template>
     585        </textarea>
     586
    569587        <textarea name="channel" from="basetextarea">
    570588            <area>30,494,360,30</area>
    571589        </textarea>