Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / media / base / text_renderer.cc
blob570907eb060e38f585c164ab6d61925b448edc59
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/base/text_renderer.h"
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/logging.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/stl_util.h"
12 #include "media/base/bind_to_current_loop.h"
13 #include "media/base/decoder_buffer.h"
14 #include "media/base/demuxer.h"
15 #include "media/base/demuxer_stream.h"
16 #include "media/base/text_cue.h"
18 namespace media {
20 TextRenderer::TextRenderer(
21 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
22 const AddTextTrackCB& add_text_track_cb)
23 : task_runner_(task_runner),
24 add_text_track_cb_(add_text_track_cb),
25 state_(kUninitialized),
26 pending_read_count_(0),
27 weak_factory_(this) {}
29 TextRenderer::~TextRenderer() {
30 DCHECK(task_runner_->BelongsToCurrentThread());
31 STLDeleteValues(&text_track_state_map_);
32 if (!pause_cb_.is_null())
33 base::ResetAndReturn(&pause_cb_).Run();
36 void TextRenderer::Initialize(const base::Closure& ended_cb) {
37 DCHECK(task_runner_->BelongsToCurrentThread());
38 DCHECK(!ended_cb.is_null());
39 DCHECK_EQ(kUninitialized, state_) << "state_ " << state_;
40 DCHECK(text_track_state_map_.empty());
41 DCHECK_EQ(pending_read_count_, 0);
42 DCHECK(pending_eos_set_.empty());
43 DCHECK(ended_cb_.is_null());
45 ended_cb_ = ended_cb;
46 state_ = kPaused;
49 void TextRenderer::StartPlaying() {
50 DCHECK(task_runner_->BelongsToCurrentThread());
51 DCHECK_EQ(state_, kPaused) << "state_ " << state_;
53 for (TextTrackStateMap::iterator itr = text_track_state_map_.begin();
54 itr != text_track_state_map_.end(); ++itr) {
55 TextTrackState* state = itr->second;
56 if (state->read_state == TextTrackState::kReadPending) {
57 DCHECK_GT(pending_read_count_, 0);
58 continue;
61 Read(state, itr->first);
64 state_ = kPlaying;
67 void TextRenderer::Pause(const base::Closure& callback) {
68 DCHECK(task_runner_->BelongsToCurrentThread());
69 DCHECK(state_ == kPlaying || state_ == kEnded) << "state_ " << state_;
70 DCHECK_GE(pending_read_count_, 0);
72 if (pending_read_count_ == 0) {
73 state_ = kPaused;
74 task_runner_->PostTask(FROM_HERE, callback);
75 return;
78 pause_cb_ = callback;
79 state_ = kPausePending;
82 void TextRenderer::Flush(const base::Closure& callback) {
83 DCHECK(task_runner_->BelongsToCurrentThread());
84 DCHECK_EQ(pending_read_count_, 0);
85 DCHECK(state_ == kPaused) << "state_ " << state_;
87 for (TextTrackStateMap::iterator itr = text_track_state_map_.begin();
88 itr != text_track_state_map_.end(); ++itr) {
89 pending_eos_set_.insert(itr->first);
90 itr->second->text_ranges_.Reset();
92 DCHECK_EQ(pending_eos_set_.size(), text_track_state_map_.size());
93 task_runner_->PostTask(FROM_HERE, callback);
96 void TextRenderer::AddTextStream(DemuxerStream* text_stream,
97 const TextTrackConfig& config) {
98 DCHECK(task_runner_->BelongsToCurrentThread());
99 DCHECK(state_ != kUninitialized) << "state_ " << state_;
100 DCHECK(text_track_state_map_.find(text_stream) ==
101 text_track_state_map_.end());
102 DCHECK(pending_eos_set_.find(text_stream) ==
103 pending_eos_set_.end());
105 AddTextTrackDoneCB done_cb =
106 BindToCurrentLoop(base::Bind(&TextRenderer::OnAddTextTrackDone,
107 weak_factory_.GetWeakPtr(),
108 text_stream));
110 add_text_track_cb_.Run(config, done_cb);
113 void TextRenderer::RemoveTextStream(DemuxerStream* text_stream) {
114 DCHECK(task_runner_->BelongsToCurrentThread());
116 TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
117 DCHECK(itr != text_track_state_map_.end());
119 TextTrackState* state = itr->second;
120 DCHECK_EQ(state->read_state, TextTrackState::kReadIdle);
121 delete state;
122 text_track_state_map_.erase(itr);
124 pending_eos_set_.erase(text_stream);
127 bool TextRenderer::HasTracks() const {
128 DCHECK(task_runner_->BelongsToCurrentThread());
129 return !text_track_state_map_.empty();
132 void TextRenderer::BufferReady(
133 DemuxerStream* stream,
134 DemuxerStream::Status status,
135 const scoped_refptr<DecoderBuffer>& input) {
136 DCHECK(task_runner_->BelongsToCurrentThread());
137 DCHECK_NE(status, DemuxerStream::kConfigChanged);
139 if (status == DemuxerStream::kAborted) {
140 DCHECK(!input.get());
141 DCHECK_GT(pending_read_count_, 0);
142 DCHECK(pending_eos_set_.find(stream) != pending_eos_set_.end());
144 TextTrackStateMap::iterator itr = text_track_state_map_.find(stream);
145 DCHECK(itr != text_track_state_map_.end());
147 TextTrackState* state = itr->second;
148 DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
150 --pending_read_count_;
151 state->read_state = TextTrackState::kReadIdle;
153 switch (state_) {
154 case kPlaying:
155 return;
157 case kPausePending:
158 if (pending_read_count_ == 0) {
159 state_ = kPaused;
160 base::ResetAndReturn(&pause_cb_).Run();
163 return;
165 case kPaused:
166 case kUninitialized:
167 case kEnded:
168 NOTREACHED();
169 return;
172 NOTREACHED();
173 return;
176 if (input->end_of_stream()) {
177 CueReady(stream, NULL);
178 return;
181 DCHECK_EQ(status, DemuxerStream::kOk);
182 DCHECK_GE(input->side_data_size(), 2);
184 // The side data contains both the cue id and cue settings,
185 // each terminated with a NUL.
186 const char* id_ptr = reinterpret_cast<const char*>(input->side_data());
187 size_t id_len = strlen(id_ptr);
188 std::string id(id_ptr, id_len);
190 const char* settings_ptr = id_ptr + id_len + 1;
191 size_t settings_len = strlen(settings_ptr);
192 std::string settings(settings_ptr, settings_len);
194 // The cue payload is stored in the data-part of the input buffer.
195 std::string text(input->data(), input->data() + input->data_size());
197 scoped_refptr<TextCue> text_cue(
198 new TextCue(input->timestamp(),
199 input->duration(),
201 settings,
202 text));
204 CueReady(stream, text_cue);
207 void TextRenderer::CueReady(
208 DemuxerStream* text_stream,
209 const scoped_refptr<TextCue>& text_cue) {
210 DCHECK(task_runner_->BelongsToCurrentThread());
211 DCHECK_NE(state_, kUninitialized);
212 DCHECK_GT(pending_read_count_, 0);
213 DCHECK(pending_eos_set_.find(text_stream) != pending_eos_set_.end());
215 TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
216 DCHECK(itr != text_track_state_map_.end());
218 TextTrackState* state = itr->second;
219 DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
220 DCHECK(state->text_track);
222 --pending_read_count_;
223 state->read_state = TextTrackState::kReadIdle;
225 switch (state_) {
226 case kPlaying: {
227 if (text_cue.get())
228 break;
230 const size_t count = pending_eos_set_.erase(text_stream);
231 DCHECK_EQ(count, 1U);
233 if (pending_eos_set_.empty()) {
234 DCHECK_EQ(pending_read_count_, 0);
235 state_ = kEnded;
236 task_runner_->PostTask(FROM_HERE, ended_cb_);
237 return;
240 DCHECK_GT(pending_read_count_, 0);
241 return;
243 case kPausePending: {
244 if (text_cue.get())
245 break;
247 const size_t count = pending_eos_set_.erase(text_stream);
248 DCHECK_EQ(count, 1U);
250 if (pending_read_count_ > 0) {
251 DCHECK(!pending_eos_set_.empty());
252 return;
255 state_ = kPaused;
256 base::ResetAndReturn(&pause_cb_).Run();
258 return;
261 case kPaused:
262 case kUninitialized:
263 case kEnded:
264 NOTREACHED();
265 return;
268 base::TimeDelta start = text_cue->timestamp();
270 if (state->text_ranges_.AddCue(start)) {
271 base::TimeDelta end = start + text_cue->duration();
273 state->text_track->addWebVTTCue(start, end,
274 text_cue->id(),
275 text_cue->text(),
276 text_cue->settings());
279 if (state_ == kPlaying) {
280 Read(state, text_stream);
281 return;
284 if (pending_read_count_ == 0) {
285 DCHECK_EQ(state_, kPausePending) << "state_ " << state_;
286 state_ = kPaused;
287 base::ResetAndReturn(&pause_cb_).Run();
291 void TextRenderer::OnAddTextTrackDone(DemuxerStream* text_stream,
292 scoped_ptr<TextTrack> text_track) {
293 DCHECK(task_runner_->BelongsToCurrentThread());
294 DCHECK_NE(state_, kUninitialized);
295 DCHECK(text_stream);
296 DCHECK(text_track);
298 scoped_ptr<TextTrackState> state(new TextTrackState(text_track.Pass()));
299 text_track_state_map_[text_stream] = state.release();
300 pending_eos_set_.insert(text_stream);
302 if (state_ == kPlaying)
303 Read(text_track_state_map_[text_stream], text_stream);
306 void TextRenderer::Read(
307 TextTrackState* state,
308 DemuxerStream* text_stream) {
309 DCHECK_NE(state->read_state, TextTrackState::kReadPending);
311 state->read_state = TextTrackState::kReadPending;
312 ++pending_read_count_;
314 text_stream->Read(base::Bind(
315 &TextRenderer::BufferReady, weak_factory_.GetWeakPtr(), text_stream));
318 TextRenderer::TextTrackState::TextTrackState(scoped_ptr<TextTrack> tt)
319 : read_state(kReadIdle),
320 text_track(tt.Pass()) {
323 TextRenderer::TextTrackState::~TextTrackState() {
326 } // namespace media