blob: 54bb564ee9fb75eee287ab02d60ea4854219122b [file] [log] [blame]
// 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