Saving and loading of timeline, with fading and gaps. Applying fading to

MediaSource. Playing with timeline options to facilitate its use.
This commit is contained in:
brunoherbelin
2020-08-24 00:23:03 +02:00
parent 86fd5f21f3
commit 240f1fde0a
11 changed files with 382 additions and 196 deletions

View File

@@ -636,7 +636,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
// read user input and activate widget
const bool left_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left);
const bool right_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Right);
const bool right_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Right) | (ImGui::GetIO().KeyAlt & left_mouse_press) ;
const bool mouse_press = left_mouse_press | right_mouse_press;
const bool hovered = ImGui::ItemHoverable(bbox, id);
bool temp_input_is_active = ImGui::TempInputIsActive(id);
@@ -653,7 +653,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
else
return false;
ImVec4* colors = ImGui::GetStyle().Colors;
static ImVec4* colors = ImGui::GetStyle().Colors;
ImVec4 bg_color = hovered ? colors[ImGuiCol_FrameBgHovered] : colors[ImGuiCol_FrameBg];
// enter edit if widget is active
@@ -674,12 +674,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
if (previous_index == UINT32_MAX)
previous_index = index;
if (left_mouse_press) {
lines_array[index] = values_max - y;
for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i)
lines_array[i] = values_max - y;
}
else {
if (right_mouse_press){
static float target_value = values_min;
// toggle value histo
@@ -691,6 +686,11 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i)
histogram_array[i] = target_value;
}
else {
lines_array[index] = values_max - y;
for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i)
lines_array[i] = values_max - y;
}
previous_index = index;

View File

