diff --git a/src/core/producer/frame_producer.cpp b/src/core/producer/frame_producer.cpp index 348c41abdb..71222ef7b6 100644 --- a/src/core/producer/frame_producer.cpp +++ b/src/core/producer/frame_producer.cpp @@ -185,6 +185,7 @@ class destroy_producer_proxy : public frame_producer draw_frame last_frame(const core::video_field field) override { return producer_->last_frame(field); } draw_frame first_frame(const core::video_field field) override { return producer_->first_frame(field); } core::monitor::state state() const override { return producer_->state(); } + bool is_ready() override { return producer_->is_ready(); } }; spl::shared_ptr create_destroy_proxy(spl::shared_ptr producer) diff --git a/src/core/producer/frame_producer.h b/src/core/producer/frame_producer.h index 638013e882..f67eb6fd3d 100644 --- a/src/core/producer/frame_producer.h +++ b/src/core/producer/frame_producer.h @@ -49,16 +49,13 @@ class frame_producer uint32_t frame_number_ = 0; core::draw_frame last_frame_; core::draw_frame first_frame_; + bool is_ready_ = false; public: static const spl::shared_ptr& empty(); - frame_producer(core::draw_frame frame) - : last_frame_(std::move(frame)) - { - } - frame_producer() {} - virtual ~frame_producer() {} + frame_producer() = default; + virtual ~frame_producer() = default; draw_frame receive(const video_field field, int nb_samples) { @@ -110,6 +107,12 @@ class frame_producer virtual void leading_producer(const spl::shared_ptr&) {} virtual spl::shared_ptr following_producer() const { return core::frame_producer::empty(); } virtual boost::optional auto_play_delta() const { return boost::none; } + + /** + * Some producers take a couple of frames before they produce frames. + * While this returns false, the previous producer will be left running for a limited number of frames. + */ + virtual bool is_ready() { return !!first_frame_; }; }; class const_producer : public core::frame_producer @@ -147,6 +150,8 @@ class const_producer : public core::frame_producer static const monitor::state empty; return empty; } + + bool is_ready() override { return true; } }; class frame_producer_registry; diff --git a/src/core/producer/transition/sting_producer.cpp b/src/core/producer/transition/sting_producer.cpp index 22927a6c36..55dd97ecd4 100644 --- a/src/core/producer/transition/sting_producer.cpp +++ b/src/core/producer/transition/sting_producer.cpp @@ -28,9 +28,7 @@ #include "../../monitor/monitor.h" #include "../frame_producer.h" -#include #include -#include #include diff --git a/src/core/producer/transition/transition_producer.cpp b/src/core/producer/transition/transition_producer.cpp index 36e1740742..68d685de5a 100644 --- a/src/core/producer/transition/transition_producer.cpp +++ b/src/core/producer/transition/transition_producer.cpp @@ -43,21 +43,37 @@ class transition_producer : public frame_producer spl::shared_ptr dst_producer_ = frame_producer::empty(); spl::shared_ptr src_producer_ = frame_producer::empty(); + bool dst_is_ready_ = false; public: transition_producer(const spl::shared_ptr& dest, const transition_info& info) : info_(info) , dst_producer_(dest) { + dst_is_ready_ = dst_producer_->is_ready(); update_state(); } // frame_producer + void update_is_ready(const core::video_field field) + { + // Ensure a frame has been attempted + dst_producer_->first_frame(field); + + dst_is_ready_ = dst_producer_->is_ready(); + } + core::draw_frame last_frame(const core::video_field field) override { CASPAR_SCOPE_EXIT { update_state(); }; + update_is_ready(field); + + if (!dst_is_ready_) { + return src_producer_->last_frame(field); + } + auto src = src_producer_->last_frame(field); auto dst = dst_producer_->last_frame(field); @@ -70,38 +86,48 @@ class transition_producer : public frame_producer spl::shared_ptr following_producer() const override { - return current_frame_ >= info_.duration ? dst_producer_ : core::frame_producer::empty(); + return current_frame_ >= info_.duration && dst_is_ready_ ? dst_producer_ : core::frame_producer::empty(); } boost::optional auto_play_delta() const override { return info_.duration; } void update_state() { - state_ = dst_producer_->state(); - state_["transition/producer"] = dst_producer_->name(); - state_["transition/frame"] = {current_frame_, info_.duration}; - state_["transition/type"] = [&]() -> std::string { - switch (info_.type) { - case transition_type::mix: - return "mix"; - case transition_type::wipe: - return "wipe"; - case transition_type::slide: - return "slide"; - case transition_type::push: - return "push"; - case transition_type::cut: - return "cut"; - default: - return "n/a"; - } - }(); + if (!dst_is_ready_) { + state_ = src_producer_->state(); + } else { + state_ = dst_producer_->state(); + state_["transition/producer"] = dst_producer_->name(); + state_["transition/frame"] = {current_frame_, info_.duration}; + state_["transition/type"] = [&]() -> std::string { + switch (info_.type) { + case transition_type::mix: + return "mix"; + case transition_type::wipe: + return "wipe"; + case transition_type::slide: + return "slide"; + case transition_type::push: + return "push"; + case transition_type::cut: + return "cut"; + default: + return "n/a"; + } + }(); + } } draw_frame receive_impl(const core::video_field field, int nb_samples) override { CASPAR_SCOPE_EXIT { update_state(); }; + update_is_ready(field); + + if (!dst_is_ready_) { + return src_producer_->receive(field, nb_samples); + } + auto dst = dst_producer_->receive(field, nb_samples); if (!dst) { dst = dst_producer_->last_frame(field); @@ -193,11 +219,15 @@ bool try_match_transition(const std::wstring& message, transition_info& transiti return false; } - auto transition = what["TRANSITION"].str(); transitionInfo.duration = std::stoi(what["DURATION"].str()); - auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L""; - auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L""; - transitionInfo.tweener = tween; + if (transitionInfo.duration == 0) { + return false; + } + + auto transition = what["TRANSITION"].str(); + auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L""; + auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L""; + transitionInfo.tweener = tween; if (transition == L"CUT") transitionInfo.type = transition_type::cut; @@ -210,14 +240,10 @@ bool try_match_transition(const std::wstring& message, transition_info& transiti else if (transition == L"WIPE") transitionInfo.type = transition_type::wipe; - if (direction == L"FROMLEFT") + if (direction == L"FROMLEFT" || direction == L"RIGHT") transitionInfo.direction = transition_direction::from_left; - else if (direction == L"FROMRIGHT") + else if (direction == L"FROMRIGHT" || direction == L"LEFT") transitionInfo.direction = transition_direction::from_right; - else if (direction == L"LEFT") - transitionInfo.direction = transition_direction::from_right; - else if (direction == L"RIGHT") - transitionInfo.direction = transition_direction::from_left; return true; } diff --git a/src/protocol/amcp/AMCPCommandsImpl.cpp b/src/protocol/amcp/AMCPCommandsImpl.cpp index a971eb2e16..403bbe4d15 100644 --- a/src/protocol/amcp/AMCPCommandsImpl.cpp +++ b/src/protocol/amcp/AMCPCommandsImpl.cpp @@ -282,26 +282,28 @@ std::wstring loadbg_command(command_context& ctx) bool auto_play = contains_param(L"AUTO", ctx.parameters); try { - auto pFP = ctx.static_context->producer_registry->create_producer(get_producer_dependencies(channel, ctx), - ctx.parameters); + auto new_producer = ctx.static_context->producer_registry->create_producer( + get_producer_dependencies(channel, ctx), ctx.parameters); - if (pFP == frame_producer::empty()) - CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L"")); + if (new_producer == frame_producer::empty()) + CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(!ctx.parameters.empty() ? ctx.parameters[0] : L"")); spl::shared_ptr transition_producer = frame_producer::empty(); transition_info transitionInfo; sting_info stingInfo; if (try_match_sting(ctx.parameters, stingInfo)) { - transition_producer = create_sting_producer(get_producer_dependencies(channel, ctx), pFP, stingInfo); + transition_producer = + create_sting_producer(get_producer_dependencies(channel, ctx), new_producer, stingInfo); } else { std::wstring message; - for (size_t n = 0; n < ctx.parameters.size(); ++n) - message += boost::to_upper_copy(ctx.parameters[n]) + L" "; + for (std::wstring& parameter : ctx.parameters) { + message += boost::to_upper_copy(parameter) + L" "; + } - // Always fallback to transition + // Try other transitions try_match_transition(message, transitionInfo); - transition_producer = create_transition_producer(pFP, transitionInfo); + transition_producer = create_transition_producer(new_producer, transitionInfo); } // TODO - we should pass the format into load(), so that we can catch it having changed since the producer was @@ -329,11 +331,11 @@ std::wstring load_command(command_context& ctx) ctx.channel.stage->preview(ctx.layer_index()); } else { try { - auto pFP = ctx.static_context->producer_registry->create_producer( + auto new_producer = ctx.static_context->producer_registry->create_producer( get_producer_dependencies(ctx.channel.raw_channel, ctx), ctx.parameters); - auto pFP2 = create_transition_producer(pFP, transition_info{}); + auto transition_producer = create_transition_producer(new_producer, transition_info{}); - ctx.channel.stage->load(ctx.layer_index(), pFP2, true); + ctx.channel.stage->load(ctx.layer_index(), transition_producer, true); } catch (file_not_found&) { if (contains_param(L"CLEAR_ON_404", ctx.parameters)) { ctx.channel.stage->load(