| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "garnet/bin/media/media_service/media_timeline_controller_impl.h" |
| |
| #include "garnet/bin/media/fidl/fidl_type_conversions.h" |
| #include "garnet/bin/media/util/callback_joiner.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/media/timeline/fidl_type_conversions.h" |
| #include "lib/media/timeline/timeline.h" |
| |
| namespace media { |
| |
| // static |
| std::shared_ptr<MediaTimelineControllerImpl> |
| MediaTimelineControllerImpl::Create( |
| fidl::InterfaceRequest<MediaTimelineController> request, |
| MediaServiceImpl* owner) { |
| return std::shared_ptr<MediaTimelineControllerImpl>( |
| new MediaTimelineControllerImpl(std::move(request), owner)); |
| } |
| |
| MediaTimelineControllerImpl::MediaTimelineControllerImpl( |
| fidl::InterfaceRequest<MediaTimelineController> request, |
| MediaServiceImpl* owner) |
| : MediaServiceImpl::Product<MediaTimelineController>(this, |
| std::move(request), |
| owner), |
| control_point_binding_(this), |
| consumer_binding_(this) { |
| status_publisher_.SetCallbackRunner( |
| [this](const GetStatusCallback& callback, uint64_t version) { |
| MediaTimelineControlPointStatusPtr status = |
| MediaTimelineControlPointStatus::New(); |
| status->timeline_transform = |
| TimelineTransform::From(current_timeline_function_); |
| status->end_of_stream = end_of_stream_; |
| callback(version, std::move(status)); |
| }); |
| } |
| |
| MediaTimelineControllerImpl::~MediaTimelineControllerImpl() { |
| status_publisher_.SendUpdates(); |
| |
| // Close the additional bindings before members are destroyed so we don't |
| // try to destroy any callbacks that are pending on open channels. |
| |
| if (control_point_binding_.is_bound()) { |
| control_point_binding_.Unbind(); |
| } |
| |
| if (consumer_binding_.is_bound()) { |
| consumer_binding_.Unbind(); |
| } |
| } |
| |
| void MediaTimelineControllerImpl::AddControlPoint( |
| fidl::InterfaceHandle<MediaTimelineControlPoint> control_point) { |
| control_point_states_.push_back(std::unique_ptr<ControlPointState>( |
| new ControlPointState(this, control_point.Bind()))); |
| |
| control_point_states_.back()->HandleStatusUpdates(); |
| } |
| |
| void MediaTimelineControllerImpl::GetControlPoint( |
| fidl::InterfaceRequest<MediaTimelineControlPoint> control_point) { |
| if (control_point_binding_.is_bound()) { |
| control_point_binding_.Unbind(); |
| } |
| |
| control_point_binding_.Bind(std::move(control_point)); |
| } |
| |
| void MediaTimelineControllerImpl::GetStatus(uint64_t version_last_seen, |
| const GetStatusCallback& callback) { |
| status_publisher_.Get(version_last_seen, callback); |
| } |
| |
| void MediaTimelineControllerImpl::GetTimelineConsumer( |
| fidl::InterfaceRequest<TimelineConsumer> timeline_consumer) { |
| if (consumer_binding_.is_bound()) { |
| consumer_binding_.Unbind(); |
| } |
| |
| consumer_binding_.Bind(std::move(timeline_consumer)); |
| } |
| |
| void MediaTimelineControllerImpl::SetProgramRange(uint64_t program, |
| int64_t min_pts, |
| int64_t max_pts) { |
| for (const std::unique_ptr<ControlPointState>& control_point_state : |
| control_point_states_) { |
| control_point_state->control_point_->SetProgramRange(program, min_pts, |
| max_pts); |
| } |
| } |
| |
| void MediaTimelineControllerImpl::Prime(const PrimeCallback& callback) { |
| std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create(); |
| |
| for (const std::unique_ptr<ControlPointState>& control_point_state : |
| control_point_states_) { |
| control_point_state->control_point_->Prime(callback_joiner->NewCallback()); |
| } |
| |
| callback_joiner->WhenJoined(callback); |
| } |
| |
| void MediaTimelineControllerImpl::SetTimelineTransform( |
| TimelineTransformPtr timeline_transform, |
| const SetTimelineTransformCallback& callback) { |
| RCHECK(timeline_transform); |
| RCHECK(timeline_transform->reference_delta != 0); |
| |
| // There can only be one SetTimelineTransform transition pending at any |
| // moment, so a new SetTimelineTransform call that arrives before a previous |
| // one completes cancels the previous one. This causes some problems for us, |
| // because some control points may complete the previous transition while |
| // others may not. |
| // |
| // We start by noticing that there's an incomplete previous transition, and |
| // we 'cancel' it, meaning we call its callback with a false complete |
| // parameter. |
| // |
| // If we're cancelling a previous transition, we need to take steps to make |
| // sure the control points will end up in the right state regardless of |
| // whether they completed the previous transition. Specifically, if |
| // subject_time isn't specified, we infer it here and supply the inferred |
| // value to the control points, so there's no disagreement about its value. |
| |
| std::shared_ptr<TimelineTransition> pending_transition = |
| pending_transition_.lock(); |
| if (pending_transition) { |
| // A transition is pending - cancel it. |
| pending_transition->Cancel(); |
| } |
| |
| if (timeline_transform->subject_time != kUnspecifiedTime) { |
| // We're seeking, so we may not be at end-of-stream anymore. The control |
| // sites will signal end-of-stream again if we are. |
| end_of_stream_ = false; |
| } |
| |
| // These will be recorded as part of the new TimelineFunction. |
| int64_t reference_time = |
| timeline_transform->reference_time == kUnspecifiedTime |
| ? (Timeline::local_now() + kDefaultLeadTime) |
| : timeline_transform->reference_time; |
| int64_t subject_time = timeline_transform->subject_time; |
| |
| // Determine the actual subject time, inferring it if it wasn't specified. |
| int64_t actual_subject_time = subject_time == kUnspecifiedTime |
| ? current_timeline_function_(reference_time) |
| : subject_time; |
| |
| if (pending_transition && subject_time == kUnspecifiedTime) { |
| // We're cancelling a pending transition, which may have already completed |
| // at one or more of the control sites. We don't want the sites to have to |
| // infer the subject_time, because we can't be sure what subject_time a |
| // site will infer. |
| subject_time = actual_subject_time; |
| } |
| |
| // Record the new pending transition. |
| std::shared_ptr<TimelineTransition> transition = |
| std::shared_ptr<TimelineTransition>( |
| new TimelineTransition(reference_time, actual_subject_time, |
| timeline_transform->reference_delta, |
| timeline_transform->subject_delta, callback)); |
| |
| pending_transition_ = transition; |
| |
| TimelineTransform transform_to_send; |
| transform_to_send.reference_time = reference_time; |
| transform_to_send.subject_time = subject_time; |
| transform_to_send.reference_delta = timeline_transform->reference_delta; |
| transform_to_send.subject_delta = timeline_transform->subject_delta; |
| |
| // Initiate the transition for each control point. |
| for (const std::unique_ptr<ControlPointState>& control_point_state : |
| control_point_states_) { |
| control_point_state->end_of_stream_ = false; |
| control_point_state->consumer_->SetTimelineTransform( |
| transform_to_send.Clone(), transition->NewCallback()); |
| } |
| |
| // If and when this transition is complete, adopt the new TimelineFunction |
| // and tell any status subscribers. |
| transition->WhenCompleted([this, transition]() { |
| current_timeline_function_ = transition->new_timeline_function(); |
| status_publisher_.SendUpdates(); |
| }); |
| } |
| |
| void MediaTimelineControllerImpl::SetTimelineTransformNoReply( |
| TimelineTransformPtr timeline_transform) { |
| SetTimelineTransform(std::move(timeline_transform), [](bool completed) {}); |
| } |
| |
| void MediaTimelineControllerImpl::HandleControlPointEndOfStreamChange() { |
| bool end_of_stream = true; |
| for (const std::unique_ptr<ControlPointState>& control_point_state : |
| control_point_states_) { |
| if (!control_point_state->end_of_stream_) { |
| end_of_stream = false; |
| break; |
| } |
| } |
| |
| if (end_of_stream_ != end_of_stream) { |
| end_of_stream_ = end_of_stream; |
| status_publisher_.SendUpdates(); |
| } |
| } |
| |
| MediaTimelineControllerImpl::ControlPointState::ControlPointState( |
| MediaTimelineControllerImpl* parent, |
| MediaTimelineControlPointPtr point) |
| : parent_(parent), control_point_(std::move(point)) { |
| control_point_->GetTimelineConsumer(consumer_.NewRequest()); |
| } |
| |
| MediaTimelineControllerImpl::ControlPointState::~ControlPointState() {} |
| |
| void MediaTimelineControllerImpl::ControlPointState::HandleStatusUpdates( |
| uint64_t version, |
| MediaTimelineControlPointStatusPtr status) { |
| if (status) { |
| // Respond to any end-of-stream changes. |
| if (end_of_stream_ != status->end_of_stream) { |
| end_of_stream_ = status->end_of_stream; |
| parent_->HandleControlPointEndOfStreamChange(); |
| } |
| } |
| |
| control_point_->GetStatus( |
| version, |
| [this](uint64_t version, MediaTimelineControlPointStatusPtr status) { |
| HandleStatusUpdates(version, std::move(status)); |
| }); |
| } |
| |
| MediaTimelineControllerImpl::TimelineTransition::TimelineTransition( |
| int64_t reference_time, |
| int64_t subject_time, |
| uint32_t reference_delta, |
| uint32_t subject_delta, |
| const SetTimelineTransformCallback& callback) |
| : new_timeline_function_(reference_time, |
| subject_time, |
| reference_delta, |
| subject_delta), |
| callback_(callback) { |
| FXL_DCHECK(callback_); |
| callback_joiner_.WhenJoined([this]() { |
| if (cancelled_) { |
| FXL_DCHECK(!callback_); |
| return; |
| } |
| |
| FXL_DCHECK(callback_); |
| callback_(true); |
| callback_ = nullptr; |
| if (completed_callback_) { |
| completed_callback_(); |
| completed_callback_ = nullptr; |
| } |
| }); |
| } |
| |
| MediaTimelineControllerImpl::TimelineTransition::~TimelineTransition() {} |
| |
| } // namespace media |