New MediaPlayer image with timeline

Enable playing and seeking into a timeline on a media player that loaded an image. Timeline sets a duration (end) and is saved/loaded. Add a gstreamer imagefreeze element in the pipeline to simulate a playable stream. Distinction must be made between 'isImage' (what was loaded) and 'singleFrame' (what is in the pipeline). GUI is added and customized with menu and dialog.
This commit is contained in:
Bruno Herbelin
2023-08-10 00:46:48 +02:00
parent 1d329600af
commit 4efaa1f350
13 changed files with 299 additions and 105 deletions

View File

@@ -204,8 +204,28 @@ void ImGuiToolkit::Icon(int i, int j, bool enabled)
ImGui::Image((void*)(intptr_t)textureicons, ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing()), uv0, uv1, tint_color);
}
bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip)
bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip, bool expanded)
{
ImGuiContext& g = *GImGui;
bool ret = false;
if (expanded) {
// make some space
char space_buf[] = " ";
const ImVec2 space_size = ImGui::CalcTextSize(" ", NULL);
const int space_num = static_cast<int>( ceil(g.FontSize / space_size.x) );
space_buf[space_num]='\0';
char text_buf[256];
ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%s %s", space_buf, tooltip);
ImVec2 draw_pos = ImGui::GetCursorScreenPos() + g.Style.FramePadding * 0.5;
ret = ImGui::Button(text_buf);
// overlay of icon on top of first item
_drawIcon(draw_pos, i, j);
}
else {
// icons.dds is a 20 x 20 grid of icons
if (textureicons == 0)
textureicons = Resource::getTextureDDS("images/icons.dds");
@@ -214,14 +234,14 @@ bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip)
ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 );
ImGui::PushID( i*20 + j);
ImGuiContext& g = *GImGui;
bool ret = ImGui::ImageButton((void*)(intptr_t)textureicons,
ret = ImGui::ImageButton((void*)(intptr_t)textureicons,
ImVec2(g.FontSize, g.FontSize),
uv0, uv1, g.Style.FramePadding.y);
ImGui::PopID();
if (tooltip != nullptr && ImGui::IsItemHovered())
ImGuiToolkit::ToolTip(tooltip);
}
return ret;
}

View File

@@ -24,7 +24,7 @@ namespace ImGuiToolkit
void ShowIconsWindow(bool* p_open);
// buttons and gui items with icon
bool ButtonIcon (int i, int j, const char* tooltip = nullptr);
bool ButtonIcon (int i, int j, const char* tooltip = nullptr, bool expanded = false);
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltip = nullptr);
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state, std::vector<std::string> tooltips);
bool MenuItemIcon (int i, int j, const char* label, const char* shortcut = nullptr, bool selected = false, bool enabled = true);

View File

@@ -98,14 +98,14 @@ void InfoVisitor::visit(MediaPlayer &mp)
oss << SystemToolkit::filename(mp.filename()) << std::endl;
oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(" (,")) << ", ";
oss << mp.width() << " x " << mp.height();
if (!mp.isImage() && mp.frameRate() > 0.)
if (!mp.singleFrame() && mp.frameRate() > 0.)
oss << ", " << std::fixed << std::setprecision(0) << mp.frameRate() << " fps";
}
else {
oss << mp.filename() << std::endl;
oss << mp.media().codec_name << std::endl;
oss << mp.width() << " x " << mp.height() ;
if (!mp.isImage() && mp.frameRate() > 0.)
if (!mp.singleFrame() && mp.frameRate() > 0.)
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
}
}

View File