@@ -31,6 +31,7 @@ MediaPlayer::MediaPlayer()
uri_ = "undefined";
pipeline_ = nullptr;
converter_ = nullptr;
ready_ = false;
failed_ = false;
@@ -209,15 +210,22 @@ void MediaPlayer::execute_open()
// equivalent to gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink
// Build string describing pipeline
// NB: video convertion chroma-resampler
// Duplicates the samples when upsampling and drops when downsampling 0
// Uses linear interpolation 1 (default)
// Uses cubic interpolation 2
// Uses sinc interpolation 3
// video deinterlacing method
// tomsmocomp (0) Motion Adaptive: Motion Search
// greedyh (1) Motion Adaptive: Advanced Detection
// greedyl (2) Motion Adaptive: Simple Detection
// vfir (3) Blur Vertical
// linear (4) Linear
// scalerbob (6) Double lines
// video convertion chroma-resampler
// Duplicates the samples when upsampling and drops when downsampling 0
// Uses linear interpolation 1 (default)
// Uses cubic interpolation 2
// Uses sinc interpolation 3
string description = "uridecodebin uri=" + uri_ + " ! ";
if (media_.interlaced)
description += "deinterlace ! ";
description += "videoconvert chroma-resampler=2 ! appsink name=sink";
description += "deinterlace method=2 ! ";
description += "videoconvert chroma-resampler=2 n-threads=2 ! appsink name=sink";
// parse pipeline descriptor
GError *error = NULL;
@@ -336,6 +344,10 @@ void MediaPlayer::close()
// un-ready the media player
ready_ = false;
// no more need to reference converter
if (converter_ != nullptr)
gst_object_unref (converter_);
// clean up GST
if (pipeline_ != nullptr) {
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
@@ -697,12 +709,7 @@ void MediaPlayer::update()
// try to get info from discoverer
if (discoverer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready )
{
// remember loaded gaps
TimeIntervalSet gaps = media_.timeline.gaps();
// ok, discovering thread is finished ! Get the media info
media_ = discoverer_.get();
// restore loaded gaps (overwriten above)
media_.timeline.setGaps( gaps );
// if its ok, open the media
if (media_.valid)
execute_open();
@@ -715,13 +722,6 @@ void MediaPlayer::update()
if (!enabled_ || (media_.isimage && textureindex_>0 ) )
return;
// if (seeking_) {
// GstState state;
// gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
// seeking_ = false;
// return;
// }
// local variables before trying to update
guint read_index = 0;
bool need_loop = false;
@@ -895,11 +895,15 @@ double MediaPlayer::playSpeed() const
return rate_;
}
Timeline MediaPlayer::timeline()
Timeline *MediaPlayer::timeline()
{
return media_.timeline;
return &media_.timeline;
}
float MediaPlayer::currentTimelineFading()
{
return media_.timeline.fadingAt(position_);
}
void MediaPlayer::setTimeline(Timeline tl)
{
@@ -1040,6 +1044,7 @@ GstFlowReturn MediaPlayer::callback_new_preroll (GstAppSink *sink, gpointer p)
// send frames to media player only if ready
MediaPlayer *m = (MediaPlayer *)p;
if (m && m->ready_) {
// fill frame from buffer
if ( !m->fill_frame(buf, MediaPlayer::PREROLL) )
ret = GST_FLOW_ERROR;

View File

@@ -49,7 +49,9 @@ struct MediaInfo {
inline MediaInfo& operator = (const MediaInfo& b)
{
if (this != &b) {
this->timeline = b.timeline;
this->timeline.setEnd( b.timeline.end() );
this->timeline.setStep( b.timeline.step() );
this->timeline.setFirst( b.timeline.first() );
this->width = b.width;
this->par_width = b.par_width;
this->height = b.height;
@@ -194,11 +196,10 @@ public:
* - duration : timeline.duration()
* - frame duration : timeline.step()
*/
Timeline timeline();
Timeline *timeline();
void setTimeline(Timeline tl);
// void toggleGapInTimeline(GstClockTime from, GstClockTime to);
float currentTimelineFading();
/**
* Get position time
* */
@@ -261,6 +262,7 @@ private:
LoopMode loop_;
GstState desired_state_;
GstElement *pipeline_;
GstElement *converter_;
GstVideoInfo v_frame_video_info_;
std::atomic<bool> ready_;
std::atomic<bool> failed_;

View File

@@ -135,6 +135,7 @@ void MediaSource::render()
// render the media player into frame buffer
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
renderbuffer_->begin();
mediasurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
mediasurface_->draw(glm::identity<glm::mat4>(), projection);
renderbuffer_->end();
}

View File

@@ -184,17 +184,25 @@ void SessionCreator::visit(MediaPlayer &n)
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
if (mediaplayerNode) {
// timeline
XMLElement *gapselement = mediaplayerNode->FirstChildElement("Gaps");
if (gapselement) {
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
if (timelineelement) {
Timeline tl;
XMLElement* gap = gapselement->FirstChildElement("Interval");
for( ; gap ; gap = gap->NextSiblingElement())
{
GstClockTime a = GST_CLOCK_TIME_NONE;
GstClockTime b = GST_CLOCK_TIME_NONE;
gap->QueryUnsigned64Attribute("begin", &a);
gap->QueryUnsigned64Attribute("end", &b);
tl.addGap( a, b );
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
if (gapselement) {
XMLElement* gap = gapselement->FirstChildElement("Interval");
for( ; gap ; gap = gap->NextSiblingElement())
{
GstClockTime a = GST_CLOCK_TIME_NONE;
GstClockTime b = GST_CLOCK_TIME_NONE;
gap->QueryUnsigned64Attribute("begin", &a);
gap->QueryUnsigned64Attribute("end", &b);
tl.addGap( a, b );
}
}
XMLElement *fadingselement = timelineelement->FirstChildElement("Fading");
if (fadingselement) {
XMLElement* array = fadingselement->FirstChildElement("array");
XMLElementDecodeArray(array, tl.fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
}
n.setTimeline(tl);
}

View File

@@ -147,17 +147,27 @@ void SessionVisitor::visit(MediaPlayer &n)
newelement->SetAttribute("loop", (int) n.loop());
newelement->SetAttribute("speed", n.playSpeed());
// timeline
XMLElement *timelineelement = xmlDoc_->NewElement("Timeline");
// gaps in timeline
XMLElement *gapselement = xmlDoc_->NewElement("Gaps");
TimeIntervalSet gaps = n.timeline().gaps();
TimeIntervalSet gaps = n.timeline()->gaps();
for( auto it = gaps.begin(); it!= gaps.end(); it++) {
XMLElement *g = xmlDoc_->NewElement("Interval");
g->SetAttribute("begin", (*it).begin);
g->SetAttribute("end", (*it).end);
gapselement->InsertEndChild(g);
}
newelement->InsertEndChild(gapselement);
timelineelement->InsertEndChild(gapselement);
// fading in timeline
XMLElement *fadingelement = xmlDoc_->NewElement("Fading");
XMLElement *array = XMLElementEncodeArray(xmlDoc_, n.timeline()->fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
fadingelement->InsertEndChild(array);
timelineelement->InsertEndChild(fadingelement);
newelement->InsertEndChild(timelineelement);
xmlCurrent_->InsertEndChild(newelement);
}

View File

@@ -6,8 +6,6 @@
#include "Log.h"
#include "Timeline.h"
#define SEGMENT_ARRAY_MAX_SIZE 1000
struct includesTime: public std::unary_function<TimeInterval, bool>
{
inline bool operator()(const TimeInterval s) const
@@ -36,6 +34,8 @@ Timeline& Timeline::operator = (const Timeline& b)
this->timing_ = b.timing_;
this->step_ = b.step_;
this->gaps_ = b.gaps_;
memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float));
memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float));
}
return *this;
}
@@ -47,8 +47,8 @@ void Timeline::reset()
first_ = GST_CLOCK_TIME_NONE;
step_ = GST_CLOCK_TIME_NONE;
// clear gaps
gaps_.clear();
clearGaps();
clearFading();
}
bool Timeline::is_valid()
@@ -58,8 +58,10 @@ bool Timeline::is_valid()
void Timeline::setFirst(GstClockTime first)
{
timing_.begin = 0;
first_ = first;
// validate timing
if (first_ != GST_CLOCK_TIME_NONE)
timing_.begin = 0;
}
void Timeline::setEnd(GstClockTime end)
@@ -72,7 +74,6 @@ void Timeline::setStep(GstClockTime dt)
step_ = dt;
}
GstClockTime Timeline::next(GstClockTime time) const
{
GstClockTime next_time = time;
@@ -94,52 +95,18 @@ GstClockTime Timeline::previous(GstClockTime time) const
return prev_time;
}
void Timeline::updateGapsFromArray(float *array_, size_t array_size_)
float *Timeline::gapsArray()
{
// reset gaps
gaps_.clear();
// loop over the array to detect gaps
float status = 1.f;
GstClockTime begin_gap = GST_CLOCK_TIME_NONE;
for (size_t i = 0; i < array_size_; ++i) {
// detect a change of value between two slots
if ( array_[i] != status) {
// compute time of the event in array
GstClockTime t = (timing_.duration() * i) / array_size_;
// change from 1.f to 0.f : begin of a gap
if (status) {
begin_gap = t;
}
// change from 0.f to 1.f : end of a gap
else {
addGap( begin_gap, t );
begin_gap = GST_CLOCK_TIME_NONE;
}
// swap
status = array_[i];
}
if (gaps_array_need_update_) {
fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY);
}
// end a potentially pending gap if reached end of array with no end of gap
if (begin_gap != GST_CLOCK_TIME_NONE)
addGap( begin_gap, timing_.end );
return gapsArray_;
}
void Timeline::fillArrayFromGaps(float *array_, size_t array_size_)
void Timeline::update()
{
if (array_ != nullptr && array_size_ > 0 && timing_.is_valid()) {
// fill the array from gaps
// NB: this is the less efficient algorithm possible! but we should not keep arrays anyway
// TODO : implement an ImGui widget to plot a timeline instead of an array
TimeInterval gap;
for (size_t i = 0; i < array_size_; ++i) {
GstClockTime t = (timing_.duration() * i) / array_size_;
array_[i] = gapAt(t, gap) ? 0.f : 1.f;
}
}
updateGapsFromArray(gapsArray_, MAX_TIMELINE_ARRAY);
gaps_array_need_update_ = false;
}
bool Timeline::gapAt(const GstClockTime t, TimeInterval &gap) const
@@ -161,64 +128,220 @@ bool Timeline::addGap(GstClockTime begin, GstClockTime end)
bool Timeline::addGap(TimeInterval s)
{
if ( s.is_valid() )
if ( s.is_valid() ) {
gaps_array_need_update_ = true;
return gaps_.insert(s).second;
}
return false;
}
void Timeline::setGaps(TimeIntervalSet g)
{
gaps_array_need_update_ = true;
gaps_ = g;
}
//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_) {
// }
// // 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);
// }
// }
// Log::Info("%d gaps in timeline", numGaps());
//}
bool Timeline::removeGaptAt(GstClockTime t)
{
TimeIntervalSet::const_iterator s = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
if ( s != gaps_.end() ) {
gaps_.erase(s);
gaps_array_need_update_ = true;
return true;
}
return false;
}
TimeIntervalSet Timeline::sections() const
{
TimeIntervalSet sec;
GstClockTime begin_sec = timing_.begin;
if (gaps_.size() > 0) {
auto it = gaps_.begin();
if ((*it).begin == begin_sec) {
begin_sec = (*it).end;
++it;
}
for (; it != gaps_.end(); ++it)
{
sec.insert( TimeInterval(begin_sec, (*it).begin) );
begin_sec = (*it).end;
}
}
if (begin_sec != timing_.end)
sec.insert( TimeInterval(begin_sec, timing_.end) );
return sec;
}
void Timeline::clearGaps()
{
gaps_.clear();
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
gapsArray_[i] = 0.f;
gaps_array_need_update_ = true;
}
float Timeline::fadingAt(const GstClockTime t)
{
GstClockTime modulo = t % step_;
GstClockTime keyframe = t - modulo;
size_t keyframe_index = CLAMP( (MAX_TIMELINE_ARRAY * keyframe) / timing_.end, 0, MAX_TIMELINE_ARRAY-1);
size_t keyframe_next_index = CLAMP( keyframe_index+1, 0, MAX_TIMELINE_ARRAY-1);
float interpol = static_cast<float>( static_cast<double>(modulo) / static_cast<double>(step_) );
float v = fadingArray_[keyframe_index];
v += interpol * (fadingArray_[keyframe_next_index] - fadingArray_[keyframe_index]);
return v;
}
void Timeline::clearFading()
{
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
fadingArray_[i] = 1.f;
}
void Timeline::smoothFading(uint N)
{
const float kernel[7] = { 2.f, 22.f, 97.f, 159.f, 97.f, 22.f, 2.f};
float tmparray[MAX_TIMELINE_ARRAY];
for (uint n = 0; n < N; ++n) {
for (long i = 0; i < MAX_TIMELINE_ARRAY; ++i) {
tmparray[i] = 0.f;
float divider = 0.f;
for( long j = 0; j < 7; ++j) {
long k = i - 3 + j;
if (k > -1 && k < MAX_TIMELINE_ARRAY-1) {
tmparray[i] += fadingArray_[k] * kernel[j];
divider += kernel[j];
}
}
tmparray[i] *= 1.f / divider;
}
memcpy( fadingArray_, tmparray, MAX_TIMELINE_ARRAY * sizeof(float));
}
}
void Timeline::autoFading(uint milisecond)
{
GstClockTime stepduration = timing_.end / MAX_TIMELINE_ARRAY;
stepduration = GST_TIME_AS_MSECONDS(stepduration);
uint N = milisecond / stepduration;
// reset all to zero
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
fadingArray_[i] = 0.f;
// get sections (inverse of gaps)
TimeIntervalSet sec = sections();
// fading for each section
// NB : there is at least one
for (auto it = sec.begin(); it != sec.end(); ++it)
{
// get index of begining of section
size_t s = ( (*it).begin * MAX_TIMELINE_ARRAY ) / timing_.end;
// get index of ending of section
size_t e = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end;
// calculate size of the smooth transition in [s e] interval
uint n = MIN( (e-s)/3, N );
// linear fade in starting at s
size_t i = s;
for (; i < s+n; ++i)
fadingArray_[i] = static_cast<float>(i-s) / static_cast<float>(n);
// plateau
for (; i < e-n; ++i)
fadingArray_[i] = 1.f;
// linear fade out ending at e
for (; i < e; ++i)
fadingArray_[i] = static_cast<float>(e-i) / static_cast<float>(n);
}
}
void Timeline::updateGapsFromArray(float *array, size_t array_size)
{
// reset gaps
gaps_.clear();
// fill the gaps from array
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
// loop over the array to detect gaps
float status = 0.f;
GstClockTime begin_gap = GST_CLOCK_TIME_NONE;
for (size_t i = 0; i < array_size; ++i) {
// detect a change of value between two slots
if ( array[i] != status) {
// compute time of the event in array
GstClockTime t = (timing_.duration() * i) / array_size;
// change from 0.f to 1.f : begin of a gap
if (array[i] > 0.f) {
begin_gap = t;
}
// change from 1.f to 0.f : end of a gap
else {
addGap( begin_gap, t );
begin_gap = GST_CLOCK_TIME_NONE;
}
// swap
status = array[i];
}
}
// end a potentially pending gap if reached end of array with no end of gap
if (begin_gap != GST_CLOCK_TIME_NONE)
addGap( begin_gap, timing_.end );
}
}
void Timeline::fillArrayFromGaps(float *array, size_t array_size)
{
// fill the array from gaps
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
for(int i=0;i<array_size;++i)
gapsArray_[i] = 0.f;
// for each gap
for (auto it = gaps_.begin(); it != gaps_.end(); ++it)
{
size_t s = ( (*it).begin * array_size ) / timing_.end;
size_t e = ( (*it).end * array_size ) / timing_.end;
// fill with 1 where there is a gap
for (size_t i = s; i < e; ++i) {
gapsArray_[i] = 1.f;
}
}
gaps_array_need_update_ = false;
}
// // NB: less efficient algorithm :
// TimeInterval gap;
// for (size_t i = 0; i < array_size; ++i) {
// GstClockTime t = (timing_.duration() * i) / array_size;
// array[i] = gapAt(t, gap) ? 1.f : 0.f;
// }
}

