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(task_runner_->BelongsToCurrentThread());
31 STLDeleteValues(&text_track_state_map_);
32 if (!pause_cb_.is_null())
33 base::ResetAndReturn(&pause_cb_).Run();
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
StartPlaying()49 void TextRenderer::StartPlaying() {
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 }
66
Pause(const base::Closure & callback)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);
71
72 if (pending_read_count_ == 0) {
73 state_ = kPaused;
74 task_runner_->PostTask(FROM_HERE, callback);
75 return;
76 }
77
78 pause_cb_ = callback;
79 state_ = kPausePending;
80 }
81
Flush(const base::Closure & callback)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_;
86
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();
91 }
92 DCHECK_EQ(pending_eos_set_.size(), text_track_state_map_.size());
93 task_runner_->PostTask(FROM_HERE, callback);
94 }
95
AddTextStream(DemuxerStream * text_stream,const TextTrackConfig & config)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());
104
105 AddTextTrackDoneCB done_cb =
106 BindToCurrentLoop(base::Bind(&TextRenderer::OnAddTextTrackDone,
107 weak_factory_.GetWeakPtr(),
108 text_stream));
109
110 add_text_track_cb_.Run(config, done_cb);
111 }
112
RemoveTextStream(DemuxerStream * text_stream)113 void TextRenderer::RemoveTextStream(DemuxerStream* text_stream) {
114 DCHECK(task_runner_->BelongsToCurrentThread());
115
116 TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
117 DCHECK(itr != text_track_state_map_.end());
118
119 TextTrackState* state = itr->second;
120 DCHECK_EQ(state->read_state, TextTrackState::kReadIdle);
121 delete state;
122 text_track_state_map_.erase(itr);
123
124 pending_eos_set_.erase(text_stream);
125 }
126
HasTracks() const127 bool TextRenderer::HasTracks() const {
128 DCHECK(task_runner_->BelongsToCurrentThread());
129 return !text_track_state_map_.empty();
130 }
131
BufferReady(DemuxerStream * stream,DemuxerStream::Status status,const scoped_refptr<DecoderBuffer> & input)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);
138
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());
143
144 TextTrackStateMap::iterator itr = text_track_state_map_.find(stream);
145 DCHECK(itr != text_track_state_map_.end());
146
147 TextTrackState* state = itr->second;
148 DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
149
150 --pending_read_count_;
151 state->read_state = TextTrackState::kReadIdle;
152
153 switch (state_) {
154 case kPlaying:
155 return;
156
157 case kPausePending:
158 if (pending_read_count_ == 0) {
159 state_ = kPaused;
160 base::ResetAndReturn(&pause_cb_).Run();
161 }
162
163 return;
164
165 case kPaused:
166 case kUninitialized:
167 case kEnded:
168 NOTREACHED();
169 return;
170 }
171
172 NOTREACHED();
173 return;
174 }
175
176 if (input->end_of_stream()) {
177 CueReady(stream, NULL);
178 return;
179 }
180
181 DCHECK_EQ(status, DemuxerStream::kOk);
182 DCHECK_GE(input->side_data_size(), 2);
183
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);
189
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);
193
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());
196
197 scoped_refptr<TextCue> text_cue(
198 new TextCue(input->timestamp(),
199 input->duration(),
200 id,
201 settings,
202 text));
203
204 CueReady(stream, text_cue);
205 }
206
CueReady(DemuxerStream * text_stream,const scoped_refptr<TextCue> & 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());
214
215 TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
216 DCHECK(itr != text_track_state_map_.end());
217
218 TextTrackState* state = itr->second;
219 DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
220 DCHECK(state->text_track);
221
222 --pending_read_count_;
223 state->read_state = TextTrackState::kReadIdle;
224
225 switch (state_) {
226 case kPlaying: {
227 if (text_cue.get())
228 break;
229
230 const size_t count = pending_eos_set_.erase(text_stream);
231 DCHECK_EQ(count, 1U);
232
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;
238 }
239
240 DCHECK_GT(pending_read_count_, 0);
241 return;
242 }
243 case kPausePending: {
244 if (text_cue.get())
245 break;
246
247 const size_t count = pending_eos_set_.erase(text_stream);
248 DCHECK_EQ(count, 1U);
249
250 if (pending_read_count_ > 0) {
251 DCHECK(!pending_eos_set_.empty());
252 return;
253 }
254
255 state_ = kPaused;
256 base::ResetAndReturn(&pause_cb_).Run();
257
258 return;
259 }
260
261 case kPaused:
262 case kUninitialized:
263 case kEnded:
264 NOTREACHED();
265 return;
266 }
267
268 base::TimeDelta start = text_cue->timestamp();
269
270 if (state->text_ranges_.AddCue(start)) {
271 base::TimeDelta end = start + text_cue->duration();
272
273 state->text_track->addWebVTTCue(start, end,
274 text_cue->id(),
275 text_cue->text(),
276 text_cue->settings());
277 }
278
279 if (state_ == kPlaying) {
280 Read(state, text_stream);
281 return;
282 }
283
284 if (pending_read_count_ == 0) {
285 DCHECK_EQ(state_, kPausePending) << "state_ " << state_;
286 state_ = kPaused;
287 base::ResetAndReturn(&pause_cb_).Run();
288 }
289 }
290
OnAddTextTrackDone(DemuxerStream * text_stream,scoped_ptr<TextTrack> text_track)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);
297
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);
301
302 if (state_ == kPlaying)
303 Read(text_track_state_map_[text_stream], text_stream);
304 }
305
Read(TextTrackState * state,DemuxerStream * text_stream)306 void TextRenderer::Read(
307 TextTrackState* state,
308 DemuxerStream* text_stream) {
309 DCHECK_NE(state->read_state, TextTrackState::kReadPending);
310
311 state->read_state = TextTrackState::kReadPending;
312 ++pending_read_count_;
313
314 text_stream->Read(base::Bind(
315 &TextRenderer::BufferReady, weak_factory_.GetWeakPtr(), text_stream));
316 }
317
TextTrackState(scoped_ptr<TextTrack> tt)318 TextRenderer::TextTrackState::TextTrackState(scoped_ptr<TextTrack> tt)
319 : read_state(kReadIdle),
320 text_track(tt.Pass()) {
321 }
322
~TextTrackState()323 TextRenderer::TextTrackState::~TextTrackState() {
324 }
325
326 } // namespace media
327