Index: services/media/factory_service/media_timeline_controller_impl.cc |
diff --git a/services/media/factory_service/media_timeline_controller_impl.cc b/services/media/factory_service/media_timeline_controller_impl.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..54bb564ee9fb75eee287ab02d60ea4854219122b |
--- /dev/null |
+++ b/services/media/factory_service/media_timeline_controller_impl.cc |
@@ -0,0 +1,264 @@ |
+// Copyright 2016 The Chromium 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 "base/logging.h" |
+#include "mojo/services/media/common/cpp/timeline.h" |
+#include "services/media/factory_service/media_timeline_controller_impl.h" |
+#include "services/media/framework_mojo/mojo_type_conversions.h" |
+ |
+namespace mojo { |
+namespace media { |
+ |
+// static |
+std::shared_ptr<MediaTimelineControllerImpl> |
+MediaTimelineControllerImpl::Create( |
+ InterfaceRequest<MediaTimelineController> request, |
+ MediaFactoryService* owner) { |
+ return std::shared_ptr<MediaTimelineControllerImpl>( |
+ new MediaTimelineControllerImpl(request.Pass(), owner)); |
+} |
+ |
+MediaTimelineControllerImpl::MediaTimelineControllerImpl( |
+ InterfaceRequest<MediaTimelineController> request, |
+ MediaFactoryService* owner) |
+ : MediaFactoryService::Product<MediaTimelineController>(this, |
+ request.Pass(), |
+ owner), |
+ control_site_binding_(this), |
+ consumer_binding_(this) { |
+ status_publisher_.SetCallbackRunner( |
+ [this](const GetStatusCallback& callback, uint64_t version) { |
+ MediaTimelineControlSiteStatusPtr status = |
+ MediaTimelineControlSiteStatus::New(); |
+ status->timeline_transform = |
+ mojo::TimelineTransform::From(current_timeline_function_); |
+ status->end_of_stream = end_of_stream_; |
+ callback.Run(version, status.Pass()); |
+ }); |
+} |
+ |
+MediaTimelineControllerImpl::~MediaTimelineControllerImpl() { |
+ status_publisher_.SendUpdates(); |
+} |
+ |
+void MediaTimelineControllerImpl::AddControlSite( |
+ InterfaceHandle<MediaTimelineControlSite> control_site) { |
+ site_states_.emplace_back( |
+ this, MediaTimelineControlSitePtr::Create(std::move(control_site))); |
+ site_states_.back().HandleStatusUpdates(); |
+} |
+ |
+void MediaTimelineControllerImpl::GetControlSite( |
+ InterfaceRequest<MediaTimelineControlSite> control_site) { |
+ if (control_site_binding_.is_bound()) { |
+ control_site_binding_.Close(); |
+ } |
+ |
+ control_site_binding_.Bind(control_site.Pass()); |
+} |
+ |
+void MediaTimelineControllerImpl::GetStatus(uint64_t version_last_seen, |
+ const GetStatusCallback& callback) { |
+ status_publisher_.Get(version_last_seen, callback); |
+} |
+ |
+void MediaTimelineControllerImpl::GetTimelineConsumer( |
+ InterfaceRequest<TimelineConsumer> timeline_consumer) { |
+ if (consumer_binding_.is_bound()) { |
+ consumer_binding_.Close(); |
+ } |
+ |
+ consumer_binding_.Bind(timeline_consumer.Pass()); |
+} |
+ |
+void MediaTimelineControllerImpl::SetTimelineTransform( |
+ int64_t subject_time, |
+ uint32_t reference_delta, |
+ uint32_t subject_delta, |
+ int64_t effective_reference_time, |
+ int64_t effective_subject_time, |
+ const SetTimelineTransformCallback& callback) { |
+ // At most one effective time may be specified. |
+ RCHECK(effective_subject_time == kUnspecifiedTime || |
+ effective_reference_time == kUnspecifiedTime); |
+ // effective_subject_time can only be specified if we're progressing. |
+ RCHECK(effective_subject_time == kUnspecifiedTime || |
+ current_timeline_function_.subject_delta() != 0u); |
+ RCHECK(reference_delta != 0); |
+ |
+ // There can only be once 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 sites may complete the previous transition while other 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 sites will end up in the right state regardless of whether they |
+ // completed the previous transition. We do two things: |
+ // |
+ // 1) If subject_time isn't specified, we infer it here and supply the |
+ // inferred value to the sites, so there's no disagreement about its |
+ // value. |
+ // 2) If the effective_subject_time is specified rather than the |
+ // effective_reference_time, we infer effective_reference_time and send |
+ // it to the sites instead of effective_subject_time, so there's no |
+ // disagreement about effective time and so that no sites reject the |
+ // transition due to having a zero subject_delta. |
+ |
+ std::shared_ptr<TimelineTransition> pending_transition = |
+ pending_transition_.lock(); |
+ if (pending_transition) { |
+ // A transition is pending - cancel it. |
+ pending_transition->Cancel(); |
+ } |
+ |
+ // These will be recorded as part of the new TimelineFunction. |
+ int64_t new_reference_time; |
+ int64_t new_subject_time; |
+ |
+ if (effective_subject_time != kUnspecifiedTime) { |
+ // Infer new_reference_time from effective_subject_time. |
+ new_reference_time = |
+ current_timeline_function_.ApplyInverse(effective_subject_time); |
+ |
+ // Figure out what the subject_time will be after this transition. |
+ if (subject_time == kUnspecifiedTime) { |
+ new_subject_time = effective_subject_time; |
+ } else { |
+ new_subject_time = subject_time; |
+ } |
+ } else { |
+ if (effective_reference_time == kUnspecifiedTime) { |
+ // Neither effective time was specified. Effective time is now. |
+ effective_reference_time = Timeline::local_now() + kDefaultLeadTime; |
+ } |
+ |
+ // new_reference_time is just effective_reference_time. |
+ new_reference_time = effective_reference_time; |
+ |
+ // Figure out what the subject_time will be after this transition. |
+ if (subject_time == kUnspecifiedTime) { |
+ new_subject_time = current_timeline_function_(effective_reference_time); |
+ } else { |
+ new_subject_time = subject_time; |
+ } |
+ } |
+ |
+ if (pending_transition) { |
+ // This transition cancels a previous one. Use effective_reference_time |
+ // rather than effective_subject_time, because we can't be sure what |
+ // effective_subject_time will mean to the sites. |
+ effective_reference_time = new_reference_time; |
+ effective_subject_time = kUnspecifiedTime; |
+ |
+ // 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 = new_subject_time; |
+ } |
+ |
+ // Recording the new pending transition. |
+ std::shared_ptr<TimelineTransition> transition = |
+ std::shared_ptr<TimelineTransition>( |
+ new TimelineTransition(new_reference_time, new_subject_time, |
+ reference_delta, subject_delta, callback)); |
+ |
+ pending_transition_ = transition; |
+ |
+ // Initiate the transition for each site. |
+ for (const SiteState& site_state : site_states_) { |
+ site_state.consumer_->SetTimelineTransform( |
+ subject_time, reference_delta, subject_delta, effective_reference_time, |
+ effective_subject_time, 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::HandleSiteEndOfStreamChange() { |
+ bool end_of_stream = true; |
+ for (const SiteState& site_state : site_states_) { |
+ if (!site_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::SiteState::SiteState( |
+ MediaTimelineControllerImpl* parent, |
+ MediaTimelineControlSitePtr site) |
+ : parent_(parent), site_(site.Pass()) { |
+ site_->GetTimelineConsumer(GetProxy(&consumer_)); |
+} |
+ |
+MediaTimelineControllerImpl::SiteState::SiteState(SiteState&& other) |
+ : parent_(other.parent_), |
+ site_(other.site_.Pass()), |
+ consumer_(other.consumer_.Pass()) {} |
+ |
+MediaTimelineControllerImpl::SiteState::~SiteState() {} |
+ |
+void MediaTimelineControllerImpl::SiteState::HandleStatusUpdates( |
+ uint64_t version, |
+ MediaTimelineControlSiteStatusPtr 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_->HandleSiteEndOfStreamChange(); |
+ } |
+ } |
+ |
+ site_->GetStatus(version, [this](uint64_t version, |
+ MediaTimelineControlSiteStatusPtr status) { |
+ HandleStatusUpdates(version, status.Pass()); |
+ }); |
+} |
+ |
+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) { |
+ DCHECK(!callback_.is_null()); |
+ callback_joiner_.WhenJoined([this]() { |
+ if (cancelled_) { |
+ DCHECK(callback_.is_null()); |
+ return; |
+ } |
+ |
+ DCHECK(!callback_.is_null()); |
+ callback_.Run(true); |
+ callback_.reset(); |
+ if (!completed_callback_.is_null()) { |
+ completed_callback_.Run(); |
+ completed_callback_.reset(); |
+ } |
+ }); |
+} |
+ |
+MediaTimelineControllerImpl::TimelineTransition::~TimelineTransition() {} |
+ |
+} // namespace media |
+} // namespace mojo |