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.
This commit is contained in:
brunoherbelin
2020-08-21 00:58:20 +02:00
parent 13867e2192
commit 710514b478
6 changed files with 127 additions and 79 deletions

View File

@@ -286,7 +286,7 @@ void ImGuiToolkit::HelpMarker(const char* desc)
#define NUM_MARKS 10 #define NUM_MARKS 10
#define LARGE_TICK_INCREMENT 1 #define LARGE_TICK_INCREMENT 1
#define LABEL_TICK_INCREMENT 3 #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 }; 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 ) ); 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) // units conversion: from time to float (calculation made with higher precision first)
float time_ = static_cast<float> ( static_cast<double>(*time) / static_cast<double>(duration) ); float time_ = static_cast<float> ( static_cast<double>(*time - start) / static_cast<double>(end) );
float step_ = static_cast<float> ( static_cast<double>(step) / static_cast<double>(duration) ); float step_ = static_cast<float> ( static_cast<double>(step) / static_cast<double>(end) );
// //
// SECOND GET USER INPUT AND PERFORM CHANGES AND DECISIONS // 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); &time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb);
if (value_changed){ if (value_changed){
// g_print("slider %f %ld \n", time_slider, static_cast<guint64> ( static_cast<double>(time_slider) * static_cast<double>(duration) )); // g_print("slider %f %ld \n", time_slider, static_cast<guint64> ( static_cast<double>(time_slider) * static_cast<double>(duration) ));
*time = static_cast<guint64> ( 0.1 * static_cast<double>(time_slider) * static_cast<double>(duration) ); *time = static_cast<guint64> ( 0.1 * static_cast<double>(time_slider) * static_cast<double>(end) ) + start;
grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive); 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]; tick_step = optimal_tick_marks[i];
large_tick_step = optimal_tick_marks[i+LARGE_TICK_INCREMENT]; large_tick_step = optimal_tick_marks[i+LARGE_TICK_INCREMENT];
label_tick_step = optimal_tick_marks[i+LABEL_TICK_INCREMENT]; label_tick_step = optimal_tick_marks[i+LABEL_TICK_INCREMENT];
tick_step_pixels = timeline_bbox.GetWidth() * static_cast<float> ( static_cast<double>(tick_step) / static_cast<double>(duration) ); tick_step_pixels = timeline_bbox.GetWidth() * static_cast<float> ( static_cast<double>(tick_step) / static_cast<double>(end) );
} }
} }
@@ -409,14 +409,14 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 dura
// render text duration // render text duration
ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", 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); overlay_size = ImGui::CalcTextSize(overlay_buf, NULL);
ImVec2 duration_label = bbox.GetBR() - overlay_size - ImVec2(3.f, 3.f); ImVec2 duration_label = bbox.GetBR() - overlay_size - ImVec2(3.f, 3.f);
if (overlay_size.x > 0.0f) if (overlay_size.x > 0.0f)
ImGui::RenderTextClipped( duration_label, bbox.Max, overlay_buf, NULL, &overlay_size); ImGui::RenderTextClipped( duration_label, bbox.Max, overlay_buf, NULL, &overlay_size);
// render tick marks // render tick marks
while ( tick < duration) while ( tick < end)
{ {
// large tick mark // large tick mark
float tick_length = !(tick%large_tick_step) ? fontsize - style.FramePadding.y : style.FramePadding.y; 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 // next tick
tick += tick_step; tick += tick_step;
tick_percent = static_cast<float> ( static_cast<double>(tick) / static_cast<double>(duration) ); tick_percent = static_cast<float> ( static_cast<double>(tick) / static_cast<double>(end) );
pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent); pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent);
} }

View File

@@ -27,7 +27,7 @@ namespace ImGuiToolkit
// utility sliders // utility sliders
void Bar (float value, float in, float out, float min, float max, const char* title, bool expand); 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); bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size);
// fonts from ressources 'fonts/' // fonts from ressources 'fonts/'

View File

