From 710514b478840d51f204c8607431d4629568f4fd Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Fri, 21 Aug 2020 00:58:20 +0200 Subject: [PATCH] Revert behavior of MediaPlayer position to normal and instead fixed the GUI to match the [start end] range of timeline (instead of shifting position in MediaPlayer). Fixed Loop mode for bi-directional and stop modes to react according to Timeline gaps. --- ImGuiToolkit.cpp | 16 ++++---- ImGuiToolkit.h | 2 +- MediaPlayer.cpp | 86 ++++++++++++++++++++++++---------------- Timeline.cpp | 82 ++++++++++++++++++++++++-------------- Timeline.h | 14 +++++-- UserInterfaceManager.cpp | 6 +-- 6 files changed, 127 insertions(+), 79 deletions(-) diff --git a/ImGuiToolkit.cpp b/ImGuiToolkit.cpp index b16732e..38a156c 100644 --- a/ImGuiToolkit.cpp +++ b/ImGuiToolkit.cpp @@ -286,7 +286,7 @@ void ImGuiToolkit::HelpMarker(const char* desc) #define NUM_MARKS 10 #define LARGE_TICK_INCREMENT 1 #define LABEL_TICK_INCREMENT 3 -bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 duration, guint64 step, const float width) +bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width) { static guint64 optimal_tick_marks[NUM_MARKS + LABEL_TICK_INCREMENT] = { 100 * MILISECOND, 500 * MILISECOND, 1 * SECOND, 2 * SECOND, 5 * SECOND, 10 * SECOND, 20 * SECOND, 1 * MINUTE, 2 * MINUTE, 5 * MINUTE, 10 * MINUTE, 60 * MINUTE, 60 * MINUTE }; @@ -326,8 +326,8 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 dura ImRect slider_bbox( timeline_bbox.GetTL() + ImVec2(-cursor_width + 2.f, cursor_width + 4.f ), timeline_bbox.GetBR() + ImVec2( cursor_width - 2.f, 0.f ) ); // units conversion: from time to float (calculation made with higher precision first) - float time_ = static_cast ( static_cast(*time) / static_cast(duration) ); - float step_ = static_cast ( static_cast(step) / static_cast(duration) ); + float time_ = static_cast ( static_cast(*time - start) / static_cast(end) ); + float step_ = static_cast ( static_cast(step) / static_cast(end) ); // // SECOND GET USER INPUT AND PERFORM CHANGES AND DECISIONS @@ -359,7 +359,7 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 dura &time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb); if (value_changed){ // g_print("slider %f %ld \n", time_slider, static_cast ( static_cast(time_slider) * static_cast(duration) )); - *time = static_cast ( 0.1 * static_cast(time_slider) * static_cast(duration) ); + *time = static_cast ( 0.1 * static_cast(time_slider) * static_cast(end) ) + start; grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive); } @@ -393,7 +393,7 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 dura tick_step = optimal_tick_marks[i]; large_tick_step = optimal_tick_marks[i+LARGE_TICK_INCREMENT]; label_tick_step = optimal_tick_marks[i+LABEL_TICK_INCREMENT]; - tick_step_pixels = timeline_bbox.GetWidth() * static_cast ( static_cast(tick_step) / static_cast(duration) ); + tick_step_pixels = timeline_bbox.GetWidth() * static_cast ( static_cast(tick_step) / static_cast(end) ); } } @@ -409,14 +409,14 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 dura // render text duration ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", - GstToolkit::time_to_string(duration, GstToolkit::TIME_STRING_MINIMAL).c_str()); + GstToolkit::time_to_string(end, GstToolkit::TIME_STRING_MINIMAL).c_str()); overlay_size = ImGui::CalcTextSize(overlay_buf, NULL); ImVec2 duration_label = bbox.GetBR() - overlay_size - ImVec2(3.f, 3.f); if (overlay_size.x > 0.0f) ImGui::RenderTextClipped( duration_label, bbox.Max, overlay_buf, NULL, &overlay_size); // render tick marks - while ( tick < duration) + while ( tick < end) { // large tick mark float tick_length = !(tick%large_tick_step) ? fontsize - style.FramePadding.y : style.FramePadding.y; @@ -440,7 +440,7 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 dura // next tick tick += tick_step; - tick_percent = static_cast ( static_cast(tick) / static_cast(duration) ); + tick_percent = static_cast ( static_cast(tick) / static_cast(end) ); pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent); } diff --git a/ImGuiToolkit.h b/ImGuiToolkit.h index ed3d3c6..3161323 100644 --- a/ImGuiToolkit.h +++ b/ImGuiToolkit.h @@ -27,7 +27,7 @@ namespace ImGuiToolkit // utility sliders void Bar (float value, float in, float out, float min, float max, const char* title, bool expand); - bool TimelineSlider (const char* label, guint64 *time, guint64 duration, guint64 step, const float width); + bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width); bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size); // fonts from ressources 'fonts/' diff --git a/MediaPlayer.cpp b/MediaPlayer.cpp index 2315779..2f8e528 100644 --- a/MediaPlayer.cpp +++ b/MediaPlayer.cpp @@ -387,15 +387,13 @@ float MediaPlayer::aspectRatio() const GstClockTime MediaPlayer::position() { - GstClockTime pos = position_; - - if (pos == GST_CLOCK_TIME_NONE && pipeline_ != nullptr) { + if (position_ == GST_CLOCK_TIME_NONE && pipeline_ != nullptr) { gint64 p = GST_CLOCK_TIME_NONE; if ( gst_element_query_position (pipeline_, GST_FORMAT_TIME, &p) ) - pos = p; + position_ = p; } - - return pos - media_.timeline.start(); + + return position_; } void MediaPlayer::enable(bool on) @@ -457,7 +455,8 @@ void MediaPlayer::play(bool on) // requesting to play, but stopped at end of stream : rewind first ! if ( desired_state_ == GST_STATE_PLAYING) { - if ( ( rate_>0.0 ? media_.timeline.end() - position() : position() ) < 2 * media_.timeline.step() ) + if ( ( rate_ < 0.0 && position_ <= media_.timeline.next(0) ) + || ( rate_ > 0.0 && position_ >= media_.timeline.previous(media_.timeline.last()) ) ) rewind(); } @@ -511,12 +510,18 @@ void MediaPlayer::rewind() if (!enabled_ || !media_.seekable) return; - if (rate_ > 0.0) - // playing forward, loop to begin - execute_seek_command(0); - else - // playing backward, loop to end - execute_seek_command(media_.timeline.end() - media_.timeline.step()); + // playing forward, loop to begin + if (rate_ > 0.0) { + // begin is the end of a gab which includes the first PTS (if exists) + // normal case, begin is zero + execute_seek_command( media_.timeline.next(0) ); + } + // playing backward, loop to endTimeInterval gap; + else { + // end is the start of a gab which includes the last PTS (if exists) + // normal case, end is last frame + execute_seek_command( media_.timeline.previous(media_.timeline.last())); + } } @@ -526,7 +531,8 @@ void MediaPlayer::step() if (!enabled_ || isPlaying()) return; - if ( ( rate_ < 0.0 && position_ <= media_.timeline.start() ) || ( rate_ > 0.0 && position_ >= media_.timeline.end() ) ) + if ( ( rate_ < 0.0 && position_ <= media_.timeline.next(0) ) + || ( rate_ > 0.0 && position_ >= media_.timeline.previous(media_.timeline.last()) ) ) rewind(); // step @@ -539,8 +545,7 @@ void MediaPlayer::seek(GstClockTime pos) return; // apply seek - GstClockTime target = CLAMP(pos, 0, media_.timeline.end()); -// GstClockTime target = CLAMP(pos, timeline.start(), timeline.end()); + GstClockTime target = CLAMP(pos, media_.timeline.start(), media_.timeline.end()); execute_seek_command(target); } @@ -690,6 +695,13 @@ void MediaPlayer::update() index_lock_.lock(); // get the last frame filled from fill_frame() read_index = last_index_; + // Do NOT miss and jump directly (after seek) to a pre-roll + for (guint i = 0; i < N_VFRAME; ++i) { + if (frame_[i].status == PREROLL) { + read_index = i; + break; + } + } // unlock access to index change index_lock_.unlock(); @@ -738,22 +750,28 @@ void MediaPlayer::update() seeking_ = false; } + // manage timeline + TimeInterval gap; + if (position_ != GST_CLOCK_TIME_NONE && media_.timeline.gapAt(position_, gap)) { + + if (gap.is_valid()) { + + GstClockTime jumpPts = (rate_>0.f) ? gap.end : gap.begin; + + if (jumpPts > media_.timeline.first() && jumpPts < media_.timeline.last()) + seek( jumpPts); + else + need_loop = true; + + } + + } + // manage loop mode if (need_loop) { execute_loop_command(); } - // manage timeline - TimeInterval gap; - if (position_ != GST_CLOCK_TIME_NONE && media_.timeline.gapAt(position_, gap)) { - - - if (gap.is_valid()) - seek( (rate_>0.f) ? gap.end : gap.begin); - - // TODO : manage loop when jumping out of timeline - - } } @@ -783,9 +801,9 @@ void MediaPlayer::execute_seek_command(GstClockTime target) // no target given if (target == GST_CLOCK_TIME_NONE) // create seek event with current position (rate changed ?) - seek_pos = position(); + seek_pos = position_; // target is given but useless - else if ( ABS_DIFF(target, position()) < media_.timeline.step()) { + else if ( ABS_DIFF(target, position_) < media_.timeline.step()) { // ignore request return; } @@ -887,7 +905,7 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status) { // Do NOT overwrite an unread EOS if ( frame_[write_index_].status == EOS ) - return true; + write_index_ = (write_index_ + 1) % N_VFRAME; // lock access to frame frame_[write_index_].access.lock(); @@ -925,8 +943,8 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status) // set the start position (i.e. pts of first frame we got) if (media_.timeline.start() == GST_CLOCK_TIME_NONE) { - media_.timeline.setStart(buf->pts); - Log::Info("Timeline %ld [%ld %ld]", media_.timeline.numFrames(), media_.timeline.start(), media_.timeline.end()); + media_.timeline.setFirst(buf->pts); + Log::Info("Timeline %ld [%ld %ld]", media_.timeline.numFrames(), media_.timeline.first(), media_.timeline.end()); } } // full but invalid frame : will be deleted next iteration @@ -987,7 +1005,7 @@ GstFlowReturn MediaPlayer::callback_new_preroll (GstAppSink *sink, gpointer p) if ( !m->fill_frame(buf, MediaPlayer::PREROLL) ) ret = GST_FLOW_ERROR; // loop negative rate: emulate an EOS - else if (m->playSpeed() < 0.f && buf->pts <= m->media_.timeline.start()) { + else if (m->playSpeed() < 0.f && !(buf->pts > 0) ) { m->fill_frame(NULL, MediaPlayer::EOS); } } @@ -1021,7 +1039,7 @@ GstFlowReturn MediaPlayer::callback_new_sample (GstAppSink *sink, gpointer p) if ( !m->fill_frame(buf, MediaPlayer::SAMPLE) ) ret = GST_FLOW_ERROR; // loop negative rate: emulate an EOS - else if (m->playSpeed() < 0.f && buf->pts <= m->media_.timeline.start()) { + else if (m->playSpeed() < 0.f && !(buf->pts > 0) ) { m->fill_frame(NULL, MediaPlayer::EOS); } } diff --git a/Timeline.cpp b/Timeline.cpp index ec53e8a..e1d9d16 100644 --- a/Timeline.cpp +++ b/Timeline.cpp @@ -44,6 +44,7 @@ void Timeline::reset() { // reset timing timing_.reset(); + first_ = GST_CLOCK_TIME_NONE; step_ = GST_CLOCK_TIME_NONE; // clear gaps @@ -55,9 +56,10 @@ bool Timeline::is_valid() return timing_.is_valid() && step_ != GST_CLOCK_TIME_NONE; } -void Timeline::setStart(GstClockTime start) +void Timeline::setFirst(GstClockTime first) { - timing_.begin = start; + timing_.begin = 0; + first_ = first; } void Timeline::setEnd(GstClockTime end) @@ -70,6 +72,28 @@ void Timeline::setStep(GstClockTime dt) step_ = dt; } + +GstClockTime Timeline::next(GstClockTime time) const +{ + GstClockTime next_time = time; + + TimeInterval gap; + if (gapAt(time, gap) && gap.is_valid()) + next_time = gap.end; + + return next_time; +} + +GstClockTime Timeline::previous(GstClockTime time) const +{ + GstClockTime prev_time = time; + TimeInterval gap; + if (gapAt(time, gap) && gap.is_valid()) + prev_time = gap.begin; + + return prev_time; +} + void Timeline::updateGapsFromArray(float *array_, size_t array_size_) { // reset gaps @@ -123,7 +147,7 @@ size_t Timeline::numGaps() return gaps_.size(); } -bool Timeline::gapAt(const GstClockTime t, TimeInterval &gap) +bool Timeline::gapAt(const GstClockTime t, TimeInterval &gap) const { TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t)); @@ -148,37 +172,37 @@ bool Timeline::addGap(TimeInterval s) return false; } -void Timeline::toggleGaps(GstClockTime from, GstClockTime to) -{ - TimeInterval interval(from, to); - TimeInterval gap; - bool is_a_gap_ = gapAt(from, gap); +//void Timeline::toggleGaps(GstClockTime from, GstClockTime to) +//{ +// TimeInterval interval(from, to); +// TimeInterval gap; +// bool is_a_gap_ = gapAt(from, gap); - if (interval.is_valid()) - { - // fill gap - if (is_a_gap_) { +// if (interval.is_valid()) +// { +// // fill gap +// if (is_a_gap_) { - } - // else create gap (from time is not in a gap) - else { +// } +// // else create gap (from time is not in a gap) +// else { - TimeIntervalSet::iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(to)); - // if there is a gap overlap with [from to] (or [to from]), expand the gap - if ( g != gaps_.end() ) { - interval.begin = MIN( (*g).begin, interval.begin); - interval.end = MAX( (*g).end , interval.end); - gaps_.erase(g); - } - // add the new gap - addGap(interval); - Log::Info("add gap [ %ld %ld ]", interval.begin, interval.end); - } +// TimeIntervalSet::iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(to)); +// // if there is a gap overlap with [from to] (or [to from]), expand the gap +// if ( g != gaps_.end() ) { +// interval.begin = MIN( (*g).begin, interval.begin); +// interval.end = MAX( (*g).end , interval.end); +// gaps_.erase(g); +// } +// // add the new gap +// addGap(interval); +// Log::Info("add gap [ %ld %ld ]", interval.begin, interval.end); +// } - } - Log::Info("%d gaps in timeline", numGaps()); -} +// } +// Log::Info("%d gaps in timeline", numGaps()); +//} bool Timeline::removeGaptAt(GstClockTime t) { diff --git a/Timeline.h b/Timeline.h index aa97726..3d35a0c 100644 --- a/Timeline.h +++ b/Timeline.h @@ -58,7 +58,7 @@ struct TimeInterval } inline bool includes(const GstClockTime t) const { - return (is_valid() && t != GST_CLOCK_TIME_NONE && t > this->begin && t < this->end); + return (is_valid() && t != GST_CLOCK_TIME_NONE && !(t < this->begin) && !(t > this->end) ); } }; @@ -86,27 +86,32 @@ public: // global properties of the timeline // timeline is invalid untill all 3 are set - void setStart(GstClockTime start); + void setFirst(GstClockTime first); void setEnd(GstClockTime end); void setStep(GstClockTime dt); // get properties inline GstClockTime start() const { return timing_.begin; } inline GstClockTime end() const { return timing_.end; } + inline GstClockTime first() const { return first_; } + inline GstClockTime last() const { return timing_.end - step_; } inline GstClockTime step() const { return step_; } inline GstClockTime duration() const { return timing_.duration(); } inline size_t numFrames() const { return duration() / step_; } + GstClockTime next(GstClockTime time) const; + GstClockTime previous(GstClockTime time) const; + // Add / remove gaps in the timeline bool addGap(TimeInterval s); bool addGap(GstClockTime begin, GstClockTime end); bool removeGaptAt(GstClockTime t); void clearGaps(); - void toggleGaps(GstClockTime from, GstClockTime to); +// void toggleGaps(GstClockTime from, GstClockTime to); // get gaps size_t numGaps(); - bool gapAt(const GstClockTime t, TimeInterval &gap); + bool gapAt(const GstClockTime t, TimeInterval &gap) const; std::list< std::pair > gaps() const; // synchronize data structures @@ -117,6 +122,7 @@ private: // global information on the timeline TimeInterval timing_; + GstClockTime first_; GstClockTime step_; // main data structure containing list of gaps in the timeline diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 458f944..18d0d35 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -1330,7 +1330,7 @@ void MediaController::Render() // bool interval_slider_pressed = ImGuiToolkit::InvisibleSliderInt("##TimelinePicking", &interval_time_current, 0, mp_->timeline().end(), size); // - if (timeline_mp != mp_) { + if (timeline_mp != mp_ || !working_timeline.is_valid()) { timeline_mp = mp_; working_timeline = timeline_mp->timeline(); working_timeline.fillArrayFromGaps(array, array_size); @@ -1383,7 +1383,7 @@ void MediaController::Render() } // custom timeline slider - slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, + slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, mp_->timeline().first(), mp_->timeline().end(), mp_->timeline().step(), size.x); ImGui::PopStyleVar(2); @@ -1397,7 +1397,7 @@ void MediaController::Render() // if the seek target time is different from the current position time // (i.e. the difference is less than one frame) - if ( ABS_DIFF (current_t, seek_t) > mp_->timeline().step() ) { + if ( ABS_DIFF (current_t, seek_t) > 2 * mp_->timeline().step() ) { // request seek (ASYNC) mp_->seek(seek_t); slider_pressed_ = false;