Motown: Implement MediaTimelineController and use it. Implemented MediaTimelineController, which aggregates multiple MediaTimelineControlSites, and changed MediaPlayerImpl to use the controller. R=kulakowski@chromium.org Review URL: https://codereview.chromium.org/2009643002 .
diff --git a/examples/media_test/media_test.h b/examples/media_test/media_test.h index fc17161..ca16de2 100644 --- a/examples/media_test/media_test.h +++ b/examples/media_test/media_test.h
@@ -8,7 +8,6 @@ #include "base/macros.h" #include "mojo/public/cpp/application/application_impl.h" #include "mojo/services/media/common/cpp/timeline_function.h" -#include "mojo/services/media/common/interfaces/rate_control.mojom.h" #include "mojo/services/media/control/interfaces/media_factory.mojom.h" #include "mojo/services/media/control/interfaces/media_player.mojom.h"
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart index f729ebb..50b5f37 100644 --- a/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart +++ b/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart
@@ -14,6 +14,7 @@ import 'package:mojo_services/mojo/media/media_type_converter.mojom.dart' as media_type_converter_mojom; import 'package:mojo_services/mojo/media/media_types.mojom.dart' as media_types_mojom; import 'package:mojo_services/mojo/media/seeking_reader.mojom.dart' as seeking_reader_mojom; +import 'package:mojo_services/mojo/media/timeline_controller.mojom.dart' as timeline_controller_mojom; @@ -566,12 +567,84 @@ } } + +class _MediaFactoryCreateTimelineControllerParams extends bindings.Struct { + static const List<bindings.StructDataHeader> kVersions = const [ + const bindings.StructDataHeader(16, 0) + ]; + timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController = null; + + _MediaFactoryCreateTimelineControllerParams() : super(kVersions.last.size); + + static _MediaFactoryCreateTimelineControllerParams deserialize(bindings.Message message) { + var decoder = new bindings.Decoder(message); + var result = decode(decoder); + if (decoder.excessHandles != null) { + decoder.excessHandles.forEach((h) => h.close()); + } + return result; + } + + static _MediaFactoryCreateTimelineControllerParams decode(bindings.Decoder decoder0) { + if (decoder0 == null) { + return null; + } + _MediaFactoryCreateTimelineControllerParams result = new _MediaFactoryCreateTimelineControllerParams(); + + var mainDataHeader = decoder0.decodeStructDataHeader(); + if (mainDataHeader.version <= kVersions.last.version) { + // Scan in reverse order to optimize for more recent versions. + for (int i = kVersions.length - 1; i >= 0; --i) { + if (mainDataHeader.version >= kVersions[i].version) { + if (mainDataHeader.size == kVersions[i].size) { + // Found a match. + break; + } + throw new bindings.MojoCodecError( + 'Header size doesn\'t correspond to known version size.'); + } + } + } else if (mainDataHeader.size < kVersions.last.size) { + throw new bindings.MojoCodecError( + 'Message newer than the last known version cannot be shorter than ' + 'required by the last known version.'); + } + if (mainDataHeader.version >= 0) { + + result.timelineController = decoder0.decodeInterfaceRequest(8, false, timeline_controller_mojom.MediaTimelineControllerStub.newFromEndpoint); + } + return result; + } + + void encode(bindings.Encoder encoder) { + var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last); + try { + encoder0.encodeInterfaceRequest(timelineController, 8, false); + } on bindings.MojoCodecError catch(e) { + e.message = "Error encountered while encoding field " + "timelineController of struct _MediaFactoryCreateTimelineControllerParams: $e"; + rethrow; + } + } + + String toString() { + return "_MediaFactoryCreateTimelineControllerParams(" + "timelineController: $timelineController" ")"; + } + + Map toJson() { + throw new bindings.MojoCodecError( + 'Object containing handles cannot be encoded to JSON.'); + } +} + const int _mediaFactoryMethodCreatePlayerName = 0; const int _mediaFactoryMethodCreateSourceName = 1; const int _mediaFactoryMethodCreateSinkName = 2; const int _mediaFactoryMethodCreateDemuxName = 3; const int _mediaFactoryMethodCreateDecoderName = 4; const int _mediaFactoryMethodCreateNetworkReaderName = 5; +const int _mediaFactoryMethodCreateTimelineControllerName = 6; class _MediaFactoryServiceDescription implements service_describer.ServiceDescription { dynamic getTopLevelInterface([Function responseFactory]) => @@ -612,6 +685,7 @@ void createDemux(seeking_reader_mojom.SeekingReaderInterface reader, media_demux_mojom.MediaDemuxInterfaceRequest demux); void createDecoder(media_types_mojom.MediaType inputMediaType, media_type_converter_mojom.MediaTypeConverterInterfaceRequest decoder); void createNetworkReader(String url, seeking_reader_mojom.SeekingReaderInterfaceRequest reader); + void createTimelineController(timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController); } abstract class MediaFactoryInterface @@ -756,6 +830,16 @@ ctrl.sendMessage(params, _mediaFactoryMethodCreateNetworkReaderName); } + void createTimelineController(timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController) { + if (!ctrl.isBound) { + ctrl.proxyError("The Proxy is closed."); + return; + } + var params = new _MediaFactoryCreateTimelineControllerParams(); + params.timelineController = timelineController; + ctrl.sendMessage(params, + _mediaFactoryMethodCreateTimelineControllerName); + } } class _MediaFactoryStubControl @@ -821,6 +905,11 @@ message.payload); _impl.createNetworkReader(params.url, params.reader); break; + case _mediaFactoryMethodCreateTimelineControllerName: + var params = _MediaFactoryCreateTimelineControllerParams.deserialize( + message.payload); + _impl.createTimelineController(params.timelineController); + break; default: throw new bindings.MojoCodecError("Unexpected message name"); break; @@ -897,6 +986,9 @@ void createNetworkReader(String url, seeking_reader_mojom.SeekingReaderInterfaceRequest reader) { return impl.createNetworkReader(url, reader); } + void createTimelineController(timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController) { + return impl.createTimelineController(timelineController); + } }
diff --git a/mojo/services/media/control/interfaces/media_factory.mojom b/mojo/services/media/control/interfaces/media_factory.mojom index c171886..6ad372a 100644 --- a/mojo/services/media/control/interfaces/media_factory.mojom +++ b/mojo/services/media/control/interfaces/media_factory.mojom
@@ -12,6 +12,7 @@ import "mojo/services/media/core/interfaces/media_demux.mojom"; import "mojo/services/media/core/interfaces/media_type_converter.mojom"; import "mojo/services/media/core/interfaces/seeking_reader.mojom"; +import "mojo/services/media/core/interfaces/timeline_controller.mojom"; // Exposed by the factory service to create media-related agents. [ServiceName="mojo::media::MediaFactory"] @@ -42,4 +43,7 @@ // Creates a network reader. CreateNetworkReader(string url, SeekingReader& reader); + + // Creates a timeline controller reader. + CreateTimelineController(MediaTimelineController& timeline_controller); };
diff --git a/services/media/factory_service/BUILD.gn b/services/media/factory_service/BUILD.gn index 12f60e1..a38ba0d 100644 --- a/services/media/factory_service/BUILD.gn +++ b/services/media/factory_service/BUILD.gn
@@ -28,6 +28,8 @@ "media_sink_impl.h", "media_source_impl.cc", "media_source_impl.h", + "media_timeline_controller_impl.cc", + "media_timeline_controller_impl.h", "network_reader_impl.cc", "network_reader_impl.h", ]
diff --git a/services/media/factory_service/factory_service.cc b/services/media/factory_service/factory_service.cc index 94b4c9b..5d87ad4 100644 --- a/services/media/factory_service/factory_service.cc +++ b/services/media/factory_service/factory_service.cc
@@ -8,6 +8,7 @@ #include "services/media/factory_service/media_player_impl.h" #include "services/media/factory_service/media_sink_impl.h" #include "services/media/factory_service/media_source_impl.h" +#include "services/media/factory_service/media_timeline_controller_impl.h" #include "services/media/factory_service/network_reader_impl.h" namespace mojo { @@ -64,5 +65,11 @@ AddProduct(NetworkReaderImpl::Create(url, reader.Pass(), this)); } +void MediaFactoryService::CreateTimelineController( + InterfaceRequest<MediaTimelineController> timeline_controller) { + AddProduct( + MediaTimelineControllerImpl::Create(timeline_controller.Pass(), this)); +} + } // namespace media } // namespace mojo
diff --git a/services/media/factory_service/factory_service.h b/services/media/factory_service/factory_service.h index 8b4f520..c87e271 100644 --- a/services/media/factory_service/factory_service.h +++ b/services/media/factory_service/factory_service.h
@@ -46,6 +46,9 @@ void CreateNetworkReader(const String& url, InterfaceRequest<SeekingReader> reader) override; + void CreateTimelineController( + InterfaceRequest<MediaTimelineController> timeline_controller) override; + private: BindingSet<MediaFactory> bindings_; };
diff --git a/services/media/factory_service/media_player_impl.cc b/services/media/factory_service/media_player_impl.cc index a963299..dcaa5f6 100644 --- a/services/media/factory_service/media_player_impl.cc +++ b/services/media/factory_service/media_player_impl.cc
@@ -31,7 +31,7 @@ uint64_t version) { MediaPlayerStatusPtr status = MediaPlayerStatus::New(); status->timeline_transform = TimelineTransform::From(timeline_function_); - status->end_of_stream = AllSinksAtEndOfStream(); + status->end_of_stream = end_of_stream_; status->metadata = metadata_.Clone(); callback.Run(version, status.Pass()); }); @@ -41,27 +41,29 @@ ConnectToService(app()->shell(), "mojo:media_factory", GetProxy(&factory_)); factory_->CreateDemux(reader.Pass(), GetProxy(&demux_)); - HandleDemuxMetadataUpdates(); + factory_->CreateTimelineController(GetProxy(&timeline_controller_)); + timeline_controller_->GetControlSite(GetProxy(&timeline_control_site_)); + timeline_control_site_->GetTimelineConsumer(GetProxy(&timeline_consumer_)); + HandleTimelineControlSiteStatusUpdates(); + demux_->Describe([this](mojo::Array<MediaTypePtr> stream_types) { // Populate streams_ and enable the streams we want. std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create(); for (MediaTypePtr& stream_type : stream_types) { - streams_.push_back(std::unique_ptr<Stream>( - new Stream(streams_.size(), stream_type.Pass()))); + streams_.push_back(std::unique_ptr<Stream>(new Stream())); Stream& stream = *streams_.back(); - switch (stream.media_type_->medium) { + switch (stream_type->medium) { case MediaTypeMedium::AUDIO: - stream.enabled_ = true; - PrepareStream(&stream, "mojo:audio_server", - callback_joiner->NewCallback()); + PrepareStream(&stream, streams_.size() - 1, stream_type, + "mojo:audio_server", callback_joiner->NewCallback()); break; case MediaTypeMedium::VIDEO: - stream.enabled_ = true; // TODO(dalesat): Send video somewhere. - PrepareStream(&stream, "nowhere", callback_joiner->NewCallback()); + PrepareStream(&stream, streams_.size() - 1, stream_type, "nowhere", + callback_joiner->NewCallback()); break; // TODO(dalesat): Enable other stream types. default: @@ -91,7 +93,7 @@ if (target_state_ == State::kPlaying) { if (!flushed_) { - SetSinkTimelineTransforms(1, 1); + SetTimelineTransform(1, 1); state_ = State::kWaiting; break; } @@ -99,7 +101,7 @@ flushed_ = false; state_ = State::kWaiting; demux_->Prime([this]() { - SetSinkTimelineTransforms(1, 1); + SetTimelineTransform(1, 1); state_ = State::kWaiting; Update(); }); @@ -109,12 +111,12 @@ case State::kPlaying: if (target_position_ != kUnspecifiedTime || target_state_ == State::kPaused) { - SetSinkTimelineTransforms(1, 0); + SetTimelineTransform(1, 0); state_ = State::kWaiting; break; } - if (AllSinksAtEndOfStream()) { + if (end_of_stream_) { target_state_ = State::kPaused; state_ = State::kPaused; break; @@ -150,62 +152,22 @@ }); } -void MediaPlayerImpl::SetSinkTimelineTransforms(uint32_t reference_delta, - uint32_t subject_delta) { - SetSinkTimelineTransforms( +void MediaPlayerImpl::SetTimelineTransform(uint32_t reference_delta, + uint32_t subject_delta) { + timeline_consumer_->SetTimelineTransform( transform_subject_time_, reference_delta, subject_delta, - Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime); -} + Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime, + [this, subject_delta](bool completed) { + RCHECK(state_ == State::kWaiting); -void MediaPlayerImpl::SetSinkTimelineTransforms( - int64_t subject_time, - uint32_t reference_delta, - uint32_t subject_delta, - int64_t effective_reference_time, - int64_t effective_subject_time) { - std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create(); + if (subject_delta == 0) { + state_ = State::kPaused; + } else { + state_ = State::kPlaying; + } - for (auto& stream : streams_) { - if (stream->enabled_) { - DCHECK(stream->timeline_consumer_); - callback_joiner->Spawn(); - stream->timeline_consumer_->SetTimelineTransform( - subject_time, reference_delta, subject_delta, - effective_reference_time, effective_subject_time, - [this, callback_joiner](bool completed) { - callback_joiner->Complete(); - }); - } - } - - transform_subject_time_ = kUnspecifiedTime; - - callback_joiner->WhenJoined([this, subject_delta]() { - RCHECK(state_ == State::kWaiting); - - if (subject_delta == 0) { - state_ = State::kPaused; - } else { - state_ = State::kPlaying; - } - - Update(); - }); -} - -bool MediaPlayerImpl::AllSinksAtEndOfStream() { - int result = false; - - for (auto& stream : streams_) { - if (stream->enabled_) { - result = stream->end_of_stream_; - if (!result) { - break; - } - } - } - - return result; + Update(); + }); } void MediaPlayerImpl::GetStatus(uint64_t version_last_seen, @@ -229,19 +191,21 @@ } void MediaPlayerImpl::PrepareStream(Stream* stream, + size_t index, + const MediaTypePtr& input_media_type, const String& url, const std::function<void()>& callback) { DCHECK(factory_); - demux_->GetProducer(stream->index_, GetProxy(&stream->encoded_producer_)); + demux_->GetProducer(index, GetProxy(&stream->encoded_producer_)); - if (stream->media_type_->encoding != MediaType::kAudioEncodingLpcm && - stream->media_type_->encoding != MediaType::kVideoEncodingUncompressed) { + if (input_media_type->encoding != MediaType::kAudioEncodingLpcm && + input_media_type->encoding != MediaType::kVideoEncodingUncompressed) { std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create(); // Compressed media. Insert a decoder in front of the sink. The sink would // add its own internal decoder, but we want to test the decoder. - factory_->CreateDecoder(stream->media_type_.Clone(), + factory_->CreateDecoder(input_media_type.Clone(), GetProxy(&stream->decoder_)); MediaConsumerPtr decoder_consumer; @@ -268,7 +232,7 @@ // would work for compressed media as well (the sink would decode), but we // want to test the decoder. stream->decoded_producer_ = stream->encoded_producer_.Pass(); - CreateSink(stream, stream->media_type_, url, callback); + CreateSink(stream, input_media_type, url, callback); } } @@ -281,13 +245,11 @@ DCHECK(factory_); factory_->CreateSink(url, input_media_type.Clone(), GetProxy(&stream->sink_)); - stream->sink_->GetTimelineControlSite( - GetProxy(&stream->timeline_control_site_)); - HandleTimelineControlSiteStatusUpdates(stream); + MediaTimelineControlSitePtr timeline_control_site; + stream->sink_->GetTimelineControlSite(GetProxy(&timeline_control_site)); - stream->timeline_control_site_->GetTimelineConsumer( - GetProxy(&stream->timeline_consumer_)); + timeline_controller_->AddControlSite(timeline_control_site.Pass()); MediaConsumerPtr consumer; stream->sink_->GetConsumer(GetProxy(&consumer)); @@ -313,26 +275,23 @@ } void MediaPlayerImpl::HandleTimelineControlSiteStatusUpdates( - Stream* stream, uint64_t version, MediaTimelineControlSiteStatusPtr status) { if (status) { - // TODO(dalesat): Why does one sink determine timeline_function_? timeline_function_ = status->timeline_transform.To<TimelineFunction>(); - stream->end_of_stream_ = status->end_of_stream; + end_of_stream_ = status->end_of_stream; status_publisher_.SendUpdates(); Update(); } - stream->timeline_control_site_->GetStatus( - version, [this, stream](uint64_t version, - MediaTimelineControlSiteStatusPtr status) { - HandleTimelineControlSiteStatusUpdates(stream, version, status.Pass()); + timeline_control_site_->GetStatus( + version, + [this](uint64_t version, MediaTimelineControlSiteStatusPtr status) { + HandleTimelineControlSiteStatusUpdates(version, status.Pass()); }); } -MediaPlayerImpl::Stream::Stream(size_t index, MediaTypePtr media_type) - : index_(index), media_type_(media_type.Pass()) {} +MediaPlayerImpl::Stream::Stream() {} MediaPlayerImpl::Stream::~Stream() {}
diff --git a/services/media/factory_service/media_player_impl.h b/services/media/factory_service/media_player_impl.h index d9b5be8..6ee1061 100644 --- a/services/media/factory_service/media_player_impl.h +++ b/services/media/factory_service/media_player_impl.h
@@ -15,6 +15,7 @@ #include "mojo/services/media/common/interfaces/media_transport.mojom.h" #include "mojo/services/media/control/interfaces/media_factory.mojom.h" #include "mojo/services/media/core/interfaces/seeking_reader.mojom.h" +#include "mojo/services/media/core/interfaces/timeline_controller.mojom.h" #include "services/media/common/mojo_publisher.h" #include "services/media/factory_service/factory_service.h" #include "services/media/framework/util/callback_joiner.h" @@ -54,16 +55,13 @@ }; struct Stream { - Stream(size_t index, MediaTypePtr media_type); + Stream(); ~Stream(); - size_t index_; - bool enabled_ = false; - bool end_of_stream_ = false; - MediaTypePtr media_type_; + // TODO(dalesat): Have the sink enlist the decoder. MediaTypeConverterPtr decoder_; MediaSinkPtr sink_; - MediaTimelineControlSitePtr timeline_control_site_; - TimelineConsumerPtr timeline_consumer_; + // The following fields are just temporaries used to solve lambda capture + // problems. MediaProducerPtr encoded_producer_; MediaProducerPtr decoded_producer_; }; @@ -81,25 +79,15 @@ // Handles seeking in paused state with flushed pipeline. void WhenFlushedAndSeeking(); - // Sets the timeline transforms on all the sinks. transform_subject_time_ is - // used for the subject_time, and the effective_reference_time is now plus an - // epsilon. - void SetSinkTimelineTransforms(uint32_t reference_delta, - uint32_t subject_delta); - - // Sets the timeline transforms on all the sinks. - void SetSinkTimelineTransforms(int64_t subject_time, - uint32_t reference_delta, - uint32_t subject_delta, - int64_t effective_reference_time, - int64_t effective_subject_time); - - // Determines if all the enabled sinks have reached end-of-stream. Returns - // false if there are no enabled streams. - bool AllSinksAtEndOfStream(); + // Sets the timeline transform. transform_subject_time_ is used for the + // subject_time, and the effective_reference_time is now plus + // kMinimumLeadTime. + void SetTimelineTransform(uint32_t reference_delta, uint32_t subject_delta); // Prepares a stream. void PrepareStream(Stream* stream, + size_t index, + const MediaTypePtr& input_media_type, const String& url, const std::function<void()>& callback); @@ -115,19 +103,22 @@ uint64_t version = MediaDemux::kInitialMetadata, MediaMetadataPtr metadata = nullptr); - // Handles a status update from a control site. When called with the default + // Handles a status update from the control site. When called with the default // argument values, initiates control site. status updates. void HandleTimelineControlSiteStatusUpdates( - Stream* stream, uint64_t version = MediaTimelineControlSite::kInitialStatus, MediaTimelineControlSiteStatusPtr status = nullptr); MediaFactoryPtr factory_; MediaDemuxPtr demux_; + MediaTimelineControllerPtr timeline_controller_; + MediaTimelineControlSitePtr timeline_control_site_; + TimelineConsumerPtr timeline_consumer_; std::vector<std::unique_ptr<Stream>> streams_; State state_ = State::kWaiting; State target_state_ = State::kPaused; bool flushed_ = true; + bool end_of_stream_ = false; int64_t target_position_ = kUnspecifiedTime; int64_t transform_subject_time_ = kUnspecifiedTime; TimelineFunction timeline_function_;
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 0000000..54bb564 --- /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
diff --git a/services/media/factory_service/media_timeline_controller_impl.h b/services/media/factory_service/media_timeline_controller_impl.h new file mode 100644 index 0000000..7dca89d --- /dev/null +++ b/services/media/factory_service/media_timeline_controller_impl.h
@@ -0,0 +1,161 @@ +// 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. + +#ifndef MOJO_SERVICES_MEDIA_FACTORY_TIMELINE_CONTROLLER_IMPL_H_ +#define MOJO_SERVICES_MEDIA_FACTORY_TIMELINE_CONTROLLER_IMPL_H_ + +#include <memory> +#include <vector> + +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/services/media/common/cpp/local_time.h" +#include "mojo/services/media/common/cpp/timeline.h" +#include "mojo/services/media/common/cpp/timeline_function.h" +#include "mojo/services/media/core/interfaces/timeline_controller.mojom.h" +#include "services/media/common/mojo_publisher.h" +#include "services/media/factory_service/factory_service.h" +#include "services/media/framework/util/callback_joiner.h" + +namespace mojo { +namespace media { + +// Mojo agent that controls timing in a graph. +class MediaTimelineControllerImpl + : public MediaFactoryService::Product<MediaTimelineController>, + public MediaTimelineController, + public MediaTimelineControlSite, + public TimelineConsumer { + public: + static std::shared_ptr<MediaTimelineControllerImpl> Create( + InterfaceRequest<MediaTimelineController> request, + MediaFactoryService* owner); + + ~MediaTimelineControllerImpl() override; + + // MediaTimelineController implementation. + void AddControlSite( + InterfaceHandle<MediaTimelineControlSite> control_site) override; + + void GetControlSite( + InterfaceRequest<MediaTimelineControlSite> control_site) override; + + // MediaTimelineControlSite implementation. + void GetStatus(uint64_t version_last_seen, + const GetStatusCallback& callback) override; + + void GetTimelineConsumer( + InterfaceRequest<TimelineConsumer> timeline_consumer) override; + + // TimelineConsumer implementation. + void 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) override; + + private: + static constexpr int64_t kDefaultLeadTime = Timeline::ns_from_ms(30); + + // Relationship to subordinate control site. + struct SiteState { + SiteState(MediaTimelineControllerImpl* parent, + MediaTimelineControlSitePtr site); + + SiteState(SiteState&& other); + + ~SiteState(); + + void HandleStatusUpdates( + uint64_t version = MediaTimelineControlSite::kInitialStatus, + MediaTimelineControlSiteStatusPtr status = nullptr); + + MediaTimelineControllerImpl* parent_; + MediaTimelineControlSitePtr site_; + TimelineConsumerPtr consumer_; + bool end_of_stream_ = false; + }; + + class TimelineTransition + : public std::enable_shared_from_this<TimelineTransition> { + public: + TimelineTransition(int64_t reference_time, + int64_t subject_time, + uint32_t reference_delta, + uint32_t subject_delta, + const SetTimelineTransformCallback& callback); + + ~TimelineTransition(); + + // Calls returns a new callback for a child (site) transition. THIS METHOD + // WILL ONLY WORK IF THERE IS ALREADY A SHARED POINTER TO THIS OBJECT. + std::function<void(bool)> NewCallback() { + callback_joiner_.Spawn(); + + std::shared_ptr<TimelineTransition> this_ptr = shared_from_this(); + DCHECK(!this_ptr.unique()); + + return [this_ptr](bool completed) { + DCHECK(this_ptr); + if (!completed && !this_ptr->cancelled_) { + LOG(WARNING) << "A site transition was cancelled unexpectedly."; + } + this_ptr->callback_joiner_.Complete(); + }; + } + + // Cancels this transition. + void Cancel() { + DCHECK(!cancelled_); + cancelled_ = true; + DCHECK(callback_.is_null()); + callback_.Run(false); + callback_.reset(); + completed_callback_.reset(); + } + + // Specifies a callback to be called if and when the transition is complete. + // The callback will never be called if the transition is cancelled. + void WhenCompleted(const mojo::Callback<void()>& completed_callback) { + DCHECK(completed_callback_.is_null()); + if (callback_.is_null() && !cancelled_) { + completed_callback.Run(); + } else { + completed_callback_ = completed_callback; + } + } + + // Returns the TimelineFunction that will result from this transition. + const TimelineFunction& new_timeline_function() const { + return new_timeline_function_; + } + + private: + TimelineFunction new_timeline_function_; + SetTimelineTransformCallback callback_; + CallbackJoiner callback_joiner_; + bool cancelled_ = false; + Callback<void()> completed_callback_; + }; + + MediaTimelineControllerImpl(InterfaceRequest<MediaTimelineController> request, + MediaFactoryService* owner); + + // Takes action when a site changes its end-of-stream value. + void HandleSiteEndOfStreamChange(); + + Binding<MediaTimelineControlSite> control_site_binding_; + Binding<TimelineConsumer> consumer_binding_; + MojoPublisher<GetStatusCallback> status_publisher_; + std::vector<SiteState> site_states_; + TimelineFunction current_timeline_function_; + bool end_of_stream_; + std::weak_ptr<TimelineTransition> pending_transition_; +}; + +} // namespace media +} // namespace mojo + +#endif // MOJO_SERVICES_MEDIA_FACTORY_TIMELINE_CONTROLLER_IMPL_H_
diff --git a/services/media/framework/util/callback_joiner.h b/services/media/framework/util/callback_joiner.h index 0c035ba..35fb353 100644 --- a/services/media/framework/util/callback_joiner.h +++ b/services/media/framework/util/callback_joiner.h
@@ -73,7 +73,7 @@ DCHECK(counter_ != 0); --counter_; if (counter_ == 0) { - mojo::Callback<void()> join_callback; + Callback<void()> join_callback; join_callback = join_callback_; join_callback_.reset(); join_callback.Run(); @@ -97,7 +97,7 @@ // immediately. If child operations are pending, the callback is copied. The // copy called later (and reset) when all child oeprations have completed. // Only one callback at a time can be registered with WhenJoined. - void WhenJoined(const mojo::Callback<void()>& join_callback) { + void WhenJoined(const Callback<void()>& join_callback) { DCHECK(join_callback_.is_null()); if (counter_ == 0) { join_callback.Run(); @@ -116,7 +116,7 @@ private: size_t counter_ = 0; - mojo::Callback<void()> join_callback_; + Callback<void()> join_callback_; }; } // namespace media
diff --git a/services/media/framework_mojo/mojo_producer.h b/services/media/framework_mojo/mojo_producer.h index fb8b12a..ed78add 100644 --- a/services/media/framework_mojo/mojo_producer.h +++ b/services/media/framework_mojo/mojo_producer.h
@@ -8,7 +8,6 @@ #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" #include "mojo/common/binding_set.h" -#include "mojo/services/media/common/interfaces/media_state.mojom.h" #include "mojo/services/media/common/interfaces/media_transport.mojom.h" #include "services/media/framework/models/active_sink.h" #include "services/media/framework_mojo/mojo_allocator.h"
diff --git a/services/media/framework_mojo/mojo_pull_mode_producer.cc b/services/media/framework_mojo/mojo_pull_mode_producer.cc index f953aa5..f0101b9 100644 --- a/services/media/framework_mojo/mojo_pull_mode_producer.cc +++ b/services/media/framework_mojo/mojo_pull_mode_producer.cc
@@ -9,8 +9,7 @@ namespace media { MojoPullModeProducer::MojoPullModeProducer() - : state_(MediaState::UNPREPARED), - demand_(Demand::kNegative), + : demand_(Demand::kNegative), pts_(0), cached_packet_(nullptr) {} @@ -28,13 +27,6 @@ mojo_allocator_.InitNew(256 * 1024); // TODO(dalesat): Made up! } - { - base::AutoLock lock(lock_); - if (state_ == MediaState::UNPREPARED) { - state_ = MediaState::PAUSED; - } - } - callback.Run(mojo_allocator_.GetDuplicateHandle()); DCHECK(!cached_packet_); @@ -52,11 +44,11 @@ { base::AutoLock lock(lock_); - if (state_ == MediaState::UNPREPARED) { + //if (state_ == MediaState::UNPREPARED) { // The consumer has yet to call GetBuffer. This request will have to wait. - pending_pulls_.push_back(callback); - return; - } + // pending_pulls_.push_back(callback); + // return; + //} DCHECK(mojo_allocator_.initialized()); @@ -111,7 +103,6 @@ Demand MojoPullModeProducer::SupplyPacket(PacketPtr packet) { base::AutoLock lock(lock_); DCHECK(demand_ != Demand::kNegative) << "packet pushed with negative demand"; - DCHECK(state_ != MediaState::ENDED) << "packet pushed after end-of-stream"; DCHECK(!cached_packet_); @@ -119,7 +110,6 @@ // happen if a pull client disconnects unexpectedly. if (bindings_.size() == 0) { demand_ = Demand::kNegative; - state_ = MediaState::UNPREPARED; // TODO(dalesat): More shutdown? return demand_; } @@ -152,11 +142,11 @@ DCHECK(!callback.is_null()); lock_.AssertAcquired(); - if (state_ == MediaState::ENDED) { + //if (state_ == MediaState::ENDED) { // At end-of-stream. Respond with empty end-of-stream packet. - HandlePullWithPacketUnsafe(callback, Packet::CreateEndOfStream(pts_)); - return true; - } + // HandlePullWithPacketUnsafe(callback, Packet::CreateEndOfStream(pts_)); + // return true; + //} if (!cached_packet_) { // Waiting for packet or end-of-stream indication.
diff --git a/services/media/framework_mojo/mojo_pull_mode_producer.h b/services/media/framework_mojo/mojo_pull_mode_producer.h index 0d27a74..741458c 100644 --- a/services/media/framework_mojo/mojo_pull_mode_producer.h +++ b/services/media/framework_mojo/mojo_pull_mode_producer.h
@@ -9,7 +9,6 @@ #include "base/synchronization/lock.h" #include "mojo/common/binding_set.h" -#include "mojo/services/media/common/interfaces/media_state.mojom.h" #include "mojo/services/media/common/interfaces/media_transport.mojom.h" #include "services/media/framework/models/active_sink.h" #include "services/media/framework_mojo/mojo_allocator.h" @@ -72,7 +71,6 @@ mutable base::Lock lock_; // THE FIELDS BELOW SHOULD ONLY BE ACCESSED WITH lock_ TAKEN. - MediaState state_; Demand demand_; int64_t pts_;