Ticket #11713: 11713_v1.patch

File 11713_v1.patch, 32.9 KB (added by Jim Stichnoth, 9 years ago)
  • mythtv/libs/libmyth/programinfo.cpp

    commit ef4b1a3076f60571c3f0ebcd504bfbf889809483
    Author: Jim Stichnoth <jstichnoth@mythtv.org>
    Date:   Wed Apr 15 07:45:15 2015 -0700
    
        Add utility marks for preroll amount and latest playback position.
        
        ==== Preroll:
        
        The recorder writes a mark of the new type MARK_UTIL_PREROLL as soon
        as it encounters a keyframe close to the recording's scheduled start
        time.
        
        The frontend starts playback from this keyframe if the user selects
        Play and there is no explicit bookmark (but not if the user selects
        one of the "Play from..." menu items).
        
        ==== Playback position:
        
        During playback, a timer event auto-saves the current playback frame
        with the new MARK_UTIL_PLAYPOS mark type, every 30 seconds. The actual
        saving is done in a background thread to avoid playback glitches.
        Saving is done only during active playback, i.e. not while paused.
        The MARK_UTIL_PLAYPOS mark is tidied up (cleared) if the user saves a
        bookmark while exiting playback.
        
        The "Play from..." menu gets a new item (a new translatable string):
            Play from last played position
        
        This item is added to the menu only when the recording markup actually
        contains a MARK_UTIL_PLAYPOS mark.
        
        The motivation of MARK_UTIL_PLAYPOS is that if you accidentally exit
        playback without setting a bookmark (or the frontend crashes, or the
        OS crashes, or ...), this allows you to resume roughly where you left
        off.  Because of the 30-second timer, if you start playing normally
        and realize you've made a mistake, you have 30 seconds to exit
        playback and instead "Play from last played position".
        
        Note that different frontends can fight over setting this mark, same
        as with a regular bookmark.
        
        Still to do:
        1. Preview generator can do less guessing about preroll.
        2. Provide MythVideo launcher with similar preroll/playpos options.
    
    diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
    index 8764134..f4b5a77 100644
    a b uint64_t ProgramInfo::QueryBookmark(uint chanid, const QDateTime &recstartts) 
    26952695    return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
    26962696}
    26972697
     2698/** \brief Gets any preroll position in database,
     2699 *         unless the ignore preroll flag is set.
     2700 *
     2701 *  \return Preroll position in frames if the query is executed
     2702 *          and succeeds, zero otherwise.
     2703 */
     2704uint64_t ProgramInfo::QueryPreroll(void) const
     2705{
     2706    if (programflags & FL_IGNOREPREROLL)
     2707        return 0;
     2708
     2709    frm_dir_map_t bookmarkmap;
     2710    QueryMarkupMap(bookmarkmap, MARK_UTIL_PREROLL);
     2711
     2712    return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
     2713}
     2714
     2715/** \brief Gets any playpos position in database,
     2716 *         unless the ignore playpos flag is set.
     2717 *
     2718 *  \return PlayPos position in frames if the query is executed
     2719 *          and succeeds, zero otherwise.
     2720 */
     2721uint64_t ProgramInfo::QueryPlayPos(void) const
     2722{
     2723    if (programflags & FL_IGNOREPLAYPOS)
     2724        return 0;
     2725
     2726    frm_dir_map_t bookmarkmap;
     2727    QueryMarkupMap(bookmarkmap, MARK_UTIL_PLAYPOS);
     2728
     2729    return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
     2730}
     2731
    26982732/** \brief Queries "dvdbookmark" table for bookmarking DVD serial
    26992733 *         number. Deletes old dvd bookmarks if "delbookmark" is set.
    27002734 *
  • mythtv/libs/libmyth/programinfo.h

    diff --git a/mythtv/libs/libmyth/programinfo.h b/mythtv/libs/libmyth/programinfo.h
    index ce8159d..b90e609 100644
    a b class MPUBLIC ProgramInfo 
    452452
    453453    uint32_t GetProgramFlags(void)        const { return programflags; }
    454454    ProgramInfoType GetProgramInfoType(void) const
    455         { return (ProgramInfoType)((programflags & FL_TYPEMASK) >> 16); }
     455        { return (ProgramInfoType)((programflags & FL_TYPEMASK) >> 20); }
    456456    bool IsGeneric(void) const;
    457457    bool IsInUsePlaying(void)   const { return programflags & FL_INUSEPLAYING;}
    458458    bool IsCommercialFree(void) const { return programflags & FL_CHANCOMMFREE;}
    class MPUBLIC ProgramInfo 
    489489    // Quick sets
    490490    void SetTitle(const QString &t) { title = t; title.detach(); }
    491491    void SetProgramInfoType(ProgramInfoType t)
    492         { programflags &= ~FL_TYPEMASK; programflags |= ((uint32_t)t<<16); }
     492        { programflags &= ~FL_TYPEMASK; programflags |= ((uint32_t)t<<20); }
    493493    void SetPathname(const QString&) const;
    494494    void SetChanID(uint _chanid) { chanid = _chanid; }
    495495    void SetScheduledStartTime(const QDateTime &dt) { startts      = dt;    }
    class MPUBLIC ProgramInfo 
    532532        programflags &= ~FL_IGNOREBOOKMARK;
    533533        programflags |= (ignore) ? FL_IGNOREBOOKMARK : 0;
    534534    }
     535    /// \brief If "ignore" is true QueryPreroll() will return 0, otherwise
     536    ///        QueryPreroll() will return the preroll position if it exists.
     537    void SetIgnorePreroll(bool ignore)
     538    {
     539        programflags &= ~FL_IGNOREPREROLL;
     540        programflags |= (ignore) ? FL_IGNOREPREROLL : 0;
     541    }
     542    /// \brief If "ignore" is true QueryPlayPos() will return 0, otherwise
     543    ///        QueryPlayPos() will return the playback position if it exists.
     544    void SetIgnorePlayPos(bool ignore)
     545    {
     546        programflags &= ~FL_IGNOREPLAYPOS;
     547        programflags |= (ignore) ? FL_IGNOREPLAYPOS : 0;
     548    }
    535549    virtual void SetRecordingID(uint _recordedid) { recordedid = _recordedid; }
    536550    void SetRecordingStatus(RecStatus::Type status) { recstatus = status; }
    537551    void SetRecordingRuleType(RecordingType type) { rectype   = type;   }
    class MPUBLIC ProgramInfo 
    544558    uint        QueryMplexID(void) const;
    545559    QDateTime   QueryBookmarkTimeStamp(void) const;
    546560    uint64_t    QueryBookmark(void) const;
     561    uint64_t    QueryPreroll(void) const;
     562    uint64_t    QueryPlayPos(void) const;
    547563    CategoryType QueryCategoryType(void) const;
    548564    QStringList QueryDVDBookmark(const QString &serialid) const;
    549565    bool        QueryIsEditing(void) const;
    class MPUBLIC ProgramInfo 
    658674    static QMap<QString,bool> QueryJobsRunning(int type);
    659675    static QStringList LoadFromScheduler(const QString &altTable, int recordid);
    660676
    661   protected:
    662677    // Flagging map support methods
    663678    void QueryMarkupMap(frm_dir_map_t&, MarkTypes type,
    664679                        bool merge = false) const;
    class MPUBLIC ProgramInfo 
    667682    void ClearMarkupMap(MarkTypes type = MARK_ALL,
    668683                        int64_t min_frm = -1, int64_t max_frm = -1) const;
    669684
     685  protected:
    670686    // Creates a basename from the start and end times
    671687    QString CreateRecordBasename(const QString &ext) const;
    672688
  • mythtv/libs/libmyth/programtypes.cpp

    diff --git a/mythtv/libs/libmyth/programtypes.cpp b/mythtv/libs/libmyth/programtypes.cpp
    index 26b5357..475484b 100644
    a b QString toString(MarkTypes type) 
    5353        case MARK_VIDEO_RATE:   return "VIDEO_RATE";
    5454        case MARK_DURATION_MS:  return "DURATION_MS";
    5555        case MARK_TOTAL_FRAMES: return "TOTAL_FRAMES";
     56        case MARK_UTIL_PREROLL: return "UTIL_PREROLL";
     57        case MARK_UTIL_PLAYPOS: return "UTIL_PLAYPOS";
    5658    }
    5759
    5860    return "unknown";
  • mythtv/libs/libmyth/programtypes.h

    diff --git a/mythtv/libs/libmyth/programtypes.h b/mythtv/libs/libmyth/programtypes.h
    index d85ee94..974adb1 100644
    a b typedef enum { 
    7474    MARK_VIDEO_RATE    = 32,
    7575    MARK_DURATION_MS   = 33,
    7676    MARK_TOTAL_FRAMES  = 34,
     77    MARK_UTIL_PREROLL  = 40,
     78    MARK_UTIL_PLAYPOS  = 41,
    7779} MarkTypes;
    7880MPUBLIC QString toString(MarkTypes type);
    7981
    typedef enum FlagMask { 
    145147    FL_DUPLICATE      = 0x00002000,
    146148    FL_REACTIVATE     = 0x00004000,
    147149    FL_IGNOREBOOKMARK = 0x00008000,
     150    FL_IGNOREPREROLL  = 0x00010000,
     151    FL_IGNOREPLAYPOS  = 0x00020000,
    148152    // if you move the type mask please edit {Set,Get}ProgramInfoType()
    149     FL_TYPEMASK       = 0x000F0000,
    150     FL_INUSERECORDING = 0x00100000,
    151     FL_INUSEPLAYING   = 0x00200000,
    152     FL_INUSEOTHER     = 0x00400000,
     153    FL_TYPEMASK       = 0x00F00000,
     154    FL_INUSERECORDING = 0x01000000,
     155    FL_INUSEPLAYING   = 0x02000000,
     156    FL_INUSEOTHER     = 0x04000000,
    153157} ProgramFlag;
    154158
    155159typedef enum ProgramInfoType {
  • mythtv/libs/libmythtv/mythplayer.cpp

    diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
    index b62b86c..2ce4a75 100644
    a b void MythPlayer::EventStart(void) 
    28702870    player_ctx->LockPlayingInfo(__FILE__, __LINE__);
    28712871    {
    28722872        if (player_ctx->playingInfo)
     2873        {
     2874            // When initial playback gets underway, we override the ProgramInfo
     2875            // flags such that future calls to GetBookmark() will consider only
     2876            // an actual bookmark and not preroll or playpos information.
    28732877            player_ctx->playingInfo->SetIgnoreBookmark(false);
     2878            player_ctx->playingInfo->SetIgnorePreroll(true);
     2879            player_ctx->playingInfo->SetIgnorePlayPos(true);
     2880        }
    28742881    }
    28752882    player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    28762883    commBreakMap.LoadMap(player_ctx, framesPlayed);
    uint64_t MythPlayer::GetBookmark(void) 
    36013608    {
    36023609        player_ctx->LockPlayingInfo(__FILE__, __LINE__);
    36033610        if (player_ctx->playingInfo)
     3611        {
    36043612            bookmark = player_ctx->playingInfo->QueryBookmark();
     3613            if (bookmark == 0)
     3614                bookmark = player_ctx->playingInfo->QueryPreroll();
     3615            if (bookmark == 0)
     3616                bookmark = player_ctx->playingInfo->QueryPlayPos();
     3617        }
    36053618        player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    36063619    }
    36073620
  • mythtv/libs/libmythtv/recorders/recorderbase.cpp

    diff --git a/mythtv/libs/libmythtv/recorders/recorderbase.cpp b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
    index 669c872..8654562 100644
    a b RecorderBase::RecorderBase(TVRec *rec) 
    5656      request_pause(false),     paused(false),
    5757      request_recording(false), recording(false),
    5858      nextRingBuffer(NULL),     nextRecording(NULL),
    59       positionMapType(MARK_GOP_BYFRAME)
     59      positionMapType(MARK_GOP_BYFRAME),
     60      estimatedPrerollMS(0), lastSavedKeyframe(0), lastSavedDuration(0)
    6061{
    6162    ClearStatistics();
    6263    QMutexLocker locker(avcodeclock);
    void RecorderBase::SetRecording(const RecordingInfo *pginfo) 
    114115        //       instance which may lead to the possibility that changes made
    115116        //       in the database by one are overwritten by the other
    116117        curRecording = new RecordingInfo(*pginfo);
     118        // Compute an estimate of the actual preroll for setting the
     119        // MARK_UTIL_PREROLL mark.  We can't reliably use
     120        // curRecording->GetRecordingStartTime() because the scheduler rounds it
     121        // to the nearest minute, so we use the current time instead.
     122        estimatedPrerollMS =
     123            MythDate::current().msecsTo(curRecording->GetScheduledStartTime());
    117124        RecordingFile *recFile = curRecording->GetRecordingFile();
    118125        recFile->m_containerFormat = m_containerFormat;
    119126        recFile->Save();
    void RecorderBase::SavePositionMap(bool force, bool finished) 
    579586    bool needToSave = force;
    580587    positionMapLock.lock();
    581588
    582     uint delta_size = positionMapDelta.size();
     589    bool has_delta = !positionMapDelta.empty();
    583590    // set pm_elapsed to a fake large value if the timer hasn't yet started
    584591    uint pm_elapsed = (positionMapTimer.isRunning()) ?
    585592        positionMapTimer.elapsed() : ~0;
    586593    // save on every 1.5 seconds if in the first few frames of a recording
    587594    needToSave |= (positionMap.size() < 30) &&
    588         (delta_size >= 1) && (pm_elapsed >= 1500);
     595        has_delta && (pm_elapsed >= 1500);
    589596    // save every 10 seconds later on
    590     needToSave |= (delta_size >= 1) && (pm_elapsed >= 10000);
     597    needToSave |= has_delta && (pm_elapsed >= 10000);
    591598    // Assume that durationMapDelta is the same size as
    592599    // positionMapDelta and implicitly use the same logic about when
    593600    // to same durationMapDelta.
    void RecorderBase::SavePositionMap(bool force, bool finished) 
    595602    if (curRecording && needToSave)
    596603    {
    597604        positionMapTimer.start();
    598         if (delta_size)
     605        if (has_delta)
    599606        {
    600607            // copy the delta map because most times we are called it will be in
    601608            // another thread and we don't want to lock the main recorder thread
    void RecorderBase::SavePositionMap(bool force, bool finished) 
    609616            curRecording->SavePositionMapDelta(deltaCopy, positionMapType);
    610617            curRecording->SavePositionMapDelta(durationDeltaCopy,
    611618                                               MARK_DURATION_MS);
     619
     620            TryWritePrerollMark(durationDeltaCopy);
    612621        }
    613622        else
    614623        {
    void RecorderBase::SavePositionMap(bool force, bool finished) 
    626635    }
    627636}
    628637
     638void RecorderBase::TryWritePrerollMark(const frm_pos_map_t &durationDeltaCopy)
     639{
     640    // Note: all log strings contain "preroll bookmark" for searching.
     641    if (estimatedPrerollMS <= 0)
     642    {
     643        // Do nothing because no preroll bookmark is needed.
     644        LOG(VB_RECORD, LOG_DEBUG,
     645            QString("No preroll bookmark needed because delta=%1")
     646            .arg(estimatedPrerollMS));
     647        return;
     648    }
     649    frm_pos_map_t::const_iterator last_it = durationDeltaCopy.end();
     650    --last_it;
     651    long long bookmarkFrame = 0;
     652    LOG(VB_RECORD, LOG_DEBUG,
     653        QString("durationDeltaCopy.begin() = (%1,%2)")
     654        .arg(durationDeltaCopy.begin().key())
     655        .arg(durationDeltaCopy.begin().value()));
     656    if (estimatedPrerollMS > *last_it)
     657    {
     658        // Do nothing because we haven't reached recstartts yet.
     659        LOG(VB_RECORD, LOG_DEBUG,
     660            QString("No preroll bookmark yet because estimatedPrerollMS=%1 "
     661                    "and *last_it=%2")
     662            .arg(estimatedPrerollMS).arg(*last_it));
     663    }
     664    else if (lastSavedDuration <= estimatedPrerollMS &&
     665             estimatedPrerollMS < *durationDeltaCopy.begin())
     666    {
     667        // Set preroll bookmark @ lastSavedKeyframe
     668        LOG(VB_RECORD, LOG_DEBUG,
     669            QString("Set preroll bookmark=%1 because %2<=%3<%4")
     670            .arg(lastSavedKeyframe).arg(lastSavedDuration)
     671            .arg(estimatedPrerollMS).arg(*durationDeltaCopy.begin()));
     672        bookmarkFrame = lastSavedKeyframe;
     673    }
     674    else if (*durationDeltaCopy.begin() <= estimatedPrerollMS &&
     675             estimatedPrerollMS < *last_it)
     676    {
     677        frm_pos_map_t::const_iterator upper_it = durationDeltaCopy.begin();
     678        for (; upper_it != durationDeltaCopy.end(); ++upper_it)
     679        {
     680            if (*upper_it > estimatedPrerollMS)
     681            {
     682                --upper_it;
     683                // Set preroll bookmark @ upper_it.key()
     684                LOG(VB_RECORD, LOG_DEBUG,
     685                    QString("Set preroll bookmark=%1 because "
     686                            "estimatedPrerollMS=%2 and upper_it.value()=%3")
     687                    .arg(upper_it.key()).arg(estimatedPrerollMS)
     688                    .arg(upper_it.value()));
     689                bookmarkFrame = upper_it.key();
     690                break;
     691            }
     692        }
     693    }
     694    else
     695    {
     696        // do nothing
     697        LOG(VB_RECORD, LOG_DEBUG, "No preroll bookmark due to fallthrough");
     698    }
     699    if (bookmarkFrame)
     700    {
     701        frm_dir_map_t prerollMap;
     702        prerollMap[bookmarkFrame] = MARK_UTIL_PREROLL;
     703        curRecording->SaveMarkupMap(prerollMap, MARK_UTIL_PREROLL);
     704    }
     705    lastSavedKeyframe = last_it.key();
     706    lastSavedDuration = last_it.value();
     707    LOG(VB_RECORD, LOG_DEBUG,
     708        QString("Setting lastSavedKeyframe=%1 lastSavedDuration=%2 "
     709                "for preroll bookmark calculations")
     710        .arg(lastSavedKeyframe).arg(lastSavedDuration));
     711}
     712
    629713void RecorderBase::AspectChange(uint aspect, long long frame)
    630714{
    631715    MarkTypes mark = MARK_ASPECT_4_3;
  • mythtv/libs/libmythtv/recorders/recorderbase.h

    diff --git a/mythtv/libs/libmythtv/recorders/recorderbase.h b/mythtv/libs/libmythtv/recorders/recorderbase.h
    index 8d00836..88e88a7 100644
    a b class MTV_PUBLIC RecorderBase : public QRunnable 
    292292     */
    293293    void SetTotalFrames(uint64_t total_frames);
    294294
     295    void TryWritePrerollMark(const frm_pos_map_t &durationDeltaCopy);
     296
    295297    TVRec         *tvrec;
    296298    RingBuffer    *ringBuffer;
    297299    bool           weMadeBuffer;
    class MTV_PUBLIC RecorderBase : public QRunnable 
    341343    frm_pos_map_t  durationMapDelta;
    342344    MythTimer      positionMapTimer;
    343345
     346    // Preroll bookmark support
     347    qint64         estimatedPrerollMS;
     348    long long      lastSavedKeyframe;
     349    long long      lastSavedDuration;
     350
    344351    // Statistics
    345352    // Note: Once we enter RecorderBase::run(), only that thread can
    346353    // update these values safely. These values are read in that thread
  • mythtv/libs/libmythtv/tv_play.cpp

    diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
    index ed338fa..55f9afb 100644
    a b const uint TV::kEndOfPlaybackFirstCheckTimer = 60000; 
    138138#else
    139139const uint TV::kEndOfPlaybackFirstCheckTimer = 5000;
    140140#endif
     141const uint TV::kSavePlayPosTimeout           = 30000;
    141142
    142143/**
    143144 * \brief stores last program info. maintains info so long as
    bool TV::StartTV(ProgramInfo *tvrec, uint flags, 
    335336    {
    336337        curProgram = new ProgramInfo(*tvrec);
    337338        curProgram->SetIgnoreBookmark(flags & kStartTVIgnoreBookmark);
     339        curProgram->SetIgnorePreroll(flags & kStartTVIgnorePreroll);
     340        curProgram->SetIgnorePlayPos(flags & kStartTVIgnorePlayPos);
    338341    }
    339342
    340343    PlaySettings settings(curProgram ? curProgram->GetPlaybackGroup() : "Default");
    TV::TV(PlaySettings *settings) 
    10701073      endOfPlaybackTimerId(0),      embedCheckTimerId(0),
    10711074      endOfRecPromptTimerId(0),     videoExitDialogTimerId(0),
    10721075      pseudoChangeChanTimerId(0),   speedChangeTimerId(0),
    1073       errorRecoveryTimerId(0),      exitPlayerTimerId(0)
     1076      errorRecoveryTimerId(0),      exitPlayerTimerId(0),
     1077      savePlayPosTimerId(0)
    10741078{
    10751079    LOG(VB_GENERAL, LOG_INFO, LOC + "Creating TV object");
    10761080    ctorTime.start();
    bool TV::Init(bool createWindow) 
    13331337    errorRecoveryTimerId = StartTimer(kErrorRecoveryCheckFrequency, __LINE__);
    13341338    lcdTimerId           = StartTimer(1, __LINE__);
    13351339    speedChangeTimerId   = StartTimer(kSpeedChangeCheckFrequency, __LINE__);
     1340    savePlayPosTimerId   = StartTimer(kSavePlayPosTimeout, __LINE__);
    13361341
    13371342    LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "-- end");
    13381343    return true;
    void TV::timerEvent(QTimerEvent *te) 
    27862791        HandleSpeedChangeTimerEvent();
    27872792    else if (timer_id == pipChangeTimerId)
    27882793        HandlePxPTimerEvent();
     2794    else if (timer_id == savePlayPosTimerId)
     2795        HandleSavePlayPosEvent();
    27892796    else
    27902797        handled = false;
    27912798
    void TV::PrepareToExitPlayer(PlayerContext *ctx, int line, BookmarkAction bookma 
    33303337            // Don't consider ourselves at the end if the recording is
    33313338            // in-progress.
    33323339            at_end &= !StateIsRecording(GetState(ctx));
     3340            bool clear_playpos = true;
    33333341            if (at_end && allow_clear_at_end)
    33343342                SetBookmark(ctx, true);
    3335             if (!at_end && allow_set_before_end)
     3343            else if (!at_end && allow_set_before_end)
    33363344                SetBookmark(ctx, false);
     3345            else
     3346                clear_playpos = false;
     3347            // If we are setting a bookmark upon exit (or equivalently clearing
     3348            // it due to exiting at the end), we clean up the unnecessary
     3349            // playpos mark.
     3350            if (clear_playpos && ctx->playingInfo)
     3351                ctx->playingInfo->ClearMarkupMap(MARK_UTIL_PLAYPOS);
    33373352        }
    33383353        if (db_auto_set_watched)
    33393354            ctx->player->SetWatched();
    bool TV::HandleOSDVideoExit(PlayerContext *ctx, QString action) 
    1339213407    return hide;
    1339313408}
    1339413409
     13410void TV::HandleSavePlayPosEvent(void)
     13411{
     13412    // Helper class to save the latest playback position (in a background thread
     13413    // to avoid playback glitches).  The ctor makes a copy of the ProgramInfo
     13414    // struct to avoid race conditions if playback ends and deletes objects
     13415    // before or while the background thread runs.
     13416    class PositionSaver : public QRunnable
     13417    {
     13418    public:
     13419        PositionSaver(const ProgramInfo &pginfo, uint64_t frame) :
     13420            m_pginfo(pginfo), m_frame(frame) {}
     13421        virtual void run(void)
     13422        {
     13423            LOG(VB_PLAYBACK, LOG_DEBUG,
     13424                QString("PositionSaver frame=%1").arg(m_frame));
     13425            frm_dir_map_t playPosMap;
     13426            playPosMap[m_frame] = MARK_UTIL_PLAYPOS;
     13427            m_pginfo.ClearMarkupMap(MARK_UTIL_PLAYPOS);
     13428            m_pginfo.SaveMarkupMap(playPosMap, MARK_UTIL_PLAYPOS);
     13429        }
     13430    private:
     13431        const ProgramInfo m_pginfo;
     13432        const uint64_t m_frame;
     13433    };
     13434
     13435    PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
     13436    for (uint i = 0; mctx && i < player.size(); ++i)
     13437    {
     13438        PlayerContext *ctx = GetPlayer(mctx, i);
     13439        ctx->LockDeletePlayer(__FILE__, __LINE__);
     13440        bool playing = ctx->player && !ctx->player->IsPaused();
     13441        if (playing) // Don't bother saving playpos while paused
     13442        {
     13443            uint64_t framesPlayed = ctx->player->GetFramesPlayed();
     13444            MThreadPool::globalInstance()->
     13445                start(new PositionSaver(*ctx->playingInfo, framesPlayed),
     13446                      "PositionSaver");
     13447        }
     13448        ReturnPlayerLock(ctx);
     13449    }
     13450    ReturnPlayerLock(mctx);
     13451
     13452    QMutexLocker locker(&timerIdLock);
     13453    KillTimer(savePlayPosTimerId);
     13454    savePlayPosTimerId = StartTimer(kSavePlayPosTimeout, __LINE__);
     13455}
     13456
    1339513457void TV::SetLastProgram(const ProgramInfo *rcinfo)
    1339613458{
    1339713459    QMutexLocker locker(&lastProgramLock);
  • mythtv/libs/libmythtv/tv_play.h

    diff --git a/mythtv/libs/libmythtv/tv_play.h b/mythtv/libs/libmythtv/tv_play.h
    index c55bb3a..39c31c9 100644
    a b enum { 
    116116    kStartTVInPlayList       = 0x02,
    117117    kStartTVByNetworkCommand = 0x04,
    118118    kStartTVIgnoreBookmark   = 0x08,
     119    kStartTVIgnorePreroll    = 0x10,
     120    kStartTVIgnorePlayPos    = 0x20,
    119121};
    120122
    121123class AskProgramInfo
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    410412    bool HandlePxPTimerEvent(void);
    411413    bool HandleLCDTimerEvent(void);
    412414    void HandleLCDVolumeTimerEvent(void);
     415    void HandleSavePlayPosEvent();
    413416
    414417    // Commands used by frontend UI screens (PlaybackBox, GuideGrid etc)
    415418    void EditSchedule(const PlayerContext*,
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    980983    volatile int         speedChangeTimerId;
    981984    volatile int         errorRecoveryTimerId;
    982985    mutable volatile int exitPlayerTimerId;
     986    volatile int         savePlayPosTimerId;
    983987    TimerContextMap      stateChangeTimerId;
    984988    TimerContextMap      signalMonitorTimerId;
    985989
    class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer 
    10961100    static const uint kErrorRecoveryCheckFrequency;
    10971101    static const uint kEndOfRecPromptCheckFrequency;
    10981102    static const uint kEndOfPlaybackFirstCheckTimer;
     1103    static const uint kSavePlayPosTimeout;
    10991104};
    11001105
    11011106#endif
  • mythtv/programs/mythfrontend/playbackbox.cpp

    diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp
    index 1050b4a..116b1bf 100644
    a b bool PlaybackBox::Create() 
    556556    connect(m_recordingList, SIGNAL(itemSelected(MythUIButtonListItem*)),
    557557            SLOT(ItemSelected(MythUIButtonListItem*)));
    558558    connect(m_recordingList, SIGNAL(itemClicked(MythUIButtonListItem*)),
    559             SLOT(PlayFromBookmark(MythUIButtonListItem*)));
     559            SLOT(PlayFromBookmarkOrPreroll(MythUIButtonListItem*)));
    560560    connect(m_recordingList, SIGNAL(itemVisible(MythUIButtonListItem*)),
    561561            SLOT(ItemVisible(MythUIButtonListItem*)));
    562562    connect(m_recordingList, SIGNAL(itemLoaded(MythUIButtonListItem*)),
    void PlaybackBox::playSelectedPlaylist(bool _random) 
    21972197        this, new MythEvent("PLAY_PLAYLIST"));
    21982198}
    21992199
     2200void PlaybackBox::PlayFromBookmarkOrPreroll(MythUIButtonListItem *item)
     2201{
     2202    if (!item)
     2203        item = m_recordingList->GetItemCurrent();
     2204
     2205    if (!item)
     2206        return;
     2207
     2208    ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
     2209
     2210    const bool ignoreBookmark = false;
     2211    const bool ignorePreroll = false;
     2212    const bool ignorePlayPos = true;
     2213    const bool underNetworkControl = false;
     2214    if (pginfo)
     2215        PlayX(*pginfo, ignoreBookmark, ignorePreroll, ignorePlayPos,
     2216              underNetworkControl);
     2217}
     2218
    22002219void PlaybackBox::PlayFromBookmark(MythUIButtonListItem *item)
    22012220{
    22022221    if (!item)
    void PlaybackBox::PlayFromBookmark(MythUIButtonListItem *item) 
    22072226
    22082227    ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
    22092228
     2229    const bool ignoreBookmark = false;
     2230    const bool ignorePreroll = true;
     2231    const bool ignorePlayPos = true;
     2232    const bool underNetworkControl = false;
    22102233    if (pginfo)
    2211         PlayX(*pginfo, false, false);
     2234        PlayX(*pginfo, ignoreBookmark, ignorePreroll, ignorePlayPos,
     2235              underNetworkControl);
    22122236}
    22132237
    22142238void PlaybackBox::PlayFromBeginning(MythUIButtonListItem *item)
    void PlaybackBox::PlayFromBeginning(MythUIButtonListItem *item) 
    22212245
    22222246    ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
    22232247
     2248    const bool ignoreBookmark = true;
     2249    const bool ignorePreroll = true;
     2250    const bool ignorePlayPos = true;
     2251    const bool underNetworkControl = false;
     2252    if (pginfo)
     2253        PlayX(*pginfo, ignoreBookmark, ignorePreroll, ignorePlayPos,
     2254              underNetworkControl);
     2255}
     2256
     2257void PlaybackBox::PlayFromPlayPos(MythUIButtonListItem *item)
     2258{
     2259    if (!item)
     2260        item = m_recordingList->GetItemCurrent();
     2261
     2262    if (!item)
     2263        return;
     2264
     2265    ProgramInfo *pginfo = item->GetData().value<ProgramInfo *>();
     2266
     2267    const bool ignoreBookmark = true;
     2268    const bool ignorePreroll = true;
     2269    const bool ignorePlayPos = false;
     2270    const bool underNetworkControl = false;
    22242271    if (pginfo)
    2225         PlayX(*pginfo, true, false);
     2272        PlayX(*pginfo, ignoreBookmark, ignorePreroll, ignorePlayPos,
     2273              underNetworkControl);
    22262274}
    22272275
    22282276void PlaybackBox::PlayX(const ProgramInfo &pginfo,
    22292277                        bool ignoreBookmark,
     2278                        bool ignorePreroll,
     2279                        bool ignorePlayPos,
    22302280                        bool underNetworkControl)
    22312281{
    22322282    if (!m_player)
    22332283    {
    2234         Play(pginfo, false, ignoreBookmark, underNetworkControl);
     2284        Play(pginfo, false, ignoreBookmark, ignorePreroll, ignorePlayPos,
     2285             underNetworkControl);
    22352286        return;
    22362287    }
    22372288
    void PlaybackBox::PlayX(const ProgramInfo &pginfo, 
    22422293            ignoreBookmark ? "1" : "0");
    22432294        m_player_selected_new_show.push_back(
    22442295            underNetworkControl ? "1" : "0");
     2296        // XXX add anything for ignorePreroll and ignorePlayPos?
    22452297    }
    22462298    Close();
    22472299}
    void PlaybackBox::selected(MythUIButtonListItem *item) 
    23242376    if (!item)
    23252377        return;
    23262378
    2327     PlayFromBookmark(item);
     2379    PlayFromBookmarkOrPreroll(item);
    23282380}
    23292381
    23302382void PlaybackBox::popupClosed(QString which, int result)
    void PlaybackBox::ShowGroupPopup() 
    24122464
    24132465bool PlaybackBox::Play(
    24142466    const ProgramInfo &rec,
    2415     bool inPlaylist, bool ignoreBookmark, bool underNetworkControl)
     2467    bool inPlaylist, bool ignoreBookmark, bool ignorePreroll,
     2468    bool ignorePlayPos, bool underNetworkControl)
    24162469{
    24172470    bool playCompleted = false;
    24182471
    bool PlaybackBox::Play( 
    24442497    uint flags =
    24452498        (inPlaylist          ? kStartTVInPlayList       : kStartTVNoFlags) |
    24462499        (underNetworkControl ? kStartTVByNetworkCommand : kStartTVNoFlags) |
     2500        (ignorePlayPos       ? kStartTVIgnorePlayPos    : kStartTVNoFlags) |
     2501        (ignorePreroll       ? kStartTVIgnorePreroll    : kStartTVNoFlags) |
    24472502        (ignoreBookmark      ? kStartTVIgnoreBookmark   : kStartTVNoFlags);
    24482503
    24492504    playCompleted = TV::StartTV(&tvrec, flags);
    MythMenu* PlaybackBox::createPlayFromMenu() 
    29122967
    29132968    MythMenu *menu = new MythMenu(title, this, "slotmenu");
    29142969
    2915     menu->AddItem(tr("Play from bookmark"),  SLOT(PlayFromBookmark()));
     2970    if (pginfo->IsBookmarkSet())
     2971        menu->AddItem(tr("Play from bookmark"), SLOT(PlayFromBookmark()));
    29162972    menu->AddItem(tr("Play from beginning"), SLOT(PlayFromBeginning()));
     2973    if (pginfo->QueryPlayPos())
     2974        menu->AddItem(tr("Play from last played position"),
     2975                      SLOT(PlayFromPlayPos()));
    29172976
    29182977    return menu;
    29192978}
    void PlaybackBox::ShowActionPopup(const ProgramInfo &pginfo) 
    31493208
    31503209    if (!sameProgram)
    31513210    {
    3152         if (pginfo.IsBookmarkSet())
     3211        if (pginfo.IsBookmarkSet() || pginfo.QueryPlayPos())
    31533212            m_popupMenu->AddItem(tr("Play from..."), NULL, createPlayFromMenu());
    31543213        else
    3155             m_popupMenu->AddItem(tr("Play"), SLOT(PlayFromBookmark()));
     3214            m_popupMenu->AddItem(tr("Play"), SLOT(PlayFromBookmarkOrPreroll()));
    31563215    }
    31573216
    31583217    if (!m_player)
    void PlaybackBox::processNetworkControlCommand(const QString &command) 
    37433802
    37443803                pginfo.SetPathname(pginfo.GetPlaybackURL());
    37453804
    3746                 bool ignoreBookmark = (tokens[1] == "PLAY");
    3747                 PlayX(pginfo, ignoreBookmark, true);
     3805                const bool ignoreBookmark = (tokens[1] == "PLAY");
     3806                const bool ignorePreroll = true;
     3807                const bool ignorePlayPos = true;
     3808                const bool underNetworkControl = true;
     3809                PlayX(pginfo, ignoreBookmark, ignorePreroll, ignorePlayPos,
     3810                      underNetworkControl);
    37483811            }
    37493812            else
    37503813            {
    bool PlaybackBox::keyPressEvent(QKeyEvent *event) 
    38773940            if (action == "DELETE")
    38783941                deleteSelected(m_recordingList->GetItemCurrent());
    38793942            else if (action == ACTION_PLAYBACK)
    3880                 PlayFromBookmark();
     3943                PlayFromBookmarkOrPreroll();
    38813944            else if (action == "DETAILS" || action == "INFO")
    38823945                ShowDetails();
    38833946            else if (action == "CUSTOMEDIT")
    void PlaybackBox::customEvent(QEvent *event) 
    41544217                else if (pginfo)
    41554218                {
    41564219                    playnext = false;
    4157                     Play(*pginfo, kCheckForPlaylistAction == cat, false, false);
     4220                    const bool ignoreBookmark = false;
     4221                    const bool ignorePreroll = false;
     4222                    const bool ignorePlayPos = true;
     4223                    const bool underNetworkControl = false;
     4224                    Play(*pginfo, kCheckForPlaylistAction == cat,
     4225                         ignoreBookmark, ignorePreroll, ignorePlayPos,
     4226                         underNetworkControl);
    41584227                }
    41594228            }
    41604229
    void PlaybackBox::customEvent(QEvent *event) 
    41834252            }
    41844253
    41854254            ProgramInfo *pginfo = FindProgramInUILists(recordingID);
     4255            const bool ignoreBookmark = false;
     4256            const bool ignorePreroll = true;
     4257            const bool ignorePlayPos = true;
     4258            const bool underNetworkControl = false;
    41864259            if (pginfo)
    4187                 Play(*pginfo, true, false, false);
     4260                Play(*pginfo, true, ignoreBookmark, ignorePreroll,
     4261                     ignorePlayPos, underNetworkControl);
    41884262        }
    41894263        else if ((message == "SET_PLAYBACK_URL") && (me->ExtraDataCount() == 2))
    41904264        {
  • mythtv/programs/mythfrontend/playbackbox.h

    diff --git a/mythtv/programs/mythfrontend/playbackbox.h b/mythtv/programs/mythfrontend/playbackbox.h
    index 18ae480..4415113 100644
    a b class PlaybackBox : public ScheduleCommon 
    140140    void ItemVisible(MythUIButtonListItem *item);
    141141    void ItemLoaded(MythUIButtonListItem *item);
    142142    void selected(MythUIButtonListItem *item);
     143    void PlayFromBookmarkOrPreroll(MythUIButtonListItem *item = NULL);
    143144    void PlayFromBookmark(MythUIButtonListItem *item = NULL);
    144145    void PlayFromBeginning(MythUIButtonListItem *item = NULL);
     146    void PlayFromPlayPos(MythUIButtonListItem *item = NULL);
    145147    void deleteSelected(MythUIButtonListItem *item);
    146148
    147149    void SwitchList(void);
    class PlaybackBox : public ScheduleCommon 
    270272
    271273    void PlayX(const ProgramInfo &rec,
    272274               bool ignoreBookmark,
     275               bool ignorePreroll,
     276               bool ignorePlayPos,
    273277               bool underNetworkControl);
    274278
    275279    bool Play(const ProgramInfo &rec,
    276280              bool inPlaylist,
    277281              bool ignoreBookmark,
     282              bool ignorePreroll,
     283              bool ignorePlayPos,
    278284              bool underNetworkControl);
    279285
    280286    virtual ProgramInfo *GetCurrentProgram(void) const;