From 5419622c744f9efda03ea992eb63980c96bb130d Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Mon, 7 Aug 2023 19:40:08 +0200 Subject: [PATCH] Upgrade MediaPlayer with gstreamer playbin (for GST > 18) Change implementation of gst pipeline in MediaPlayer to use gstreamer playbin. This makes everything works more smoothly (including instant rate change). This also opens the possibility to allow audio mixing. Other bugfixes include set play speed rate at start, add video effects on reopen media player. --- src/MediaPlayer.cpp | 396 +++++++++++++++++++++++------------- src/MediaPlayer.h | 20 +- src/SessionCreator.cpp | 7 + src/SessionVisitor.cpp | 1 + src/SourceControlWindow.cpp | 8 +- 5 files changed, 282 insertions(+), 150 deletions(-) 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);