blob: 8fa10af8d20ed73e2bccbb837b5487b225550cfe [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 "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,
InterfaceRequest<MediaPlayer> request,
MediaFactoryService* owner) {
return std::shared_ptr<MediaPlayerImpl>(
new MediaPlayerImpl(reader.Pass(), request.Pass(), owner));
}
MediaPlayerImpl::MediaPlayerImpl(InterfaceHandle<SeekingReader> reader,
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->state = reported_media_state_;
status->timeline_transform = transform_.Clone();
status->metadata = metadata_.Clone();
callback.Run(version, status.Pass());
});
state_ = State::kWaiting;
ConnectToService(app()->shell(), "mojo:media_factory", GetProxy(&factory_));
factory_->CreateDemux(reader.Pass(), GetProxy(&demux_));
HandleDemuxMetadataUpdates();
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())));
Stream& stream = *streams_.back();
switch (stream.media_type_->medium) {
case MediaTypeMedium::AUDIO:
stream.enabled_ = true;
PrepareStream(&stream, "mojo:audio_server",
callback_joiner->NewCallback());
break;
case MediaTypeMedium::VIDEO:
stream.enabled_ = true;
// TODO(dalesat): Send video somewhere.
PrepareStream(&stream, "nowhere", callback_joiner->NewCallback());
break;
// TODO(dalesat): Enable other stream types.
default:
break;
}
}
callback_joiner->WhenJoined([this]() {
// The enabled streams are prepared.
factory_.reset();
SetReportedMediaState(MediaState::PAUSED);
state_ = State::kPaused;
Update();
});
});
}
MediaPlayerImpl::~MediaPlayerImpl() {}
void MediaPlayerImpl::Update() {
while (true) {
switch (state_) {
case State::kPaused:
if (target_position_ != kNotSeeking) {
WhenPausedAndSeeking();
break;
}
if (target_state_ == MediaState::PLAYING) {
if (!flushed_) {
ChangeSinkStates(MediaState::PLAYING);
state_ = State::kWaitingForSinksToPlay;
break;
}
flushed_ = false;
state_ = State::kWaiting;
demux_->Prime([this]() {
ChangeSinkStates(MediaState::PLAYING);
state_ = State::kWaitingForSinksToPlay;
Update();
});
}
return;
case State::kWaitingForSinksToPlay:
if (AllSinksAre(SinkState::kPlayingOrEnded)) {
state_ = State::kPlaying;
if (target_state_ == MediaState::PLAYING) {
SetReportedMediaState(MediaState::PLAYING);
}
}
return;
case State::kPlaying:
if (target_position_ != kNotSeeking ||
target_state_ == MediaState::PAUSED) {
ChangeSinkStates(MediaState::PAUSED);
state_ = State::kWaitingForSinksToPause;
break;
}
if (AllSinksAre(SinkState::kEnded)) {
target_state_ = MediaState::ENDED;
SetReportedMediaState(MediaState::ENDED);
state_ = State::kPaused;
break;
}
return;
case State::kWaitingForSinksToPause:
if (AllSinksAre(SinkState::kPausedOrEnded)) {
if (target_state_ == MediaState::PAUSED) {
if (AllSinksAre(SinkState::kEnded)) {
SetReportedMediaState(MediaState::ENDED);
} else {
SetReportedMediaState(MediaState::PAUSED);
}
}
state_ = State::kPaused;
break;
}
return;
case State::kWaiting:
return;
}
}
}
void MediaPlayerImpl::WhenPausedAndSeeking() {
if (!flushed_) {
state_ = State::kWaiting;
demux_->Flush([this]() {
flushed_ = true;
WhenFlushedAndSeeking();
});
} else {
WhenFlushedAndSeeking();
}
}
void MediaPlayerImpl::WhenFlushedAndSeeking() {
state_ = State::kWaiting;
DCHECK(target_position_ != kNotSeeking);
demux_->Seek(target_position_, [this]() {
target_position_ = kNotSeeking;
state_ = State::kPaused;
Update();
});
}
void MediaPlayerImpl::ChangeSinkStates(MediaState media_state) {
for (auto& stream : streams_) {
if (stream->enabled_) {
if (media_state == MediaState::PAUSED) {
stream->sink_->Pause();
} else {
stream->sink_->Play();
}
}
}
}
bool MediaPlayerImpl::AllSinksAre(SinkState sink_state) {
for (auto& stream : streams_) {
if (stream->enabled_) {
switch (sink_state) {
case SinkState::kPaused:
if (stream->state_ != MediaState::PAUSED) {
return false;
}
break;
case SinkState::kPlaying:
if (stream->state_ != MediaState::PLAYING) {
return false;
}
break;
case SinkState::kEnded:
if (stream->state_ != MediaState::ENDED) {
return false;
}
break;
case SinkState::kPausedOrEnded:
if (stream->state_ != MediaState::PAUSED &&
stream->state_ != MediaState::ENDED) {
return false;
}
break;
case SinkState::kPlayingOrEnded:
if (stream->state_ != MediaState::PLAYING &&
stream->state_ != MediaState::ENDED) {
return false;
}
break;
}
}
}
return true;
}
void MediaPlayerImpl::SetReportedMediaState(MediaState media_state) {
if (reported_media_state_ != media_state) {
reported_media_state_ = media_state;
status_publisher_.SendUpdates();
}
}
void MediaPlayerImpl::GetStatus(uint64_t version_last_seen,
const GetStatusCallback& callback) {
status_publisher_.Get(version_last_seen, callback);
}
void MediaPlayerImpl::Play() {
target_state_ = MediaState::PLAYING;
Update();
}
void MediaPlayerImpl::Pause() {
target_state_ = MediaState::PAUSED;
Update();
}
void MediaPlayerImpl::Seek(int64_t position) {
target_position_ = position;
Update();
}
void MediaPlayerImpl::PrepareStream(Stream* stream,
const String& url,
const std::function<void()>& callback) {
DCHECK(factory_);
demux_->GetProducer(stream->index_, GetProxy(&stream->encoded_producer_));
if (stream->media_type_->encoding != MediaType::kAudioEncodingLpcm &&
stream->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(),
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, url, callback_joiner](MediaTypePtr output_type) {
stream->decoder_->GetProducer(GetProxy(&stream->decoded_producer_));
CreateSink(stream, output_type, url, 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, stream->media_type_, url, callback);
}
}
void MediaPlayerImpl::CreateSink(Stream* stream,
const MediaTypePtr& input_media_type,
const String& url,
const std::function<void()>& callback) {
DCHECK(input_media_type);
DCHECK(stream->decoded_producer_);
DCHECK(factory_);
factory_->CreateSink(url, input_media_type.Clone(), GetProxy(&stream->sink_));
MediaConsumerPtr consumer;
stream->sink_->GetConsumer(GetProxy(&consumer));
stream->decoded_producer_->Connect(
consumer.Pass(), [this, callback, stream]() {
stream->decoded_producer_.reset();
DCHECK(stream->state_ == MediaState::UNPREPARED);
DCHECK(reported_media_state_ == MediaState::UNPREPARED ||
reported_media_state_ == MediaState::FAULT);
stream->state_ = MediaState::PAUSED;
HandleSinkStatusUpdates(stream);
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::HandleSinkStatusUpdates(Stream* stream,
uint64_t version,
MediaSinkStatusPtr status) {
if (status && status->state > MediaState::UNPREPARED) {
// We transition to PAUSED when Connect completes.
DCHECK(stream->state_ > MediaState::UNPREPARED);
stream->state_ = status->state;
transform_ = status->timeline_transform.Pass();
status_publisher_.SendUpdates();
Update();
}
stream->sink_->GetStatus(
version, [this, stream](uint64_t version, MediaSinkStatusPtr status) {
HandleSinkStatusUpdates(stream, version, status.Pass());
});
}
MediaPlayerImpl::Stream::Stream(size_t index, MediaTypePtr media_type)
: index_(index), media_type_(media_type.Pass()) {}
MediaPlayerImpl::Stream::~Stream() {}
} // namespace media
} // namespace mojo