@@ -352,6 +352,13 @@ void MediaPlayer::execute_open()
video_filter_available_ = false;
}
// hack to simulate a playable and seekable stream with an image
if (media_.isimage && !singleFrame()) {
media_.seekable = true;
video_filter_ = "imagefreeze";
video_filter_available_ = false;
}
// Add a filter to playbin if provided
if ( !video_filter_.empty()) {
// try to create the pipeline element given
@@ -403,7 +410,7 @@ void MediaPlayer::execute_open()
callbacks.new_event = NULL;
#endif
callbacks.new_preroll = callback_new_preroll;
if (media_.isimage) {
if (singleFrame()) {
callbacks.eos = NULL;
callbacks.new_sample = NULL;
}
@@ -434,8 +441,8 @@ void MediaPlayer::execute_open()
return;
}
// in case discoverer failed to get duration
if (timeline_.end() == GST_CLOCK_TIME_NONE) {
// in case discoverer failed to get duration of a video
if (!media_.isimage && timeline_.end() == GST_CLOCK_TIME_NONE) {
gint64 d = GST_CLOCK_TIME_NONE;
if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) )
timeline_.setEnd(d);
@@ -445,7 +452,7 @@ void MediaPlayer::execute_open()
Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(),
SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(), media_.width, media_.height);
if (!isImage())
if (!singleFrame())
Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(),
timeline_.begin(), timeline_.end(), timeline_.numFrames(), timeline_.numGaps());
@@ -506,6 +513,12 @@ void MediaPlayer::execute_open()
description += std::to_string(media_.framerate_d) + " ! ";
}
// hack to simulate a playable and seekable stream with an image
if (media_.isimage && !singleFrame()) {
media_.seekable = true;
description += "imagefreeze ! ";
}
// set app sink
description += "queue ! appsink name=sink";
@@ -563,7 +576,7 @@ void MediaPlayer::execute_open()
callbacks.new_event = NULL;
#endif
callbacks.new_preroll = callback_new_preroll;
if (media_.isimage) {
if (singleFrame()) {
callbacks.eos = NULL;
callbacks.new_sample = NULL;
}
@@ -591,8 +604,8 @@ void MediaPlayer::execute_open()
return;
}
// in case discoverer failed to get duration
if (timeline_.end() == GST_CLOCK_TIME_NONE) {
// in case discoverer failed to get duration of a video
if (!media_.isimage && timeline_.end() == GST_CLOCK_TIME_NONE) {
gint64 d = GST_CLOCK_TIME_NONE;
if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) )
timeline_.setEnd(d);
@@ -602,7 +615,7 @@ void MediaPlayer::execute_open()
Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(),
SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(), media_.width, media_.height);
if (!isImage())
if (!singleFrame())
Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(),
timeline_.begin(), timeline_.end(), timeline_.numFrames(), timeline_.numGaps());
@@ -783,6 +796,11 @@ bool MediaPlayer::isImage() const
return media_.isimage;
}
bool MediaPlayer::singleFrame() const
{
return timeline_.end() == GST_CLOCK_TIME_NONE;
}
std::string MediaPlayer::decoderName()
{
if (decoder_name_.empty()) {
@@ -856,8 +874,8 @@ void MediaPlayer::execute_play_command(bool on)
void MediaPlayer::play(bool on)
{
// ignore if disabled, and cannot play an image
if (!enabled_ || media_.isimage || pending_)
// ignore if disabled, and cannot play a single frame
if (!enabled_ || pending_ || singleFrame())
return;
// Metronome
@@ -881,7 +899,7 @@ void MediaPlayer::play(bool on)
bool MediaPlayer::isPlaying(bool testpipeline) const
{
// image cannot play
if (media_.isimage)
if (singleFrame())
return false;
// if not ready yet, answer with requested state
@@ -1032,7 +1050,7 @@ void MediaPlayer::init_texture(guint index)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (!media_.isimage) {
if ( !singleFrame() ) {
// set pbo image size
pbo_size_ = media_.height * media_.width * 4;
@@ -1146,9 +1164,10 @@ void MediaPlayer::update()
if (media_.valid) {
if (!media_.log.empty())
Log::Info("'%s' : %s", uri().c_str(), media_.log.c_str());
if (!media_.isimage) {
timeline_.setEnd( media_.end );
timeline_.setStep( media_.dt );
}
execute_open();
}
else {
@@ -1163,7 +1182,7 @@ void MediaPlayer::update()
}
// prevent unnecessary updates: disabled or already filled image
if ( (!enabled_ && !force_update_) || (media_.isimage && textureindex_>0 ) )
if ( (!enabled_ && !force_update_) || (singleFrame() && textureindex_>0 ) )
return;
// local variables before trying to update
@@ -1222,10 +1241,15 @@ void MediaPlayer::update()
// do NOT do another seek yet
}
// otherwise check for need to seek (pipeline management)
else {
// manage timeline: test if position falls into a gap
else if (position_ != GST_CLOCK_TIME_NONE) {
// manage timeline:
TimeInterval gap;
if (position_ != GST_CLOCK_TIME_NONE && timeline_.getGapAt(position_, gap)) {
// ensure we remain within the begin to end range
if (position_ > timeline_.last() || position_ < timeline_.first()) {
need_loop = true;
}
// test if position falls into a gap
else if (timeline_.getGapAt(position_, gap)) {
// if in a gap, seek to next section
if (gap.is_valid()) {
// jump in one or the other direction
@@ -1339,7 +1363,7 @@ void MediaPlayer::setPlaySpeed(double s)
rate_ = SIGN(rate_) * MIN_PLAY_SPEED;
// discard if too early or not possible
if (media_.isimage || !media_.seekable || pipeline_ == nullptr)
if (pipeline_ == nullptr || !media_.seekable)
return;
//
@@ -1352,7 +1376,7 @@ void MediaPlayer::setPlaySpeed(double s)
// are GST_CLOCK_TIME_NONE, the playback direction does not change
// and the seek is not flushing. (Since: 1.18)
// if all conditions to use GST_SEEK_FLAG_INSTANT_RATE_CHANGE are met
if ( rate_change_ == RATE_CHANGE_INSTANT && !change_direction ) {
if ( rate_change_ == RATE_CHANGE_INSTANT && !change_direction && !media_.isimage) {
int seek_flags = GST_SEEK_FLAG_INSTANT_RATE_CHANGE;
seek_flags |= GST_SEEK_FLAG_TRICKMODE;

View File

@@ -119,6 +119,10 @@ public:
* True if its an image
* */
bool isImage() const;
/**
* True if it has only one frame
* */
bool singleFrame() const;
/**
* Pause / Play
* Can play backward if play speed is negative
@@ -270,7 +274,9 @@ public:
static std::list<MediaPlayer*> registered() { return registered_; }
static std::list<MediaPlayer*>::const_iterator begin() { return registered_.cbegin(); }
static std::list<MediaPlayer*>::const_iterator end() { return registered_.cend(); }
/**
* Discoverer to check uri and get media info
* */
static MediaInfo UriDiscoverer(const std::string &uri);
std::string log() const { return media_.log; }

View File

@@ -154,7 +154,7 @@ void MediaSource::play (bool on)
bool MediaSource::playable () const
{
return !mediaplayer_->isImage();
return !mediaplayer_->singleFrame();
}
void MediaSource::replay ()

View File

@@ -851,7 +851,21 @@ void SessionLoader::visit(MediaPlayer &n)
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
if (timelineelement) {
Timeline tl;
tl.setTiming( n.timeline()->interval(), n.timeline()->step());
TimeInterval interval_(n.timeline()->interval());
if (interval_.is_valid())
tl.setTiming( interval_, n.timeline()->step());
else {
GstClockTime b = GST_CLOCK_TIME_NONE;
GstClockTime e = GST_CLOCK_TIME_NONE;
GstClockTime s = GST_CLOCK_TIME_NONE;
timelineelement->QueryUnsigned64Attribute("begin", &b);
timelineelement->QueryUnsigned64Attribute("end", &e);
timelineelement->QueryUnsigned64Attribute("step", &s);
interval_ = TimeInterval(b,e);
tl.setTiming( interval_, s);
}
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
if (gapselement) {
XMLElement* gap = gapselement->FirstChildElement("Interval");

View File

@@ -410,7 +410,7 @@ void SessionVisitor::visit(MediaPlayer &n)
XMLElement *newelement = xmlDoc_->NewElement("MediaPlayer");
newelement->SetAttribute("id", n.id());
if (!n.isImage()) {
if (!n.singleFrame()) {
newelement->SetAttribute("play", n.isPlaying());
newelement->SetAttribute("loop", (int) n.loop());
newelement->SetAttribute("speed", n.playSpeed());
@@ -421,6 +421,9 @@ void SessionVisitor::visit(MediaPlayer &n)
// timeline
XMLElement *timelineelement = xmlDoc_->NewElement("Timeline");
timelineelement->SetAttribute("begin", n.timeline()->begin());
timelineelement->SetAttribute("end", n.timeline()->end());
timelineelement->SetAttribute("step", n.timeline()->step());
// gaps in timeline
XMLElement *gapselement = xmlDoc_->NewElement("Gaps");

View File

@@ -573,7 +573,7 @@ void PlayFastForward::update(Source *s, float dt)
if (ms != nullptr) {
MediaPlayer *mp = ms->mediaplayer();
// works only if media player is enabled and valid
if (mp && mp->isEnabled() && !mp->isImage()) {
if (mp && mp->isEnabled() && !mp->singleFrame()) {
media_ = mp;
playspeed_ = media_->playSpeed();
}

View File

@@ -372,23 +372,14 @@ void SourceControlWindow::Render()
//
// Menu for video Mediaplayer control
//
if (ImGui::BeginMenu(ICON_FA_FILM " Video", mediaplayer_active_) )
if (ImGui::BeginMenu(ICON_FA_PHOTO_VIDEO " Media", mediaplayer_active_) )
{
if ( !mediaplayer_active_->singleFrame() ) {
if (ImGui::MenuItem( ICON_FA_REDO_ALT " Reload" ))
mediaplayer_active_->reopen();
if (ImGuiToolkit::MenuItemIcon(16, 16, "Gstreamer effect", nullptr,
false, mediaplayer_active_->videoEffectAvailable()) )
mediaplayer_edit_pipeline_ = true;
if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " On deactivation"))
{
bool option = !mediaplayer_active_->rewindOnDisabled();
if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option ))
mediaplayer_active_->setRewindOnDisabled(false);
option = mediaplayer_active_->rewindOnDisabled();
if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option ))
mediaplayer_active_->setRewindOnDisabled(true);
ImGui::EndMenu();
}
if (ImGui::BeginMenu(ICON_FA_MICROCHIP " Hardware decoding"))
{
bool hwdec = !mediaplayer_active_->softwareDecodingForced();
@@ -399,8 +390,35 @@ void SourceControlWindow::Render()
mediaplayer_active_->setSoftwareDecodingForced(true);
ImGui::EndMenu();
}
if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " On deactivation"))
{
bool option = !mediaplayer_active_->rewindOnDisabled();
if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option ))
mediaplayer_active_->setRewindOnDisabled(false);
option = mediaplayer_active_->rewindOnDisabled();
if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option ))
mediaplayer_active_->setRewindOnDisabled(true);
ImGui::EndMenu();
}
ImGui::Separator();
ImGui::TextDisabled("Timeline");
if ( mediaplayer_active_->isImage()) {
if ( ImGuiToolkit::MenuItemIcon(1, 14, "Remove")){
// set empty timeline
Timeline tl;
mediaplayer_active_->setTimeline(tl);
mediaplayer_active_->play(false);
// re-open the image with NO timeline
mediaplayer_active_->reopen();
mediaplayer_active_ = nullptr;
}
if ( ImGui::MenuItem(ICON_FA_HOURGLASS_HALF " Duration")){
mediaplayer_set_duration_ = true;
}
}
if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset")){
mediaplayer_timeline_zoom_ = 1.f;
mediaplayer_active_->timeline()->clearFading();
@@ -428,6 +446,12 @@ void SourceControlWindow::Render()
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE);
ImGui::EndMenu();
}
}
else {
if (ImGui::MenuItem( ICON_FA_REDO_ALT " Reload" ))
mediaplayer_active_->reopen();
}
ImGui::EndMenu();
}
@@ -640,7 +664,7 @@ void SourceControlWindow::RenderSelection(size_t i)
for (auto source = selection_.begin(); source != selection_.end(); ++source) {
// collect durations of all media sources
MediaSource *ms = dynamic_cast<MediaSource *>(*source);
if (ms != nullptr && !ms->mediaplayer()->isImage())
if (ms != nullptr && !ms->mediaplayer()->singleFrame())
durations.push_back(static_cast<guint64>(static_cast<double>(ms->mediaplayer()->timeline()->sectionsDuration()) / fabs(ms->mediaplayer()->playSpeed())));
// compute the displayed width of frames of this source, and keep the max to align afterwards
float w = 1.5f * timeline_height_ * (*source)->frame()->aspectRatio();
@@ -677,7 +701,7 @@ void SourceControlWindow::RenderSelection(size_t i)
// get media source
MediaSource *ms = dynamic_cast<MediaSource *>(*source);
if (ms == nullptr || ms->mediaplayer()->isImage()) {
if (ms == nullptr || ms->mediaplayer()->singleFrame()) {
// leave the source in the list and continue
++source;
continue;
@@ -1284,7 +1308,7 @@ void SourceControlWindow::RenderSelectedSources()
std::string label(ICON_FA_PLUS_CIRCLE);
if (space > buttons_width_) { // enough space to show full button with label text
label += LABEL_STORE_SELECTION;
width = buttons_width_;
width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing();
}
ImGui::SameLine(0, space -width);
ImGui::SetNextItemWidth(width);
@@ -1385,6 +1409,47 @@ void SourceControlWindow::RenderSingleSource(Source *s)
/// Play button bar
///
DrawButtonBar(bottom, rendersize.x);
// If possibly a media source, but is not playable
// then offer to make it playable by adding a timeline
if ( ms != nullptr )
{
if (ms->mediaplayer()->isImage()) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
float space = ImGui::GetContentRegionAvail().x;
float width = buttons_height_ ;
if (space > buttons_width_)
width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing();
ImGui::SameLine(0, space -width);
ImGui::SetNextItemWidth(width);
if (ImGuiToolkit::ButtonIcon( 0, 14, LABEL_ADD_TIMELINE, space > buttons_width_ )) {
// activate mediaplayer
mediaplayer_active_ = ms->mediaplayer();
// set timeline to default 1 second
Timeline tl;
TimeInterval interval(0, GST_SECOND);
// set fixed framerate to 25 FPS
tl.setTiming( interval, 40 * GST_MSECOND);
mediaplayer_active_->setTimeline(tl);
// set to play
mediaplayer_active_->play(true);
// re-open the image with a timeline
mediaplayer_active_->reopen();
// open dialog to set duration
mediaplayer_set_duration_ = true;
}
ImGui::PopStyleColor(2);
}
}
}
}
@@ -1746,6 +1811,9 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
}
///
/// Dialog to edit timeline fade in and out
///
if (mediaplayer_edit_fading_) {
ImGui::OpenPopup(LABEL_EDIT_FADING);
mediaplayer_edit_fading_ = false;
@@ -1824,11 +1892,14 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::EndPopup();
}
///
/// Dialog to set gstreamer video effect
///
static std::string _effect_description = "";
static bool _effect_description_changed = false;
if (mediaplayer_edit_pipeline_) {
// open dialog
ImGui::OpenPopup("Gstreamer Video effect");
ImGui::OpenPopup(DIALOG_GST_EFFECT);
mediaplayer_edit_pipeline_ = false;
// initialize dialog
_effect_description = mediaplayer_active_->videoEffect();
@@ -1838,7 +1909,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::SetNextWindowSize(mpp_dialog_size, ImGuiCond_Always);
const ImVec2 mpp_dialog_pos = top + rendersize * 0.5f - mpp_dialog_size * 0.5f;
ImGui::SetNextWindowPos(mpp_dialog_pos, ImGuiCond_Always);
if (ImGui::BeginPopupModal("Gstreamer Video effect", NULL, ImGuiWindowFlags_NoResize))
if (ImGui::BeginPopupModal(DIALOG_GST_EFFECT, NULL, ImGuiWindowFlags_NoResize))
{
const ImVec2 pos = ImGui::GetCursorPos();
const ImVec2 area = ImGui::GetContentRegionAvail();
@@ -1970,6 +2041,58 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
////
/// Dialog to set timeline duration
///
static double timeline_duration_ = 0.0;
if (mediaplayer_set_duration_) {
mediaplayer_set_duration_ = false;
// open dialog
if (mediaplayer_active_) {
// get current duration of mediaplayer
GstClockTime end = mediaplayer_active_->timeline()->end();
timeline_duration_ = (double) ( GST_TIME_AS_MSECONDS(end) ) / 1000.f;
// open dialog to change duration
ImGui::OpenPopup(DIALOG_TIMELINE_DURATION);
}
}
const ImVec2 tld_dialog_size(buttons_width_ * 2.f, buttons_height_ * 4);
ImGui::SetNextWindowSize(tld_dialog_size, ImGuiCond_Always);
const ImVec2 tld_dialog_pos = top + rendersize * 0.5f - tld_dialog_size * 0.5f;
ImGui::SetNextWindowPos(tld_dialog_pos, ImGuiCond_Always);
if (ImGui::BeginPopupModal(DIALOG_TIMELINE_DURATION, NULL, ImGuiWindowFlags_NoResize))
{
const ImVec2 pos = ImGui::GetCursorPos();
const ImVec2 area = ImGui::GetContentRegionAvail();
ImGui::Spacing();
ImGui::Text("Set the duration of the timeline");
ImGui::Spacing();
// get current timeline
Timeline tl = *mediaplayer_active_->timeline();
ImGui::InputDouble("second", &timeline_duration_, 1.0f, 10.0f, "%.2f");
timeline_duration_ = ABS(timeline_duration_);
bool close = false;
ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0)))
close = true;
ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0))
|| ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
// change timeline end
mediaplayer_active_->timeline()->setEnd( GST_MSECOND * (GstClockTime) ( timeline_duration_ * 1000.f ) );
// close dialog
close = true;
}
ImGui::PopStyleColor(1);
if (close)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
void SourceControlWindow::DrawButtonBar(ImVec2 bottom, float width)

View File

@@ -48,6 +48,7 @@ class SourceControlWindow : public WorkspaceWindow
// Render a single media player
MediaPlayer *mediaplayer_active_;
bool mediaplayer_edit_fading_;
bool mediaplayer_set_duration_;
bool mediaplayer_edit_pipeline_;
bool mediaplayer_mode_;
bool mediaplayer_slider_pressed_;

View File

@@ -89,7 +89,7 @@ public:
* */
bool enabled() const;
/**
* True if its an image
* True if it has only one frame
* */
bool singleFrame() const;
/**

View File

@@ -217,5 +217,8 @@
#define LABEL_STORE_SELECTION " Create batch"
#define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out"
#define LABEL_VIDEO_SEQUENCE " Encode an image sequence"
#define LABEL_ADD_TIMELINE "Add timeline"
#define DIALOG_TIMELINE_DURATION ICON_FA_HOURGLASS_HALF " Set timeline duration"
#define DIALOG_GST_EFFECT "Gstreamer Video effect"
#endif // VMIX_DEFINES_H