@@ -387,15 +387,13 @@ float MediaPlayer::aspectRatio() const
GstClockTime MediaPlayer::position() GstClockTime MediaPlayer::position()
{ {
GstClockTime pos = position_; if (position_ == GST_CLOCK_TIME_NONE && pipeline_ != nullptr) {
if (pos == GST_CLOCK_TIME_NONE && pipeline_ != nullptr) {
gint64 p = GST_CLOCK_TIME_NONE; gint64 p = GST_CLOCK_TIME_NONE;
if ( gst_element_query_position (pipeline_, GST_FORMAT_TIME, &p) ) 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) 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 ! // requesting to play, but stopped at end of stream : rewind first !
if ( desired_state_ == GST_STATE_PLAYING) { 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(); rewind();
} }
@@ -511,12 +510,18 @@ void MediaPlayer::rewind()
if (!enabled_ || !media_.seekable) if (!enabled_ || !media_.seekable)
return; return;
if (rate_ > 0.0) // playing forward, loop to begin
// playing forward, loop to begin if (rate_ > 0.0) {
execute_seek_command(0); // begin is the end of a gab which includes the first PTS (if exists)
else // normal case, begin is zero
// playing backward, loop to end execute_seek_command( media_.timeline.next(0) );
execute_seek_command(media_.timeline.end() - media_.timeline.step()); }
// 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()) if (!enabled_ || isPlaying())
return; 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(); rewind();
// step // step
@@ -539,8 +545,7 @@ void MediaPlayer::seek(GstClockTime pos)
return; return;
// apply seek // apply seek
GstClockTime target = CLAMP(pos, 0, media_.timeline.end()); GstClockTime target = CLAMP(pos, media_.timeline.start(), media_.timeline.end());
// GstClockTime target = CLAMP(pos, timeline.start(), timeline.end());
execute_seek_command(target); execute_seek_command(target);
} }
@@ -690,6 +695,13 @@ void MediaPlayer::update()
index_lock_.lock(); index_lock_.lock();
// get the last frame filled from fill_frame() // get the last frame filled from fill_frame()
read_index = last_index_; 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 // unlock access to index change
index_lock_.unlock(); index_lock_.unlock();
@@ -738,22 +750,28 @@ void MediaPlayer::update()
seeking_ = false; 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 // manage loop mode
if (need_loop) { if (need_loop) {
execute_loop_command(); 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 // no target given
if (target == GST_CLOCK_TIME_NONE) if (target == GST_CLOCK_TIME_NONE)
// create seek event with current position (rate changed ?) // create seek event with current position (rate changed ?)
seek_pos = position(); seek_pos = position_;
// target is given but useless // 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 // ignore request
return; return;
} }
@@ -887,7 +905,7 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
{ {
// Do NOT overwrite an unread EOS // Do NOT overwrite an unread EOS
if ( frame_[write_index_].status == EOS ) if ( frame_[write_index_].status == EOS )
return true; write_index_ = (write_index_ + 1) % N_VFRAME;
// lock access to frame // lock access to frame
frame_[write_index_].access.lock(); 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) // set the start position (i.e. pts of first frame we got)
if (media_.timeline.start() == GST_CLOCK_TIME_NONE) { if (media_.timeline.start() == GST_CLOCK_TIME_NONE) {
media_.timeline.setStart(buf->pts); media_.timeline.setFirst(buf->pts);
Log::Info("Timeline %ld [%ld %ld]", media_.timeline.numFrames(), media_.timeline.start(), media_.timeline.end()); Log::Info("Timeline %ld [%ld %ld]", media_.timeline.numFrames(), media_.timeline.first(), media_.timeline.end());
} }
} }
// full but invalid frame : will be deleted next iteration // 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) ) if ( !m->fill_frame(buf, MediaPlayer::PREROLL) )
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
// loop negative rate: emulate an EOS // 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); 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) ) if ( !m->fill_frame(buf, MediaPlayer::SAMPLE) )
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
// loop negative rate: emulate an EOS // 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); m->fill_frame(NULL, MediaPlayer::EOS);
} }
} }

View File