View File

@@ -8,6 +8,8 @@
#include <gst/pbutils/pbutils.h>
#define MAX_TIMELINE_ARRAY 2000
struct TimeInterval
{
GstClockTime begin;
@@ -81,16 +83,16 @@ public:
~Timeline();
Timeline& operator = (const Timeline& b);
void reset();
bool is_valid();
void update();
// global properties of the timeline
// timeline is invalid untill all 3 are set
// timeline is valid only if all 3 are set
void setFirst(GstClockTime first);
void setEnd(GstClockTime end);
void setStep(GstClockTime dt);
// get properties
// Timing manipulation
inline GstClockTime start() const { return timing_.begin; }
inline GstClockTime end() const { return timing_.end; }
inline GstClockTime first() const { return first_; }
@@ -98,33 +100,45 @@ public:
inline GstClockTime step() const { return step_; }
inline GstClockTime duration() const { return timing_.duration(); }
inline size_t numFrames() const { return duration() / step_; }
inline TimeIntervalSet gaps() const { return gaps_; }
inline size_t numGaps() const { return gaps_.size(); }
GstClockTime next(GstClockTime time) const;
GstClockTime previous(GstClockTime time) const;
// Add / remove / get gaps in the timeline
// Manipulation of gaps in the timeline
inline TimeIntervalSet gaps() const { return gaps_; }
inline TimeIntervalSet sections() const;
inline size_t numGaps() const { return gaps_.size(); }
float *gapsArray();
void clearGaps();
void setGaps(TimeIntervalSet g);
bool addGap(TimeInterval s);
bool addGap(GstClockTime begin, GstClockTime end);
bool removeGaptAt(GstClockTime t);
bool gapAt(const GstClockTime t, TimeInterval &gap) const;
void setGaps(TimeIntervalSet g);
// synchronize data structures
void updateGapsFromArray(float *array_, size_t array_size_);
void fillArrayFromGaps(float *array_, size_t array_size_);
float fadingAt(const GstClockTime t);
inline float *fadingArray() { return fadingArray_; }
void clearFading();
void smoothFading(uint N = 1);
void autoFading(uint milisecond = 100);
private:
void reset();
// global information on the timeline
TimeInterval timing_;
GstClockTime first_;
GstClockTime step_;
// main data structure containing list of gaps in the timeline
TimeIntervalSet gaps_;
TimeIntervalSet gaps_;
float gapsArray_[MAX_TIMELINE_ARRAY];
bool gaps_array_need_update_;
// synchronize data structures
void updateGapsFromArray(float *array, size_t array_size);
void fillArrayFromGaps(float *array, size_t array_size);
float fadingArray_[MAX_TIMELINE_ARRAY];
};

