Add Flag functionality to ControlManager and InputMappingWindow with a new Flag SourceCallback. Fixed media player window control.

This commit is contained in:
brunoherbelin
2025-11-15 08:58:41 +01:00
parent 137f110e1d
commit 8d26f5d78a
14 changed files with 176 additions and 27 deletions

View File

@@ -970,6 +970,13 @@ bool Control::receiveSourceAttribute(Source *target, const std::string &attribut
}
target->setImageProcessingEnabled(on > 0.5f);
}
else if ( attribute.compare(OSC_SOURCE_FLAG) == 0) {
float f = -1.f;
if (!arguments.Eos()) {
arguments >> f >> osc::EndMessage;
}
target->call( new Flag( f ));
}
/// e.g. '/vimix/current/seek f 0.25' ; seek to 25% of duration
/// e.g. '/vimix/current/seek iiii 0 0 25 500' ; seek to time
else if ( attribute.compare(OSC_SOURCE_SEEK) == 0) {

View File

@@ -78,6 +78,7 @@
#define OSC_SOURCE_FILTER "/filter"
#define OSC_SOURCE_UNIFORM "/uniform"
#define OSC_SOURCE_BLENDING "/blending"
#define OSC_SOURCE_FLAG "/flag"
#define OSC_SESSION "/session"
#define OSC_SESSION_VERSION "/version"

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "MediaSource.h"
#include <string>
#include <regex>
@@ -39,6 +40,7 @@
#include "SourceCallback.h"
#include "ControlManager.h"
#include "Metronome.h"
#include "MediaPlayer.h"
#include "InputMappingWindow.h"
@@ -119,9 +121,9 @@ Target InputMappingWindow::ComboSelectTarget(const Target &current)
return selected;
}
uint InputMappingWindow::ComboSelectCallback(uint current, bool imageprocessing)
uint InputMappingWindow::ComboSelectCallback(uint current, bool imageprocessing, bool ismediaplayer)
{
const char* callback_names[23] = { "Select",
const char* callback_names[24] = { "Select",
ICON_FA_BULLSEYE " Alpha",
ICON_FA_BULLSEYE " Loom",
ICON_FA_OBJECT_UNGROUP " Geometry",
@@ -133,6 +135,7 @@ uint InputMappingWindow::ComboSelectCallback(uint current, bool imageprocessing)
ICON_FA_PLAY_CIRCLE " Speed",
ICON_FA_PLAY_CIRCLE " Fast forward",
ICON_FA_PLAY_CIRCLE " Seek",
ICON_FA_PLAY_CIRCLE " Flag",
" None",
" None",
" None",
@@ -148,7 +151,8 @@ uint InputMappingWindow::ComboSelectCallback(uint current, bool imageprocessing)
uint selected = 0;
if (ImGui::BeginCombo("##ComboSelectCallback", callback_names[current]) ) {
for (uint i = SourceCallback::CALLBACK_ALPHA; i <= SourceCallback::CALLBACK_SEEK; ++i){
for (uint i = SourceCallback::CALLBACK_ALPHA;
i <= (ismediaplayer ? SourceCallback::CALLBACK_FLAG : SourceCallback::CALLBACK_PLAY) ; ++i){
if ( ImGui::Selectable( callback_names[i]) ) {
selected = i;
}
@@ -488,6 +492,32 @@ void InputMappingWindow::SliderParametersCallback(SourceCallback *callback, cons
}
break;
case SourceCallback::CALLBACK_FLAG:
{
Flag *edited = static_cast<Flag*>(callback);
ImGuiToolkit::Indication(press_tooltip[0], 2, 13);
ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
int max = -1;
if (Source * const* v = std::get_if<Source *>(&target)) {
MediaSource *ms = dynamic_cast<MediaSource*>(*v);
if (ms)
max = ms->mediaplayer()->timeline()->numFlags() - 1;
}
int val = MIN( (int) edited->value(), max);
ImGui::SetNextItemWidth(right_align);
ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
if (ImGui::SliderInt("##CALLBACK_PLAY_FLAG", &val, -1, max, val < 0 ? "Next Flag" : "Flag <%d>"))
edited->setValue(val );
ImGui::SameLine(0, IMGUI_SAME_LINE / 3);
ImGuiToolkit::Indication("Flag to jump to in a video source.", 12, 6);
}
break;
case SourceCallback::CALLBACK_BRIGHTNESS:
{
SetBrightness *edited = static_cast<SetBrightness*>(callback);
@@ -1362,17 +1392,19 @@ void InputMappingWindow::Render()
}
// check if target is a Source with image processing enabled
bool ismediaplayer = false;
bool withimageprocessing = false;
if ( target.index() == 1 ) {
if (Source * const* v = std::get_if<Source *>(&target)) {
withimageprocessing = (*v)->imageProcessingEnabled();
ismediaplayer = dynamic_cast<MediaSource*>(*v) != nullptr;
}
}
// Select Reaction
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImGui::SetNextItemWidth(w);
uint type = ComboSelectCallback( callback->type(), withimageprocessing );
uint type = ComboSelectCallback( callback->type(), withimageprocessing, ismediaplayer );
if (type > 0) {
// remove previous callback
S->deleteInputCallback(callback);
@@ -1431,16 +1463,18 @@ void InputMappingWindow::Render()
// possible new target
if (temp_new_target.index() > 0) {
// check if target is a Source with image processing enabled
bool mediaplayer = false;
bool withimageprocessing = false;
if ( temp_new_target.index() == 1 ) {
if (Source * const* v = std::get_if<Source *>(&temp_new_target)) {
withimageprocessing = (*v)->imageProcessingEnabled();
mediaplayer = dynamic_cast<MediaSource*>(*v) != nullptr;
}
}
// step 3: Get input for callback type
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImGui::SetNextItemWidth(w);
temp_new_callback = ComboSelectCallback( temp_new_callback, withimageprocessing );
temp_new_callback = ComboSelectCallback( temp_new_callback, withimageprocessing, mediaplayer );
// user selected a callback type
if (temp_new_callback > 0) {
// step 4 : create new callback and add it to source

View File

@@ -16,7 +16,7 @@ class InputMappingWindow : public WorkspaceWindow
uint current_input_;
Target ComboSelectTarget(const Target &current);
uint ComboSelectCallback(uint current, bool imageprocessing);
uint ComboSelectCallback(uint current, bool imageprocessing, bool mediaplayer);
void SliderParametersCallback(SourceCallback *callback, const Target &target);
public:

View File

@@ -1134,6 +1134,9 @@ bool MediaPlayer::go_to(GstClockTime pos)
if (ABS_DIFF (position_, jumpPts) > 2 * timeline_.step() ) {
ret = true;
seek( jumpPts );
// Revert loop status to default
loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT;
}
}
return ret;
@@ -1614,10 +1617,14 @@ bool MediaPlayer::go_to_flag(TimeInterval flag)
current_flag_ = flag;
// change timeline status accordingly
if ( flag.type == (int) LoopStatus::LOOP_STATUS_BLACKOUT)
if ( flag.type == (int) LoopStatus::LOOP_STATUS_BLACKOUT) {
loop_status_ = LoopStatus::LOOP_STATUS_BLACKOUT;
else if ( flag.type == (int) LoopStatus::LOOP_STATUS_STOPPED)
loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT;
execute_play_command(false);
}
else if ( flag.type == (int) LoopStatus::LOOP_STATUS_STOPPED) {
loop_status_ = LoopStatus::LOOP_STATUS_STOPPED;
execute_play_command(false);
}
}
}
return ret;

View File

@@ -1703,6 +1703,12 @@ void SessionLoader::visit (Seek &c)
xmlCurrent_->QueryBoolAttribute("bidirectional", &b);
c.setBidirectional(b);
}
void SessionLoader::visit (Flag &c)
{
int v = -1;
xmlCurrent_->QueryIntAttribute("value", &v);
c.setValue(v);
}
void SessionLoader::visit (SetAlpha &c)
{

View File

@@ -93,6 +93,7 @@ public:
void visit (Play&) override;
void visit (PlayFastForward&) override;
void visit (Seek&) override;
void visit (Flag&) override;
static void XMLToNode(const tinyxml2::XMLElement *xml, Node &n);
static void XMLToSourcecore(tinyxml2::XMLElement *xml, SourceCore &s);

View File

@@ -1058,6 +1058,11 @@ void SessionVisitor::visit (PlayFastForward &c)
xmlCurrent_->SetAttribute("duration", c.duration());
}
void SessionVisitor::visit (Flag &c)
{
xmlCurrent_->SetAttribute("value", (int) c.value());
}
void SessionVisitor::visit (Seek &c)
{
xmlCurrent_->SetAttribute("value", (uint64_t) c.value());

View File

@@ -99,6 +99,7 @@ public:
void visit (Play&) override;
void visit (PlayFastForward&) override;
void visit (Seek&) override;
void visit (Flag&) override;
static tinyxml2::XMLElement *NodeToXML(const Node &n, tinyxml2::XMLDocument *doc);
static tinyxml2::XMLElement *ImageToXML(const FrameBufferImage *img, tinyxml2::XMLDocument *doc);

View File

@@ -72,6 +72,9 @@ SourceCallback *SourceCallback::create(CallbackType type)
case SourceCallback::CALLBACK_SEEK:
loadedcallback = new Seek;
break;
case SourceCallback::CALLBACK_FLAG:
loadedcallback = new Flag;
break;
case SourceCallback::CALLBACK_REPLAY:
loadedcallback = new RePlay;
break;
@@ -718,6 +721,73 @@ void Seek::accept(Visitor& v)
v.visit(*this);
}
Flag::Flag(int target)
: SourceCallback()
, flag_index_(target)
{}
void Flag::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
// access media player if target source is a media source
MediaSource *ms = dynamic_cast<MediaSource *>(s);
if (ms != nullptr) {
// can operate on flags if there are some
int num = ms->mediaplayer()->timeline()->numFlags();
if (num > 1) {
// default flag index is -1 to mean next flag
if (flag_index_ < 0) {
GstClockTime _time = ms->mediaplayer()->position();
if( ms->mediaplayer()->playSpeed() < 0 ) {
// Go to previous flag when playing backward
TimeInterval target_flag = ms->mediaplayer()->timeline()->getPreviousFlag( _time );
bool has_prev = target_flag.is_valid() &&
( ms->mediaplayer()->loop() == MediaPlayer::LOOP_REWIND || (target_flag.end < _time) );
if( has_prev)
ms->mediaplayer()->go_to_flag( target_flag );
}
else {
// go to next flag when playing forward
TimeInterval target_flag = ms->mediaplayer()->timeline()->getNextFlag( _time );
bool has_next = target_flag.is_valid() &&
( ms->mediaplayer()->loop() == MediaPlayer::LOOP_REWIND || (target_flag.begin > _time) );
if( has_next )
ms->mediaplayer()->go_to_flag( target_flag );
}
}
else if (flag_index_ < num) {
int index = 0;
const TimeIntervalSet flags = ms->mediaplayer()->timeline()->flags();
for (const auto &flag_Interval : flags) {
if ( index == flag_index_) {
ms->mediaplayer()->go_to_flag( flag_Interval );
break;
}
++index;
}
}
}
}
status_ = FINISHED;
}
SourceCallback *Flag::clone() const
{
return new Flag(flag_index_);
}
void Flag::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
SetGeometry::SetGeometry(const Group *g, float ms, bool revert) : SourceCallback(),
duration_(ms), bidirectional_(revert)
{

View File

@@ -40,6 +40,7 @@ public:
CALLBACK_PLAYSPEED,
CALLBACK_PLAYFFWD,
CALLBACK_SEEK,
CALLBACK_FLAG,
CALLBACK_REPLAY,
CALLBACK_RESETGEO,
CALLBACK_LOCK,
@@ -295,6 +296,22 @@ public:
void accept (Visitor& v) override;
};
class Flag : public SourceCallback
{
int flag_index_;
public:
Flag (int target = -1);
int value () const { return flag_index_; }
void setValue (int t) { flag_index_ = t; }
void update (Source *s, float) override;
SourceCallback *clone() const override;
CallbackType type () const override { return CALLBACK_FLAG; }
void accept (Visitor& v) override;
};
class ResetGeometry : public SourceCallback
{
public:

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glib.h>
#include <iostream>
#include <iomanip>
#include <thread>
@@ -946,6 +947,7 @@ bool TimelineSlider (const char* label, guint64 *time, TimeInterval *flag, Timel
//
// FLAGS
//
int index = 0;
bool flag_pressed = false;
const TimeIntervalSet flags = tl->flags();
for (const auto &flag_Interval : flags) {
@@ -972,7 +974,9 @@ bool TimelineSlider (const char* label, guint64 *time, TimeInterval *flag, Timel
// show time when hovering
if (hovered)
ImGui::SetTooltip(" %s ", GstToolkit::time_to_string(flag_time).c_str());
ImGui::SetTooltip(" <%d> %s ", index, GstToolkit::time_to_string(flag_time).c_str());
++index;
}
//
@@ -2305,7 +2309,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
// flag buttons
if (rendersize.x > buttons_height_ * 6.0f) {
if ( !mediaplayer_mode_ && mediaplayer_active_->timeline()->numFlags() > 0 ) {
if ( mediaplayer_active_->timeline()->numFlags() > 0 ) {
GstClockTime _paused_time = mediaplayer_active_->position();
@@ -2317,7 +2321,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
!( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) &&
( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.end < _paused_time) );
if( ImGuiToolkit::ButtonIcon(6, 0, "Go to previous flag", has_prev) )
mediaplayer_active_->go_to_flag( target_flag );
seek_flag = target_flag;
}
else {
// go to next flag when playing forward
@@ -2326,11 +2330,11 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
!( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) &&
( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.begin > _paused_time) );
if( ImGuiToolkit::ButtonIcon(5, 0, "Go to next flag", has_next) )
mediaplayer_active_->go_to_flag( target_flag );
seek_flag = target_flag;
}
// if stopped at a flag, show flag menu
if (mediaplayer_active_->currentFlag().is_valid()) {
if (!mediaplayer_mode_ && mediaplayer_active_->currentFlag().is_valid()) {
ImGui::SameLine(0, h_space_);
if (ImGuiToolkit::IconButton(3, 0) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
counter_menu_timeout=0;
@@ -2396,23 +2400,18 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
// play/stop command should be following the playing mode (buttons)
// AND force to stop when the slider is pressed
bool media_play = mediaplayer_mode_ & (!mediaplayer_slider_pressed_);
// Flag pressed in timeline
if (seek_flag.is_valid()) {
// go to the flag position
if ( mediaplayer_active_->go_to_flag(seek_flag) ){
// stop if flag type is 'Stop' (1) or 'Blackout' (2)
if (seek_flag.type > 0)
media_play = false;
}
}
bool media_play = mediaplayer_mode_ & (!mediaplayer_slider_pressed_);
// apply play action to media only if status should change
if ( mediaplayer_active_->isPlaying() != media_play ) {
mediaplayer_active_->play( media_play );
}
// Flag pressed in timeline
if (seek_flag.is_valid()) {
// go to the flag position
mediaplayer_active_->go_to_flag(seek_flag);
}
}
else {
///

View File

@@ -76,6 +76,7 @@ public:
virtual void visit (class Play&) {}
virtual void visit (class PlayFastForward&) {}
virtual void visit (class Seek&) {}
virtual void visit (class Flag&) {}
};

View File

@@ -111,8 +111,8 @@
#define IMGUI_LABEL_RECENT_FILES " Recent files"
#define IMGUI_LABEL_RECENT_RECORDS " Recent recordings"
#define IMGUI_RIGHT_ALIGN -3.8f * ImGui::GetTextLineHeightWithSpacing()
#define IMGUI_SAME_LINE 8
#define IMGUI_TOP_ALIGN 10
#define IMGUI_SAME_LINE 8.f
#define IMGUI_TOP_ALIGN 10.f
#define IMGUI_COLOR_OVERLAY IM_COL32(5, 5, 5, 150)
#define IMGUI_COLOR_LIGHT_OVERLAY IM_COL32(5, 5, 5, 50)
#define IMGUI_COLOR_CAPTURE 1.0, 0.55, 0.05