blob: ee33b06a47359f9d292026e2d6539b96872fbf73 [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/public/cpp/application/connect.h"
#include "mojo/services/media/common/cpp/timeline.h"
#include "services/media/factory_service/media_player_impl.h"
#include "services/media/framework/parts/reader.h"
#include "services/media/framework/util/callback_joiner.h"
namespace mojo {
namespace media {
// static
std::shared_ptr<MediaPlayerImpl> MediaPlayerImpl::Create(
InterfaceHandle<SeekingReader> reader,
InterfaceHandle<MediaRenderer> audio_renderer,
InterfaceHandle<MediaRenderer> video_renderer,
InterfaceRequest<MediaPlayer> request,
MediaFactoryService* owner) {
return std::shared_ptr<MediaPlayerImpl>(
new MediaPlayerImpl(reader.Pass(), audio_renderer.Pass(),
video_renderer.Pass(), request.Pass(), owner));
}
MediaPlayerImpl::MediaPlayerImpl(InterfaceHandle<SeekingReader> reader,
InterfaceHandle<MediaRenderer> audio_renderer,
InterfaceHandle<MediaRenderer> video_renderer,
InterfaceRequest<MediaPlayer> request,
MediaFactoryService* owner)
: MediaFactoryService::Product<MediaPlayer>(this, request.Pass(), owner) {
DCHECK(reader);
status_publisher_.SetCallbackRunner([this](const GetStatusCallback& callback,
uint64_t version) {
MediaPlayerStatusPtr status = MediaPlayerStatus::New();
status->timeline_transform = TimelineTransform::From(timeline_function_);
status->end_of_stream = end_of_stream_;
status->metadata = metadata_.Clone();
callback.Run(version, status.Pass());
});
state_ = State::kWaiting;
ConnectToService(owner->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();
audio_renderer_ = audio_renderer.Pass();
video_renderer_ = video_renderer.Pass();
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()));
Stream& stream = *streams_.back();
switch (stream_type->medium) {
case MediaTypeMedium::AUDIO:
stream.renderer_ = audio_renderer_.Pass();
PrepareStream(&stream, streams_.size() - 1, stream_type,
callback_joiner->NewCallback());
break;
case MediaTypeMedium::VIDEO:
stream.renderer_ = video_renderer_.Pass();
PrepareStream(&stream, streams_.size() - 1, stream_type,
callback_joiner->NewCallback());
break;
// TODO(dalesat): Enable other stream types.
default:
break;
}
}
callback_joiner->WhenJoined([this]() {
// The enabled streams are prepared.
factory_.reset();
state_ = State::kFlushed;
Update();
});
});
}
MediaPlayerImpl::~MediaPlayerImpl() {}
void MediaPlayerImpl::Update() {
// This method is called whenever we might want to take action based on the
// current state and recent events. The current state is in |state_|. Recent
// events are recorded in |target_state_|, which indicates what state we'd
// like to transition to, |target_position_|, which can indicate a position
// we'd like to stream to, and |end_of_stream_| which tells us we've reached
// end of stream.
//
// The states are as follows:
//
// |kWaiting| - Indicates that we've done something asynchronous, and no
// further action should be taken by the state machine until that
// something completes (at which point the callback will change
// the state and call |Update|).
// |kFlushed| - Indicates that presentation time is not progressing and that
// the pipeline is not primed with packets. This is the initial
// state and the state we transition to in preparation for
// seeking. A seek is currently only done when when the pipeline
// is clear of packets.
// |kPrimed| - Indicates that presentation time is not progressing and that
// the pipeline is primed with packets. We transition to this
// state when the client calls |Pause|, either from |kFlushed| or
// |kPlaying| state.
// |kPlaying| - Indicates that presentation time is progressing and there are
// packets in the pipeline. We transition to this state when the
// client calls |Play|. If we're in |kFlushed| when |Play| is
// called, we transition through |kPrimed| state.
//
// The while loop that surrounds all the logic below is there because, after
// taking some action and transitioning to a new state, we may want to check
// to see if there's more to do in the new state. You'll also notice that
// the callback lambdas generally call |Update|.
while (true) {
switch (state_) {
case State::kFlushed:
// Presentation time is not progressing, and the pipeline is clear of
// packets.
if (target_position_ != kUnspecifiedTime) {
// We want to seek. Enter |kWaiting| state until the operation is
// complete.
state_ = State::kWaiting;
demux_->Seek(target_position_, [this]() {
transform_subject_time_ = target_position_;
target_position_ = kUnspecifiedTime;
state_ = State::kFlushed;
// Back in |kFlushed|. Call |Update| to see if there's further
// action to be taken.
Update();
});
// Done for now. We're in kWaiting, and the callback will call Update
// when the Seek call is complete.
return;
}
if (target_state_ == State::kPlaying ||
target_state_ == State::kPrimed) {
// We want to transition to |kPrimed| or to |kPlaying|, for which
// |kPrimed| is a prerequisite. We enter |kWaiting| state, issue the
// |Prime| request and transition to |kPrimed| when the operation is
// complete.
state_ = State::kWaiting;
demux_->Prime([this]() {
state_ = State::kPrimed;
// Now we're in |kPrimed|. Call |Update| to see if there's further
// action to be taken.
Update();
});
// Done for now. We're in |kWaiting|, and the callback will call
// |Update| when the prime is complete.
return;
}
// No interesting events to respond to. Done for now.
return;
case State::kPrimed:
// Presentation time is not progressing, and the pipeline is primed with
// packets.
if (target_position_ != kUnspecifiedTime ||
target_state_ == State::kFlushed) {
// Either we want to seek or just want to transition to |kFlushed|.
// We transition to |kWaiting|, issue the |Flush| request and
// transition to |kFlushed| when the operation is complete.
state_ = State::kWaiting;
demux_->Flush([this]() {
state_ = State::kFlushed;
// Now we're in |kFlushed|. Call |Update| to see if there's further
// action to be taken.
Update();
});
// Done for now. We're in |kWaiting|, and the callback will call
// |Update| when the flush is complete.
return;
}
if (target_state_ == State::kPlaying) {
// We want to transition to |kPlaying|. Enter |kWaiting|, start the
// presentation timeline and transition to |kPlaying| when the
// operation completes.
state_ = State::kWaiting;
timeline_consumer_->SetTimelineTransform(
transform_subject_time_, 1, 1,
Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime,
[this](bool completed) {
state_ = State::kPlaying;
// Now we're in |kPlaying|. Call |Update| to see if there's
// further
// action to be taken.
Update();
});
transform_subject_time_ = kUnspecifiedTime;
return;
}
// No interesting events to respond to. Done for now.
return;
case State::kPlaying:
// Presentation time is progressing, and packets are moving through
// the pipeline.
if (target_position_ != kUnspecifiedTime ||
target_state_ == State::kFlushed ||
target_state_ == State::kPrimed) {
// Either we want to seek or we want to stop playback. In either case,
// we need to enter |kWaiting|, stop the presentation timeline and
// transition to |kPrimed| when the operation completes.
state_ = State::kWaiting;
timeline_consumer_->SetTimelineTransform(
transform_subject_time_, 1, 0,
Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime,
[this](bool completed) {
state_ = State::kPrimed;
// Now we're in |kPrimed|. Call |Update| to see if there's
// further
// action to be taken.
Update();
});
transform_subject_time_ = kUnspecifiedTime;
return;
}
if (end_of_stream_) {
// We've reached end of stream. The presentation timeline stops by
// itself, so we just need to transition to |kPrimed|.
target_state_ = State::kPrimed;
state_ = State::kPrimed;
// Loop around to check if there's more work to do.
break;
}
// No interesting events to respond to. Done for now.
return;
case State::kWaiting:
// Waiting for some async operation. Nothing to do until it completes.
return;
}
}
}
void MediaPlayerImpl::GetStatus(uint64_t version_last_seen,
const GetStatusCallback& callback) {
status_publisher_.Get(version_last_seen, callback);
}
void MediaPlayerImpl::Play() {
target_state_ = State::kPlaying;
Update();
}
void MediaPlayerImpl::Pause() {
target_state_ = State::kPrimed;
Update();
}
void MediaPlayerImpl::Seek(int64_t position) {
target_position_ = position;
Update();
}
void MediaPlayerImpl::PrepareStream(Stream* stream,
size_t index,
const MediaTypePtr& input_media_type,
const std::function<void()>& callback) {
DCHECK(factory_);
demux_->GetProducer(index, GetProxy(&stream->encoded_producer_));
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(input_media_type.Clone(),
GetProxy(&stream->decoder_));
MediaConsumerPtr decoder_consumer;
stream->decoder_->GetConsumer(GetProxy(&decoder_consumer));
callback_joiner->Spawn();
stream->encoded_producer_->Connect(decoder_consumer.Pass(),
[stream, callback_joiner]() {
stream->encoded_producer_.reset();
callback_joiner->Complete();
});
callback_joiner->Spawn();
stream->decoder_->GetOutputType(
[this, stream, callback_joiner](MediaTypePtr output_type) {
stream->decoder_->GetProducer(GetProxy(&stream->decoded_producer_));
CreateSink(stream, output_type, callback_joiner->NewCallback());
callback_joiner->Complete();
});
callback_joiner->WhenJoined(callback);
} else {
// Uncompressed media. Connect the demux stream directly to the sink. This
// 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, input_media_type, callback);
}
}
void MediaPlayerImpl::CreateSink(Stream* stream,
const MediaTypePtr& input_media_type,
const std::function<void()>& callback) {
DCHECK(input_media_type);
DCHECK(stream->decoded_producer_);
DCHECK(factory_);
factory_->CreateSink(stream->renderer_.Pass(), input_media_type.Clone(),
GetProxy(&stream->sink_));
MediaTimelineControlSitePtr timeline_control_site;
stream->sink_->GetTimelineControlSite(GetProxy(&timeline_control_site));
timeline_controller_->AddControlSite(timeline_control_site.Pass());
MediaConsumerPtr consumer;
stream->sink_->GetConsumer(GetProxy(&consumer));
stream->decoded_producer_->Connect(consumer.Pass(),
[this, callback, stream]() {
stream->decoded_producer_.reset();
callback();
});
}
void MediaPlayerImpl::HandleDemuxMetadataUpdates(uint64_t version,
MediaMetadataPtr metadata) {
if (metadata) {
metadata_ = metadata.Pass();
status_publisher_.SendUpdates();
}
demux_->GetMetadata(version,
[this](uint64_t version, MediaMetadataPtr metadata) {
HandleDemuxMetadataUpdates(version, metadata.Pass());
});
}
void MediaPlayerImpl::HandleTimelineControlSiteStatusUpdates(
uint64_t version,
MediaTimelineControlSiteStatusPtr status) {
if (status) {
timeline_function_ = status->timeline_transform.To<TimelineFunction>();
end_of_stream_ = status->end_of_stream;
status_publisher_.SendUpdates();
Update();
}
timeline_control_site_->GetStatus(
version,
[this](uint64_t version, MediaTimelineControlSiteStatusPtr status) {
HandleTimelineControlSiteStatusUpdates(version, status.Pass());
});
}
MediaPlayerImpl::Stream::Stream() {}
MediaPlayerImpl::Stream::~Stream() {}
} // namespace media
} // namespace mojo