diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp
index 22e68a3..3b4b6d8 100644
--- a/src/MediaPlayer.cpp
+++ b/src/MediaPlayer.cpp
@@ -17,6 +17,8 @@
* along with this program. If not, see .
**/
+#include
+
// Desktop OpenGL function loader
#include
@@ -37,6 +39,11 @@
#define DISCOVER_TIMOUT 15
+#if GST_VERSION_MAJOR > 0 && GST_VERSION_MINOR > 18
+#define USE_GST_PLAYBIN
+#endif
+
+
std::list MediaPlayer::registered_;
MediaPlayer::MediaPlayer()
@@ -57,9 +64,10 @@ MediaPlayer::MediaPlayer()
seeking_ = false;
rewind_on_disable_ = false;
force_software_decoding_ = false;
- force_basic_speedchange_ = false;
- decoder_name_ = "";
rate_ = 1.0;
+ rate_change_ = RATE_CHANGE_NONE;
+ decoder_name_ = "";
+ video_filter_ = "";
position_ = GST_CLOCK_TIME_NONE;
loop_ = LoopMode::LOOP_REWIND;
@@ -289,6 +297,165 @@ void MediaPlayer::reopen()
}
}
+#ifdef USE_GST_PLAYBIN
+
+// GstPlayFlags flags from playbin. It is the policy of GStreamer to
+// not publicly expose element-specific enums. That's why this
+// GstPlayFlags enum has been copied here.
+// https://gstreamer.freedesktop.org/documentation/playback/playsink.html?gi-language=c#GstPlayFlags
+typedef enum {
+ GST_PLAY_FLAG_VIDEO = 0x00000001,
+ GST_PLAY_FLAG_AUDIO = 0x00000002,
+ GST_PLAY_FLAG_TEXT = 0x00000004,
+ GST_PLAY_FLAG_VIS = 0x00000008,
+ GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010,
+ GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020,
+ GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040,
+ GST_PLAY_FLAG_DOWNLOAD = 0x00000080,
+ GST_PLAY_FLAG_BUFFERING = 0x00000100,
+ GST_PLAY_FLAG_DEINTERLACE = 0x00000200,
+ GST_PLAY_FLAG_SW_COLOR = 0x00000400,
+ GST_PLAY_FLAG_FORCE_FILTER = 0x00000800,
+ GST_PLAY_FLAG_SW_DECODER = 0x00001000
+} GstPlayFlags;
+
+//
+// Setup a media player using gstreamer playbin
+//
+void MediaPlayer::execute_open()
+{
+ // create playbin
+ pipeline_ = gst_element_factory_make ("playbin", std::to_string(id_).c_str());
+
+ // set uri of file to open
+ g_object_set ( G_OBJECT (pipeline_), "uri", uri_.c_str(), NULL);
+
+ // Get and modify playbin flags
+ gint flags;
+ // ENABLE ONLY VIDEO, NOT AUDIO AND TEXT SUBTITLES
+ flags = GST_PLAY_FLAG_VIDEO;
+ // ENABLE DEINTERLACING
+ if (media_.interlaced)
+ flags |= GST_PLAY_FLAG_DEINTERLACE;
+ // ENABLE/DISABLE FORCE SOFTWARE DECODING
+ if (force_software_decoding_ || media_.isimage) {
+ flags |= GST_PLAY_FLAG_SW_COLOR;
+ flags |= GST_PLAY_FLAG_SW_DECODER;
+ }
+ g_object_set( G_OBJECT (pipeline_), "flags", flags, NULL);
+
+ // hack to compensate for lack of PTS in gif animations
+ if (media_.codec_name.compare("image/gst-libav-gif") == 0)
+ video_filter_ = "videorate";
+
+ // Add a filter to playbin if provided
+ if ( !video_filter_.empty()) {
+ // try to create the pipeline element given
+ GError *error = NULL;
+ GstElement *effect = gst_parse_launch( video_filter_.c_str(), &error);
+ if (effect == NULL || error != NULL) {
+ if (error != NULL)
+ Log::Warning("MediaPlayer %s Error constructing filter %s:\n%s", std::to_string(id_).c_str(), video_filter_.c_str(), error->message);
+ g_clear_error (&error);
+ if (effect)
+ gst_object_unref(effect);
+ }
+ else {
+ g_object_set( G_OBJECT (pipeline_), "video-filter", effect, nullptr);
+ Log::Info("MediaPlayer %s Added filter '%s'", std::to_string(id_).c_str(), video_filter_.c_str());
+
+ }
+ }
+
+ // setup appsink
+ GstElement *sink = gst_element_factory_make ("appsink", NULL);
+ if (!sink) {
+ Log::Warning("MediaPlayer %s Could not configure sink", std::to_string(id_).c_str());
+ failed_ = true;
+ return;
+ }
+
+ // instruct the sink to send samples synched in time
+ gst_base_sink_set_sync (GST_BASE_SINK(sink), true);
+
+ // instruct sink to use the required caps
+ std::string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(media_.width) +
+ ",height=" + std::to_string(media_.height);
+ GstCaps *caps = gst_caps_from_string(capstring.c_str());
+ if (!gst_video_info_from_caps (&v_frame_video_info_, caps)) {
+ Log::Warning("MediaPlayer %s Could not configure video frame info", std::to_string(id_).c_str());
+ failed_ = true;
+ return;
+ }
+ gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
+
+ // Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
+ gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 5);
+ gst_app_sink_set_drop (GST_APP_SINK(sink), true);
+
+ // set the callbacks
+ GstAppSinkCallbacks callbacks;
+#if GST_VERSION_MINOR > 18 && GST_VERSION_MAJOR > 0
+ callbacks.new_event = NULL;
+#endif
+ callbacks.new_preroll = callback_new_preroll;
+ if (media_.isimage) {
+ callbacks.eos = NULL;
+ callbacks.new_sample = NULL;
+ }
+ else {
+ callbacks.eos = callback_end_of_stream;
+ callbacks.new_sample = callback_new_sample;
+ }
+ gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL);
+ gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false);
+
+ // set playbin sink
+ g_object_set ( G_OBJECT (pipeline_), "video-sink", sink, NULL);
+
+ // done with ref to sink
+ gst_object_unref (sink);
+ gst_caps_unref (caps);
+
+#ifdef USE_GST_OPENGL_SYNC_HANDLER
+ // capture bus signals to force a unique opengl context for all GST elements
+ Rendering::LinkPipeline(GST_PIPELINE (pipeline_));
+#endif
+
+ // set to desired state (PLAY or PAUSE)
+ GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ Log::Warning("MediaPlayer %s Could not open '%s'", std::to_string(id_).c_str(), uri_.c_str());
+ failed_ = true;
+ return;
+ }
+
+ // in case discoverer failed to get duration
+ if (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);
+ }
+
+ // all good
+ 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())
+ 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());
+
+ opened_ = true;
+
+ // register media player
+ MediaPlayer::registered_.push_back(this);
+}
+
+#else
+
+//
+// Setup a media player using gstreamer custom pipeline
+//
void MediaPlayer::execute_open()
{
// Create gstreamer pipeline :
@@ -298,9 +465,6 @@ void MediaPlayer::execute_open()
std::string description = "uridecodebin name=decoder uri=" + uri_ + " ! ";
// NB: queue adds some control over the buffer, thereby limiting the frame delay. zero size means no buffering
-// string description = "uridecodebin name=decoder uri=" + uri_ + " decoder. ! ";
-// description += "audioconvert ! autoaudiosink decoder. ! ";
-
// video deinterlacing method (if media is interlaced)
// tomsmocomp (0) – Motion Adaptive: Motion Search
// greedyh (1) – Motion Adaptive: Advanced Detection
@@ -311,8 +475,11 @@ void MediaPlayer::execute_open()
if (media_.interlaced)
description += "deinterlace method=2 ! ";
- // queue
- description += "queue max-size-time=0 name=prev ! ";
+ // Add a filter to playbin if provided
+ if ( !video_filter_.empty()) {
+ description += "videoconvert chroma-resampler=1 dither=0 ! "; // fast
+ description += video_filter_ + " ! ";
+ }
// video convertion algorithm (should only do colorspace conversion, no scaling)
// chroma-resampler:
@@ -326,7 +493,7 @@ void MediaPlayer::execute_open()
// Dither with floyd-steinberg error diffusion 2
// Dither with Sierra Lite error diffusion 3
// ordered dither using a bayer pattern 4 (default)
- description += "videoconvert chroma-resampler=1 dither=0 name=post ! "; // fast
+ description += "videoconvert chroma-resampler=1 dither=0 ! "; // fast
// hack to compensate for lack of PTS in gif animations
if (media_.codec_name.compare("image/gst-libav-gif") == 0){
@@ -347,20 +514,14 @@ void MediaPlayer::execute_open()
failed_ = true;
return;
}
+#ifdef MEDIA_PLAYER_DEBUG
+ Log::Info("MediaPlayer %s Pipeline [%s]", std::to_string(id_).c_str(), description.c_str());
+#endif
+
// setup pipeline
g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL);
gst_pipeline_set_auto_flush_bus( GST_PIPELINE(pipeline_), true);
- // GstCaps *caps = gst_static_caps_get (&frame_render_caps);
- std::string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(media_.width) +
- ",height=" + std::to_string(media_.height);
- GstCaps *caps = gst_caps_from_string(capstring.c_str());
- if (!gst_video_info_from_caps (&v_frame_video_info_, caps)) {
- Log::Warning("MediaPlayer %s Could not configure video frame info", std::to_string(id_).c_str());
- failed_ = true;
- return;
- }
-
// setup software decode
if (force_software_decoding_ || media_.isimage) {
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "decoder")), "force-sw-decoders", true, NULL);
@@ -378,6 +539,14 @@ void MediaPlayer::execute_open()
gst_base_sink_set_sync (GST_BASE_SINK(sink), true);
// instruct sink to use the required caps
+ std::string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(media_.width) +
+ ",height=" + std::to_string(media_.height);
+ GstCaps *caps = gst_caps_from_string(capstring.c_str());
+ if (!gst_video_info_from_caps (&v_frame_video_info_, caps)) {
+ Log::Warning("MediaPlayer %s Could not configure video frame info", std::to_string(id_).c_str());
+ failed_ = true;
+ return;
+ }
gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
@@ -439,98 +608,29 @@ void MediaPlayer::execute_open()
MediaPlayer::registered_.push_back(this);
}
+#endif
-bool MediaPlayer::setEffect(const std::string &pipeline_element)
+
+void MediaPlayer::setVideoEffect(const std::string &pipeline_element)
{
- if ( pipeline_ == nullptr )
- return false;
-
- GstElement *prev = gst_bin_get_by_name (GST_BIN (pipeline_), "prev");
- if (!prev) {
- Log::Warning("MediaPlayer %s Could not find prev", std::to_string(id_).c_str());
- return false;
- }
- GstElement *post = gst_bin_get_by_name (GST_BIN (pipeline_), "post");
- if (!post) {
- Log::Warning("MediaPlayer %s Could not find post", std::to_string(id_).c_str());
- return false;
- }
-
- // try to create the pipeline element given
- GError *error = NULL;
- std::string elem = pipeline_element + " name=effect ";
- GstElement *effect = gst_parse_launch( elem.c_str(), &error);
- if (error != NULL) {
- Log::Warning("MediaPlayer %s Error constructing %s:\n%s", std::to_string(id_).c_str(), pipeline_element.c_str(), error->message);
+ if ( !pipeline_element.empty() ) {
+ GError *error = NULL;
+ GstElement *effect = gst_parse_launch( pipeline_element.c_str(), &error);
+ if (effect == NULL || error != NULL) {
+ Log::Warning("MediaPlayer %s Error constructing %s:\n%s", std::to_string(id_).c_str(), video_filter_.c_str(), error->message);
+ g_clear_error (&error);
+ if (effect)
+ gst_object_unref(effect);
+ return ;
+ }
+ // cleanup
g_clear_error (&error);
- return false;
- }
- if (effect == NULL) {
- Log::Warning("MediaPlayer %s Could not parse %s", std::to_string(id_).c_str(), pipeline_element.c_str());
- return false;
+ if (effect)
+ gst_object_unref(effect);
}
- // try to create the pipeline converter needed
- elem = "videoconvert name=conv ";
- GstElement *conv = gst_parse_launch( elem.c_str(), &error);
- if (error != NULL) {
- Log::Warning("MediaPlayer %s Error constructing %s:\n%s", std::to_string(id_).c_str(), elem.c_str(), error->message);
- g_clear_error (&error);
- return false;
- }
- if (conv == NULL) {
- Log::Warning("MediaPlayer %s Could not parse %s", std::to_string(id_).c_str(), elem.c_str());
- return false;
- }
-
- // PAUSE pipeline
- execute_play_command(false);
-
- // remove previous (if exists)
- GstElement *ef = gst_bin_get_by_name (GST_BIN (pipeline_), "effect");
- if (ef) {
- // remove the previous effect (this deconnects from prev and post)
- if ( !gst_bin_remove (GST_BIN (pipeline_), ef) ) {
- Log::Warning("MediaPlayer %s Could not remove effect", std::to_string(id_).c_str());
- return false;
- }
- gst_object_unref(ef);
- }
-
- GstElement *co = gst_bin_get_by_name (GST_BIN (pipeline_), "conv");
- if (co) {
- // remove the previous convertor (this deconnects from prev and post)
- if ( !gst_bin_remove (GST_BIN (pipeline_), co) ) {
- Log::Warning("MediaPlayer %s Could not remove convert for effect", std::to_string(id_).c_str());
- return false;
- }
- gst_object_unref(co);
- }
- else
- gst_element_unlink(prev, post);
-
- // add new elements to the pipeline
- if ( gst_bin_add (GST_BIN (pipeline_), conv) && gst_bin_add (GST_BIN (pipeline_), effect) ) {
- // reconnect pipeline
- if ( !gst_element_link_many (prev, conv, effect, post, NULL) ) {
- gst_bin_remove (GST_BIN (pipeline_), conv);
- gst_bin_remove (GST_BIN (pipeline_), effect);
- // restore pileline without any effect
- gst_element_link_many (prev, post, NULL);
- // inform
- Log::Warning("MediaPlayer %s Could not re-link effect", std::to_string(id_).c_str());
- }
- }
- else {
- // restore pileline without any effect
- gst_element_link_many (prev, post, NULL);
- Log::Warning("MediaPlayer %s Could not add new effect", std::to_string(id_).c_str());
- }
-
- // PLAY pipeline
- execute_play_command(true);
-
- return true;
+ video_filter_ = pipeline_element;
+ reopen();
}
bool MediaPlayer::isOpen() const
@@ -553,13 +653,13 @@ void MediaPlayer::Frame::unmap()
void delayed_terminate( GstElement *p )
{
- GstElement *__pipeline = p;
+ GstObject *__pipeline = (GstObject *) gst_object_ref(p);
// end pipeline
- gst_element_set_state (__pipeline, GST_STATE_NULL);
+ gst_element_set_state ( GST_ELEMENT(__pipeline), GST_STATE_NULL);
// unref to free pipeline
- gst_object_unref ( GST_OBJECT (__pipeline) );
+ gst_object_unref ( __pipeline );
}
@@ -577,18 +677,16 @@ void MediaPlayer::close()
// un-ready the media player
opened_ = false;
+ failed_ = false;
+ pending_ = false;
+ seeking_ = false;
+ force_update_ = false;
+ rate_ = 1.0;
+ rate_change_ = RATE_CHANGE_NONE;
// clean up GST
if (pipeline_ != nullptr) {
- // // force flush
- // gst_element_send_event(pipeline_, gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
- // GST_SEEK_TYPE_NONE, 0, GST_SEEK_TYPE_NONE, 0) );
- // gst_element_get_state (pipeline_, NULL, NULL, GST_CLOCK_TIME_NONE);
- // // end pipeline
- // gst_element_set_state (pipeline_, GST_STATE_NULL);
- // gst_object_unref ( GST_OBJECT (pipeline_) );
-
// end pipeline asynchronously // TODO more stress test?
std::thread(delayed_terminate, pipeline_).detach();
@@ -749,6 +847,7 @@ void MediaPlayer::execute_play_command(bool on)
else
Log::Info("MediaPlayer %s Stop [%ld]", std::to_string(id_).c_str(), position());
#endif
+
}
void MediaPlayer::play(bool on)
@@ -802,8 +901,6 @@ void MediaPlayer::setLoop(MediaPlayer::LoopMode mode)
loop_ = mode;
}
-//void
-
void MediaPlayer::rewind(bool force)
{
if (!enabled_ || !media_.seekable || pending_)
@@ -1205,14 +1302,14 @@ void MediaPlayer::execute_seek_command(GstClockTime target, bool force)
}
// Send the event (ASYNC)
- if (seek_event && !gst_element_send_event(pipeline_, seek_event) )
- Log::Warning("MediaPlayer %s Seek failed", std::to_string(id_).c_str());
- else {
+ if (seek_event && gst_element_send_event(pipeline_, seek_event) ) {
seeking_ = true;
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s Seek %ld %.1f", std::to_string(id_).c_str(), seek_pos, rate_);
#endif
}
+ else
+ Log::Warning("MediaPlayer %s Seek failed", std::to_string(id_).c_str());
// Force update
if (force) {
@@ -1222,45 +1319,65 @@ void MediaPlayer::execute_seek_command(GstClockTime target, bool force)
}
+
+
void MediaPlayer::setPlaySpeed(double s)
{
-#if GST_VERSION_MINOR > 17 && GST_VERSION_MAJOR > 0
+#ifdef USE_GST_PLAYBIN
bool change_direction = s * rate_ < 0.0;
#endif
- if (media_.isimage)
- return;
-
+ // Set rate to requested value (may be executed later)
// bound to interval [-MAX_PLAY_SPEED MAX_PLAY_SPEED]
rate_ = CLAMP(s, -MAX_PLAY_SPEED, MAX_PLAY_SPEED);
// skip interval [-MIN_PLAY_SPEED MIN_PLAY_SPEED]
if (ABS(rate_) < MIN_PLAY_SPEED)
rate_ = SIGN(rate_) * MIN_PLAY_SPEED;
- // Apply with gstreamer seek
+ // discard if too early or not possible
+ if (media_.isimage || !media_.seekable || pipeline_ == nullptr)
+ return;
+
//
+ // Apply rate change with gstreamer seek
+ //
+
+#ifdef USE_GST_PLAYBIN
// GST_SEEK_FLAG_INSTANT_RATE_CHANGE: Signals that a rate change should be
// applied immediately. Only valid if start/stop position
// are GST_CLOCK_TIME_NONE, the playback direction does not change
// and the seek is not flushing. (Since: 1.18)
-#if GST_VERSION_MINOR > 17 && GST_VERSION_MAJOR > 0
// if all conditions to use GST_SEEK_FLAG_INSTANT_RATE_CHANGE are met
- if ( pipeline_ != nullptr && media_.seekable && !change_direction && !force_basic_speedchange_) {
+ if ( rate_change_ == RATE_CHANGE_INSTANT && !change_direction ) {
- GstEvent *seek_event = gst_event_new_seek (rate_, GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
- GST_SEEK_TYPE_NONE, 0, GST_SEEK_TYPE_NONE, 0);
+ int seek_flags = GST_SEEK_FLAG_INSTANT_RATE_CHANGE;
+ seek_flags |= GST_SEEK_FLAG_TRICKMODE;
- if (seek_event && !gst_element_send_event(pipeline_, seek_event) ) {
- Log::Info("MediaPlayer %s; cannot perform instantaneous speed change. Change of play speed will not be smooth.", std::to_string(id_).c_str());
- force_basic_speedchange_ = true;
- }
- else
+ GstEvent *seek_event = gst_event_new_seek (rate_, GST_FORMAT_TIME,
+ (GstSeekFlags) seek_flags,
+ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE,
+ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+
+ if (seek_event && gst_element_send_event(pipeline_, seek_event) ) {
seeking_ = true;
+ Log::Info("MediaPlayer %s; instant rate to %f", std::to_string(id_).c_str(), rate_);
+ }
+ else {
+ Log::Info("MediaPlayer %s; cannot perform instantaneous speed change. Change of play speed will not be smooth.", std::to_string(id_).c_str());
+ rate_change_ = RATE_CHANGE_FLUSH;
+ }
}
- // Generic way is following the example from
+ // Generic way is a flush seek
+ // following the example from
// https://gstreamer.freedesktop.org/documentation/tutorials/basic/playback-speed.html
- else
+ else {
+ Log::Info("MediaPlayer %s; flush rate to %f", std::to_string(id_).c_str(), rate_);
execute_seek_command();
+ }
+
+ // Set after initialization (will be used next time)
+ if (rate_change_ == RATE_CHANGE_NONE)
+ rate_change_ = RATE_CHANGE_INSTANT;
#else
execute_seek_command();
#endif
@@ -1286,11 +1403,6 @@ void MediaPlayer::setTimeline(const Timeline &tl)
timeline_ = tl;
}
-//void MediaPlayer::toggleGapInTimeline(GstClockTime from, GstClockTime to)
-//{
-// return timeline.toggleGaps(from, to);
-//}
-
MediaInfo MediaPlayer::media() const
{
return media_;
diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h
index 67c19a0..e5604a9 100644
--- a/src/MediaPlayer.h
+++ b/src/MediaPlayer.h
@@ -252,6 +252,12 @@ public:
* */
inline void setSyncToMetronome(Metronome::Synchronicity s) { metro_sync_ = s; }
inline Metronome::Synchronicity syncToMetronome() const { return metro_sync_; }
+ /**
+ * Adds a video effect into the gstreamer pipeline
+ * NB: setVideoEffect reopens the video
+ * */
+ void setVideoEffect(const std::string &pipeline_element);
+ inline std::string videoEffect() { return video_filter_; }
/**
* Accept visitors
* */
@@ -267,7 +273,6 @@ public:
static MediaInfo UriDiscoverer(const std::string &uri);
std::string log() const { return media_.log; }
- bool setEffect(const std::string &pipeline_element);
private:
@@ -284,7 +289,6 @@ private:
// GST & Play status
GstClockTime position_;
- gdouble rate_;
LoopMode loop_;
GstState desired_state_;
GstElement *pipeline_;
@@ -297,8 +301,18 @@ private:
bool enabled_;
bool rewind_on_disable_;
bool force_software_decoding_;
- bool force_basic_speedchange_;
std::string decoder_name_;
+ std::string video_filter_;
+
+ // Play speed
+ gdouble rate_;
+ typedef enum {
+ RATE_CHANGE_NONE = 0,
+ RATE_CHANGE_INSTANT = 1,
+ RATE_CHANGE_FLUSH = 2
+ } RateChangeMode;
+ RateChangeMode rate_change_;
+
Metronome::Synchronicity metro_sync_;
// fps counter
diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp
index bc8e530..b9e04a9 100644
--- a/src/SessionCreator.cpp
+++ b/src/SessionCreator.cpp
@@ -875,6 +875,10 @@ void SessionLoader::visit(MediaPlayer &n)
// change play status only if different id (e.g. new media player)
if ( n.id() != id__ ) {
+ const char *pFilter = mediaplayerNode->Attribute("video_effect");
+ if (pFilter)
+ n.setVideoEffect(std::string(pFilter));
+
double speed = 1.0;
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
n.setPlaySpeed(speed);
@@ -1078,6 +1082,9 @@ void SessionLoader::visit (MediaSource& s)
// set config media player
s.mediaplayer()->accept(*this);
+
+ // add a callback to activate play speed
+ s.call( new PlaySpeed( s.mediaplayer()->playSpeed() ) );
}
void SessionLoader::visit (SessionFileSource& s)
diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp
index 50e7dbd..11d17a4 100644
--- a/src/SessionVisitor.cpp
+++ b/src/SessionVisitor.cpp
@@ -414,6 +414,7 @@ void SessionVisitor::visit(MediaPlayer &n)
newelement->SetAttribute("play", n.isPlaying());
newelement->SetAttribute("loop", (int) n.loop());
newelement->SetAttribute("speed", n.playSpeed());
+ newelement->SetAttribute("video_effect", n.videoEffect().c_str());
newelement->SetAttribute("software_decoding", n.softwareDecodingForced());
newelement->SetAttribute("rewind_on_disabled", n.rewindOnDisabled());
newelement->SetAttribute("sync_to_metronome", (int) n.syncToMetronome());
diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp
index f60aaa2..2906033 100644
--- a/src/SourceControlWindow.cpp
+++ b/src/SourceControlWindow.cpp
@@ -1668,10 +1668,8 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::SameLine();
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f);
- if (ImGuiToolkit::IconButton(12,14,"Reset speed" )) {
- mediaplayer_active_->setPlaySpeed( 1.0 );
- oss << ": Speed x1";
- Action::manager().store(oss.str());
+ if (ImGuiToolkit::IconButton(12,14,"Reset" )) {
+ mediaplayer_active_->reopen();
}
// restore buttons style
@@ -1858,7 +1856,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
close = true;
// apply to pipeline
- mediaplayer_active_->setEffect(_description);
+ mediaplayer_active_->setVideoEffect(_description);
}
ImGui::PopStyleColor(1);