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