@@ -44,6 +44,7 @@ void Timeline::reset()
{ {
// reset timing // reset timing
timing_.reset(); timing_.reset();
first_ = GST_CLOCK_TIME_NONE;
step_ = GST_CLOCK_TIME_NONE; step_ = GST_CLOCK_TIME_NONE;
// clear gaps // clear gaps
@@ -55,9 +56,10 @@ bool Timeline::is_valid()
return timing_.is_valid() && step_ != GST_CLOCK_TIME_NONE; 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) void Timeline::setEnd(GstClockTime end)
@@ -70,6 +72,28 @@ void Timeline::setStep(GstClockTime dt)
step_ = 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_) void Timeline::updateGapsFromArray(float *array_, size_t array_size_)
{ {
// reset gaps // reset gaps
@@ -123,7 +147,7 @@ size_t Timeline::numGaps()
return gaps_.size(); 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)); TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
@@ -148,37 +172,37 @@ bool Timeline::addGap(TimeInterval s)
return false; return false;
} }
void Timeline::toggleGaps(GstClockTime from, GstClockTime to) //void Timeline::toggleGaps(GstClockTime from, GstClockTime to)
{ //{
TimeInterval interval(from, to); // TimeInterval interval(from, to);
TimeInterval gap; // TimeInterval gap;
bool is_a_gap_ = gapAt(from, gap); // bool is_a_gap_ = gapAt(from, gap);
if (interval.is_valid()) // if (interval.is_valid())
{ // {
// fill gap // // fill gap
if (is_a_gap_) { // if (is_a_gap_) {
} // }
// else create gap (from time is not in a gap) // // else create gap (from time is not in a gap)
else { // else {
TimeIntervalSet::iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(to)); // 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 there is a gap overlap with [from to] (or [to from]), expand the gap
if ( g != gaps_.end() ) { // if ( g != gaps_.end() ) {
interval.begin = MIN( (*g).begin, interval.begin); // interval.begin = MIN( (*g).begin, interval.begin);
interval.end = MAX( (*g).end , interval.end); // interval.end = MAX( (*g).end , interval.end);
gaps_.erase(g); // gaps_.erase(g);
} // }
// add the new gap // // add the new gap
addGap(interval); // addGap(interval);
Log::Info("add gap [ %ld %ld ]", interval.begin, interval.end); // 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) bool Timeline::removeGaptAt(GstClockTime t)
{ {

View File

@@ -58,7 +58,7 @@ struct TimeInterval
} }
inline bool includes(const GstClockTime t) const 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 // global properties of the timeline
// timeline is invalid untill all 3 are set // timeline is invalid untill all 3 are set
void setStart(GstClockTime start); void setFirst(GstClockTime first);
void setEnd(GstClockTime end); void setEnd(GstClockTime end);
void setStep(GstClockTime dt); void setStep(GstClockTime dt);
// get properties // get properties
inline GstClockTime start() const { return timing_.begin; } inline GstClockTime start() const { return timing_.begin; }
inline GstClockTime end() const { return timing_.end; } 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 step() const { return step_; }
inline GstClockTime duration() const { return timing_.duration(); } inline GstClockTime duration() const { return timing_.duration(); }
inline size_t numFrames() const { return duration() / step_; } inline size_t numFrames() const { return duration() / step_; }
GstClockTime next(GstClockTime time) const;
GstClockTime previous(GstClockTime time) const;
// Add / remove gaps in the timeline // Add / remove gaps in the timeline
bool addGap(TimeInterval s); bool addGap(TimeInterval s);
bool addGap(GstClockTime begin, GstClockTime end); bool addGap(GstClockTime begin, GstClockTime end);
bool removeGaptAt(GstClockTime t); bool removeGaptAt(GstClockTime t);
void clearGaps(); void clearGaps();
void toggleGaps(GstClockTime from, GstClockTime to); // void toggleGaps(GstClockTime from, GstClockTime to);
// get gaps // get gaps
size_t numGaps(); size_t numGaps();
bool gapAt(const GstClockTime t, TimeInterval &gap); bool gapAt(const GstClockTime t, TimeInterval &gap) const;
std::list< std::pair<guint64, guint64> > gaps() const; std::list< std::pair<guint64, guint64> > gaps() const;
// synchronize data structures // synchronize data structures
@@ -117,6 +122,7 @@ private:
// global information on the timeline // global information on the timeline
TimeInterval timing_; TimeInterval timing_;
GstClockTime first_;
GstClockTime step_; GstClockTime step_;
// main data structure containing list of gaps in the timeline // main data structure containing list of gaps in the timeline

View File

@@ -1330,7 +1330,7 @@ void MediaController::Render()
// bool interval_slider_pressed = ImGuiToolkit::InvisibleSliderInt("##TimelinePicking", &interval_time_current, 0, mp_->timeline().end(), size); // 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_; timeline_mp = mp_;
working_timeline = timeline_mp->timeline(); working_timeline = timeline_mp->timeline();
working_timeline.fillArrayFromGaps(array, array_size); working_timeline.fillArrayFromGaps(array, array_size);
@@ -1383,7 +1383,7 @@ void MediaController::Render()
} }
// custom timeline slider // 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); mp_->timeline().end(), mp_->timeline().step(), size.x);
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
@@ -1397,7 +1397,7 @@ void MediaController::Render()
// if the seek target time is different from the current position time // if the seek target time is different from the current position time
// (i.e. the difference is less than one frame) // (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) // request seek (ASYNC)
mp_->seek(seek_t); mp_->seek(seek_t);
slider_pressed_ = false; slider_pressed_ = false;