View File

@@ -1181,8 +1181,8 @@ void MediaController::Render()
static float timeline_zoom = 1.f;
const float width = ImGui::GetContentRegionAvail().x;
const float timeline_height = 2.f * (ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y);
const float segments_height = ImGui::GetFontSize();
const float slider_zoom_width = segments_height;
const float segments_height = timeline_height;
const float slider_zoom_width = segments_height / 2.f;
if (Settings::application.widget.media_player_view)
{
@@ -1267,45 +1267,58 @@ void MediaController::Render()
ImGui::PopButtonRepeat();
}
ImGui::SameLine(0, MAX(spacing * 4.f, width - 400.f) );
// loop modes button
ImGui::SameLine(0, spacing);
static int current_loop = 0;
static std::vector< std::pair<int, int> > iconsloop = { {0,15}, {1,15}, {19,14} };
current_loop = (int) mp_->loop();
if ( ImGuiToolkit::ButtonIconMultistate(iconsloop, &current_loop) )
mp_->setLoop( (MediaPlayer::LoopMode) current_loop );
// speed slider
ImGui::SameLine(0, MAX(spacing * 4.f, width - 400.f) );
float speed = static_cast<float>(mp_->playSpeed());
ImGui::SameLine(0, spacing);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 40.0);
if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, "Speed x %.1f", 2.f))
mp_->setPlaySpeed( static_cast<double>(speed) );
// reset
// Timeline popup menu
ImGui::SameLine(0, spacing);
if (ImGuiToolkit::ButtonIcon(11, 14)) {
timeline_zoom = 1.f;
speed = 1.f;
mp_->setPlaySpeed( static_cast<double>(speed) );
mp_->setLoop( MediaPlayer::LOOP_REWIND );
if (ImGuiToolkit::ButtonIcon(5, 8))
ImGui::OpenPopup("MenuTimeline");
if (ImGui::BeginPopup("MenuTimeline")) {
if (ImGui::Selectable( "Reset Speed" )){
speed = 1.f;
mp_->setPlaySpeed( static_cast<double>(speed) );
}
if (ImGui::Selectable( "Reset Timeline" )){
timeline_zoom = 1.f;
mp_->timeline()->clearFading();
mp_->timeline()->clearGaps();
}
ImGui::Separator();
static int smoothfactor = 10;
ImGui::SetNextItemWidth(100);
if (ImGui::Button( "Smooth Curve" )){
mp_->timeline()->smoothFading(smoothfactor);
}
ImGui::SameLine(0);
ImGui::SetNextItemWidth(100);
ImGui::DragInt("##smoothfactor", &smoothfactor, 5.f, 5, 50, "x %d");
static int milisec = 500;
ImGui::SetNextItemWidth(100);
if (ImGui::Button( "Auto Fading" )){
mp_->timeline()->autoFading(milisec);
mp_->timeline()->smoothFading(10);
}
ImGui::SameLine(0);
ImGui::SetNextItemWidth(100);
ImGui::DragInt("##milisecfading", &milisec, 100.f, 100, 2000, "%d ms");
ImGui::EndPopup();
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(3.f, 3.f));
// For controlling the current media player with timeline
static Timeline working_timeline;
static MediaPlayer *timeline_mp = nullptr;
static size_t array_size = 1200;
static float *array = (float *) malloc( sizeof(float) * array_size);
// detect change of media player to update the timeline array
if (timeline_mp != mp_ || !working_timeline.is_valid()) {
timeline_mp = mp_;
working_timeline = timeline_mp->timeline();
working_timeline.fillArrayFromGaps(array, array_size);
}
guint64 current_t = mp_->position();
guint64 seek_t = current_t;
@@ -1320,43 +1333,15 @@ void MediaController::Render()
ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), segments_height -1);
size.x *= timeline_zoom;
// draw position when entering
ImVec2 draw_pos = ImGui::GetCursorPos();
// start slider at a position which forces to change value
uint press_index = array_size;
bool pressed = ImGuiToolkit::InvisibleSliderInt("##TimelinePicking", &press_index, 0, array_size, size);
// behavior on action on array of segments
static bool active = false;
static float target_value = 0.f;
static uint starting_index = array_size;
if (pressed != active) {
active = pressed;
if (pressed) {
starting_index = press_index;
target_value = array[starting_index] > 0.f ? 0.f : 1.f;
}
else // action on released
{
// update the timeline
working_timeline.updateGapsFromArray(array, array_size);
// apply it to the media player
timeline_mp->setTimeline(working_timeline);
}
if ( ImGuiToolkit::EditPlotHistoLines("##TimelineArray", mp_->timeline()->gapsArray(),
mp_->timeline()->fadingArray(), MAX_TIMELINE_ARRAY, 0.f, 1.f, size) )
{
mp_->timeline()->update();
}
if (active) {
for (int i = MIN(starting_index, press_index); i < MAX(starting_index, press_index); ++i)
array[i] = target_value;
}
// back to drawing position to draw the segments data with historgram
ImGui::SetCursorPos(draw_pos);
ImGui::PlotHistogram("##TimelineHistogram", array, array_size, 0, NULL, 0.0f, 1.0f, size);
// custom timeline slider
slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, mp_->timeline().first(),
mp_->timeline().end(), mp_->timeline().step(), size.x);
slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, mp_->timeline()->first(),
mp_->timeline()->end(), mp_->timeline()->step(), size.x);
ImGui::PopStyleVar(2);
}
@@ -2377,7 +2362,7 @@ void ShowSandbox(bool* p_open)
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f);
ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), 40);
size.x *= 2.f;
size.x *= 1.f;
// // draw position when entering
// ImVec2 draw_pos = ImGui::GetCursorPos();

