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);