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.
4
5 #include "media/base/text_renderer.h"
6
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"
17
18 namespace media {
19
TextRenderer(const scoped_refptr<base::SingleThreadTaskRunner> & task_runner,const AddTextTrackCB & add_text_track_cb)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) {}
28
~TextRenderer()29 TextRenderer::~TextRenderer() {
30 DCHECK(state_ == kUninitialized ||
31 state_ == kStopped) << "state_ " << state_;
32 DCHECK_EQ(pending_read_count_, 0);
33 STLDeleteValues(&text_track_state_map_);
34 }
35
Initialize(const base::Closure & ended_cb)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());
44
45 ended_cb_ = ended_cb;
46 state_ = kPaused;
47 }
48
Play(const base::Closure & callback)49 void TextRenderer::Play(const base::Closure& callback) {
50 DCHECK(task_runner_->BelongsToCurrentThread());
51 DCHECK_EQ(state_, kPaused) << "state_ " << state_;
52
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;
59 }
60
61 Read(state, itr->first);
62 }
63
64 state_ = kPlaying;
65 callback.Run();
66 }
67
Pause(const base::Closure & callback)68 void TextRenderer::Pause(const base::Closure& callback) {
69 DCHECK(task_runner_->BelongsToCurrentThread());
70 DCHECK(state_ == kPlaying || state_ == kEnded) << "state_ " << state_;
71 DCHECK_GE(pending_read_count_, 0);
72 pause_cb_ = callback;
73
74 if (pending_read_count_ == 0) {
75 state_ = kPaused;
76 base::ResetAndReturn(&pause_cb_).Run();
77 return;
78 }
79
80 state_ = kPausePending;
81 }
82
Flush(const base::Closure & callback)83 void TextRenderer::Flush(const base::Closure& callback) {
84 DCHECK(task_runner_->BelongsToCurrentThread());
85 DCHECK_EQ(pending_read_count_, 0);
86 DCHECK(state_ == kPaused) << "state_ " << state_;
87
88 for (TextTrackStateMap::iterator itr = text_track_state_map_.begin();
89 itr != text_track_state_map_.end(); ++itr) {
90 pending_eos_set_.insert(itr->first);
91 itr->second->text_ranges_.Reset();
92 }
93 DCHECK_EQ(pending_eos_set_.size(), text_track_state_map_.size());
94 callback.Run();
95 }
96
Stop(const base::Closure & cb)97 void TextRenderer::Stop(const base::Closure& cb) {
98 DCHECK(task_runner_->BelongsToCurrentThread());
99 DCHECK(!cb.is_null());
100 DCHECK(state_ == kPlaying ||
101 state_ == kPausePending ||
102 state_ == kPaused ||
103 state_ == kEnded) << "state_ " << state_;
104 DCHECK_GE(pending_read_count_, 0);
105
106 stop_cb_ = cb;
107
108 if (pending_read_count_ == 0) {
109 state_ = kStopped;
110 base::ResetAndReturn(&stop_cb_).Run();
111 return;
112 }
113
114 state_ = kStopPending;
115 }
116
AddTextStream(DemuxerStream * text_stream,const TextTrackConfig & config)117 void TextRenderer::AddTextStream(DemuxerStream* text_stream,
118 const TextTrackConfig& config) {
119 DCHECK(task_runner_->BelongsToCurrentThread());
120 DCHECK(state_ != kUninitialized) << "state_ " << state_;
121 DCHECK_NE(state_, kStopPending);
122 DCHECK_NE(state_, kStopped);
123 DCHECK(text_track_state_map_.find(text_stream) ==
124 text_track_state_map_.end());
125 DCHECK(pending_eos_set_.find(text_stream) ==
126 pending_eos_set_.end());
127
128 AddTextTrackDoneCB done_cb =
129 BindToCurrentLoop(base::Bind(&TextRenderer::OnAddTextTrackDone,
130 weak_factory_.GetWeakPtr(),
131 text_stream));
132
133 add_text_track_cb_.Run(config, done_cb);
134 }
135
RemoveTextStream(DemuxerStream * text_stream)136 void TextRenderer::RemoveTextStream(DemuxerStream* text_stream) {
137 DCHECK(task_runner_->BelongsToCurrentThread());
138
139 TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
140 DCHECK(itr != text_track_state_map_.end());
141
142 TextTrackState* state = itr->second;
143 DCHECK_EQ(state->read_state, TextTrackState::kReadIdle);
144 delete state;
145 text_track_state_map_.erase(itr);
146
147 pending_eos_set_.erase(text_stream);
148 }
149
HasTracks() const150 bool TextRenderer::HasTracks() const {
151 DCHECK(task_runner_->BelongsToCurrentThread());
152 return !text_track_state_map_.empty();
153 }
154
BufferReady(DemuxerStream * stream,DemuxerStream::Status status,const scoped_refptr<DecoderBuffer> & input)155 void TextRenderer::BufferReady(
156 DemuxerStream* stream,
157 DemuxerStream::Status status,
158 const scoped_refptr<DecoderBuffer>& input) {
159 DCHECK(task_runner_->BelongsToCurrentThread());
160 DCHECK_NE(status, DemuxerStream::kConfigChanged);
161
162 if (status == DemuxerStream::kAborted) {
163 DCHECK(!input);
164 DCHECK_GT(pending_read_count_, 0);
165 DCHECK(pending_eos_set_.find(stream) != pending_eos_set_.end());
166
167 TextTrackStateMap::iterator itr = text_track_state_map_.find(stream);
168 DCHECK(itr != text_track_state_map_.end());
169
170 TextTrackState* state = itr->second;
171 DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
172
173 --pending_read_count_;
174 state->read_state = TextTrackState::kReadIdle;
175
176 switch (state_) {
177 case kPlaying:
178 return;
179
180 case kPausePending:
181 if (pending_read_count_ == 0) {
182 state_ = kPaused;
183 base::ResetAndReturn(&pause_cb_).Run();
184 }
185
186 return;
187
188 case kStopPending:
189 if (pending_read_count_ == 0) {
190 state_ = kStopped;
191 base::ResetAndReturn(&stop_cb_).Run();
192 }
193
194 return;
195
196 case kPaused:
197 case kStopped:
198 case kUninitialized:
199 case kEnded:
200 NOTREACHED();
201 return;
202 }
203
204 NOTREACHED();
205 return;
206 }
207
208 if (input->end_of_stream()) {
209 CueReady(stream, NULL);
210 return;
211 }
212
213 DCHECK_EQ(status, DemuxerStream::kOk);
214 DCHECK_GE(input->side_data_size(), 2);
215
216 // The side data contains both the cue id and cue settings,
217 // each terminated with a NUL.
218 const char* id_ptr = reinterpret_cast<const char*>(input->side_data());
219 size_t id_len = strlen(id_ptr);
220 std::string id(id_ptr, id_len);
221
222 const char* settings_ptr = id_ptr + id_len + 1;
223 size_t settings_len = strlen(settings_ptr);
224 std::string settings(settings_ptr, settings_len);
225
226 // The cue payload is stored in the data-part of the input buffer.
227 std::string text(input->data(), input->data() + input->data_size());
228
229 scoped_refptr<TextCue> text_cue(
230 new TextCue(input->timestamp(),
231 input->duration(),
232 id,
233 settings,
234 text));
235
236 CueReady(stream, text_cue);
237 }
238
CueReady(DemuxerStream * text_stream,const scoped_refptr<TextCue> & text_cue)239 void TextRenderer::CueReady(
240 DemuxerStream* text_stream,
241 const scoped_refptr<TextCue>& text_cue) {
242 DCHECK(task_runner_->BelongsToCurrentThread());
243 DCHECK(state_ != kUninitialized &&
244 state_ != kStopped) << "state_ " << state_;
245 DCHECK_GT(pending_read_count_, 0);
246 DCHECK(pending_eos_set_.find(text_stream) != pending_eos_set_.end());
247
248 TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
249 DCHECK(itr != text_track_state_map_.end());
250
251 TextTrackState* state = itr->second;
252 DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
253 DCHECK(state->text_track);
254
255 --pending_read_count_;
256 state->read_state = TextTrackState::kReadIdle;
257
258 switch (state_) {
259 case kPlaying: {
260 if (text_cue)
261 break;
262
263 const size_t count = pending_eos_set_.erase(text_stream);
264 DCHECK_EQ(count, 1U);
265
266 if (pending_eos_set_.empty()) {
267 DCHECK_EQ(pending_read_count_, 0);
268 state_ = kEnded;
269 ended_cb_.Run();
270 return;
271 }
272
273 DCHECK_GT(pending_read_count_, 0);
274 return;
275 }
276 case kPausePending: {
277 if (text_cue)
278 break;
279
280 const size_t count = pending_eos_set_.erase(text_stream);
281 DCHECK_EQ(count, 1U);
282
283 if (pending_read_count_ > 0) {
284 DCHECK(!pending_eos_set_.empty());
285 return;
286 }
287
288 state_ = kPaused;
289 base::ResetAndReturn(&pause_cb_).Run();
290
291 return;
292 }
293 case kStopPending:
294 if (pending_read_count_ == 0) {
295 state_ = kStopped;
296 base::ResetAndReturn(&stop_cb_).Run();
297 }
298
299 return;
300
301 case kPaused:
302 case kStopped:
303 case kUninitialized:
304 case kEnded:
305 NOTREACHED();
306 return;
307 }
308
309 base::TimeDelta start = text_cue->timestamp();
310
311 if (state->text_ranges_.AddCue(start)) {
312 base::TimeDelta end = start + text_cue->duration();
313
314 state->text_track->addWebVTTCue(start, end,
315 text_cue->id(),
316 text_cue->text(),
317 text_cue->settings());
318 }
319
320 if (state_ == kPlaying) {
321 Read(state, text_stream);
322 return;
323 }
324
325 if (pending_read_count_ == 0) {
326 DCHECK_EQ(state_, kPausePending) << "state_ " << state_;
327 state_ = kPaused;
328 base::ResetAndReturn(&pause_cb_).Run();
329 }
330 }
331
OnAddTextTrackDone(DemuxerStream * text_stream,scoped_ptr<TextTrack> text_track)332 void TextRenderer::OnAddTextTrackDone(DemuxerStream* text_stream,
333 scoped_ptr<TextTrack> text_track) {
334 DCHECK(task_runner_->BelongsToCurrentThread());
335 DCHECK(state_ != kUninitialized &&
336 state_ != kStopped &&
337 state_ != kStopPending) << "state_ " << state_;
338 DCHECK(text_stream);
339 DCHECK(text_track);
340
341 scoped_ptr<TextTrackState> state(new TextTrackState(text_track.Pass()));
342 text_track_state_map_[text_stream] = state.release();
343 pending_eos_set_.insert(text_stream);
344
345 if (state_ == kPlaying)
346 Read(text_track_state_map_[text_stream], text_stream);
347 }
348
Read(TextTrackState * state,DemuxerStream * text_stream)349 void TextRenderer::Read(
350 TextTrackState* state,
351 DemuxerStream* text_stream) {
352 DCHECK_NE(state->read_state, TextTrackState::kReadPending);
353
354 state->read_state = TextTrackState::kReadPending;
355 ++pending_read_count_;
356
357 text_stream->Read(base::Bind(
358 &TextRenderer::BufferReady, weak_factory_.GetWeakPtr(), text_stream));
359 }
360
TextTrackState(scoped_ptr<TextTrack> tt)361 TextRenderer::TextTrackState::TextTrackState(scoped_ptr<TextTrack> tt)
362 : read_state(kReadIdle),
363 text_track(tt.Pass()) {
364 }
365
~TextTrackState()366 TextRenderer::TextTrackState::~TextTrackState() {
367 }
368
369 } // namespace media
370