View File

@@ -1,6 +1,9 @@
#include "SystemToolkit.h"
#include "Log.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <tinyxml2.h>
#include "tinyxml2Toolkit.h"
using namespace tinyxml2;
@@ -81,6 +84,39 @@ void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::mat4 &matrix)
}
}
XMLElement *tinyxml2::XMLElementEncodeArray(XMLDocument *doc, void *array, uint64_t arraysize)
{
gchar *encoded_string = g_base64_encode( (guchar *) array, arraysize);
XMLElement *newelement = doc->NewElement( "array" );
newelement->SetAttribute("len", arraysize);
XMLText *text = doc->NewText( encoded_string );
newelement->InsertEndChild( text );
g_free(encoded_string);
return newelement;
}
bool tinyxml2::XMLElementDecodeArray(XMLElement *elem, void *array, uint64_t arraysize)
{
if ( !elem || std::string(elem->Name()).find("array") == std::string::npos )
return false;
uint64_t len = 0;
elem->QueryUnsigned64Attribute("len", &len);
if ( arraysize != len )
return false;
guchar *decoded_array = g_base64_decode(elem->GetText(), &len);
if ( arraysize != len )
return false;
memcpy(array, decoded_array, len);
return true;
}
bool tinyxml2::XMLSaveDoc(XMLDocument * const doc, std::string filename)
{
XMLDeclaration *pDec = doc->NewDeclaration();

View File

@@ -17,6 +17,8 @@ void XMLElementToGLM(XMLElement *elem, glm::vec3 &vector);
void XMLElementToGLM(XMLElement *elem, glm::vec4 &vector);
void XMLElementToGLM(XMLElement *elem, glm::mat4 &matrix);
XMLElement *XMLElementEncodeArray(XMLDocument *doc, void *array, uint64_t arraysize);
bool XMLElementDecodeArray(XMLElement *elem, void *array, uint64_t arraysize);
bool XMLSaveDoc(tinyxml2::XMLDocument * const doc, std::string filename);
bool XMLResultError(int result);