blob: 46c5a42b97c9c959b5c6531fa973b20a46045bd4 [file] [log] [blame]
// Copyright 2013 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 "media/base/text_renderer.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/demuxer.h"
#include "media/base/text_cue.h"
namespace media {
TextRenderer::TextRenderer(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const AddTextTrackCB& add_text_track_cb)
: task_runner_(task_runner),
add_text_track_cb_(add_text_track_cb),
state_(kUninitialized),
pending_read_count_(0),
weak_factory_(this) {}
TextRenderer::~TextRenderer() {
DCHECK(task_runner_->BelongsToCurrentThread());
text_track_state_map_.clear();
if (!pause_cb_.is_null())
base::ResetAndReturn(&pause_cb_).Run();
}
void TextRenderer::Initialize(const base::Closure& ended_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!ended_cb.is_null());
DCHECK_EQ(kUninitialized, state_) << "state_ " << state_;
DCHECK(text_track_state_map_.empty());
DCHECK_EQ(pending_read_count_, 0);
DCHECK(pending_eos_set_.empty());
DCHECK(ended_cb_.is_null());
ended_cb_ = ended_cb;
state_ = kPaused;
}
void TextRenderer::StartPlaying() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPaused) << "state_ " << state_;
for (auto itr = text_track_state_map_.begin();
itr != text_track_state_map_.end(); ++itr) {
TextTrackState* state = itr->second.get();
if (state->read_state == TextTrackState::kReadPending) {
DCHECK_GT(pending_read_count_, 0);
continue;
}
Read(state, itr->first);
}
state_ = kPlaying;
}
void TextRenderer::Pause(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kPlaying || state_ == kEnded) << "state_ " << state_;
DCHECK_GE(pending_read_count_, 0);
if (pending_read_count_ == 0) {
state_ = kPaused;
task_runner_->PostTask(FROM_HERE, callback);
return;
}
pause_cb_ = callback;
state_ = kPausePending;
}
void TextRenderer::Flush(const base::Closure& callback) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(pending_read_count_, 0);
DCHECK(state_ == kPaused) << "state_ " << state_;
for (auto itr = text_track_state_map_.begin();
itr != text_track_state_map_.end(); ++itr) {
pending_eos_set_.insert(itr->first);
itr->second->text_ranges_.Reset();
}
DCHECK_EQ(pending_eos_set_.size(), text_track_state_map_.size());
task_runner_->PostTask(FROM_HERE, callback);
}
void TextRenderer::AddTextStream(DemuxerStream* text_stream,
const TextTrackConfig& config) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ != kUninitialized) << "state_ " << state_;
DCHECK(text_track_state_map_.find(text_stream) ==
text_track_state_map_.end());
DCHECK(pending_eos_set_.find(text_stream) ==
pending_eos_set_.end());
AddTextTrackDoneCB done_cb =
BindToCurrentLoop(base::Bind(&TextRenderer::OnAddTextTrackDone,
weak_factory_.GetWeakPtr(),
text_stream));
add_text_track_cb_.Run(config, done_cb);
}
void TextRenderer::RemoveTextStream(DemuxerStream* text_stream) {
DCHECK(task_runner_->BelongsToCurrentThread());
auto itr = text_track_state_map_.find(text_stream);
DCHECK(itr != text_track_state_map_.end());
TextTrackState* state = itr->second.get();
DCHECK_EQ(state->read_state, TextTrackState::kReadIdle);
text_track_state_map_.erase(itr);
pending_eos_set_.erase(text_stream);
}
bool TextRenderer::HasTracks() const {
DCHECK(task_runner_->BelongsToCurrentThread());
return !text_track_state_map_.empty();
}
void TextRenderer::BufferReady(
DemuxerStream* stream,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& input) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(status, DemuxerStream::kConfigChanged);
if (status == DemuxerStream::kAborted) {
DCHECK(!input.get());
DCHECK_GT(pending_read_count_, 0);
DCHECK(pending_eos_set_.find(stream) != pending_eos_set_.end());
auto itr = text_track_state_map_.find(stream);
DCHECK(itr != text_track_state_map_.end());
TextTrackState* state = itr->second.get();
DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
--pending_read_count_;
state->read_state = TextTrackState::kReadIdle;
switch (state_) {
case kPlaying:
return;
case kPausePending:
if (pending_read_count_ == 0) {
state_ = kPaused;
base::ResetAndReturn(&pause_cb_).Run();
}
return;
case kPaused:
case kUninitialized:
case kEnded:
NOTREACHED();
return;
}
NOTREACHED();
return;
}
if (input->end_of_stream()) {
CueReady(stream, NULL);
return;
}
DCHECK_EQ(status, DemuxerStream::kOk);
DCHECK_GE(input->side_data_size(), 2u);
// The side data contains both the cue id and cue settings,
// each terminated with a NUL.
const char* id_ptr = reinterpret_cast<const char*>(input->side_data());
size_t id_len = strlen(id_ptr);
std::string id(id_ptr, id_len);
const char* settings_ptr = id_ptr + id_len + 1;
size_t settings_len = strlen(settings_ptr);
std::string settings(settings_ptr, settings_len);
// The cue payload is stored in the data-part of the input buffer.
std::string text(input->data(), input->data() + input->data_size());
scoped_refptr<TextCue> text_cue(
new TextCue(input->timestamp(),
input->duration(),
id,
settings,
text));
CueReady(stream, text_cue);
}
void TextRenderer::CueReady(
DemuxerStream* text_stream,
const scoped_refptr<TextCue>& text_cue) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(state_, kUninitialized);
DCHECK_GT(pending_read_count_, 0);
DCHECK(pending_eos_set_.find(text_stream) != pending_eos_set_.end());
auto itr = text_track_state_map_.find(text_stream);
DCHECK(itr != text_track_state_map_.end());
TextTrackState* state = itr->second.get();
DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
DCHECK(state->text_track);
--pending_read_count_;
state->read_state = TextTrackState::kReadIdle;
switch (state_) {
case kPlaying: {
if (text_cue.get())
break;
const size_t count = pending_eos_set_.erase(text_stream);
DCHECK_EQ(count, 1U);
if (pending_eos_set_.empty()) {
DCHECK_EQ(pending_read_count_, 0);
state_ = kEnded;
task_runner_->PostTask(FROM_HERE, ended_cb_);
return;
}
DCHECK_GT(pending_read_count_, 0);
return;
}
case kPausePending: {
if (text_cue.get())
break;
const size_t count = pending_eos_set_.erase(text_stream);
DCHECK_EQ(count, 1U);
if (pending_read_count_ > 0) {
DCHECK(!pending_eos_set_.empty());
return;
}
state_ = kPaused;
base::ResetAndReturn(&pause_cb_).Run();
return;
}
case kPaused:
case kUninitialized:
case kEnded:
NOTREACHED();
return;
}
base::TimeDelta start = text_cue->timestamp();
if (state->text_ranges_.AddCue(start)) {
base::TimeDelta end = start + text_cue->duration();
state->text_track->addWebVTTCue(start, end,
text_cue->id(),
text_cue->text(),
text_cue->settings());
}
if (state_ == kPlaying) {
Read(state, text_stream);
return;
}
if (pending_read_count_ == 0) {
DCHECK_EQ(state_, kPausePending) << "state_ " << state_;
state_ = kPaused;
base::ResetAndReturn(&pause_cb_).Run();
}
}
void TextRenderer::OnAddTextTrackDone(DemuxerStream* text_stream,
std::unique_ptr<TextTrack> text_track) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(state_, kUninitialized);
DCHECK(text_stream);
DCHECK(text_track);
std::unique_ptr<TextTrackState> state(
new TextTrackState(std::move(text_track)));
text_track_state_map_[text_stream] = std::move(state);
pending_eos_set_.insert(text_stream);
if (state_ == kPlaying)
Read(text_track_state_map_[text_stream].get(), text_stream);
}
void TextRenderer::Read(
TextTrackState* state,
DemuxerStream* text_stream) {
DCHECK_NE(state->read_state, TextTrackState::kReadPending);
state->read_state = TextTrackState::kReadPending;
++pending_read_count_;
text_stream->Read(base::Bind(
&TextRenderer::BufferReady, weak_factory_.GetWeakPtr(), text_stream));
}
TextRenderer::TextTrackState::TextTrackState(std::unique_ptr<TextTrack> tt)
: read_state(kReadIdle), text_track(std::move(tt)) {}
TextRenderer::TextTrackState::~TextTrackState() {
}
} // namespace media