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