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"
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/text_cue.h"
19 TextRenderer::TextRenderer(
20 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
21 const AddTextTrackCB
& add_text_track_cb
)
22 : task_runner_(task_runner
),
23 add_text_track_cb_(add_text_track_cb
),
24 state_(kUninitialized
),
25 pending_read_count_(0),
26 weak_factory_(this) {}
28 TextRenderer::~TextRenderer() {
29 DCHECK(task_runner_
->BelongsToCurrentThread());
30 STLDeleteValues(&text_track_state_map_
);
31 if (!pause_cb_
.is_null())
32 base::ResetAndReturn(&pause_cb_
).Run();
35 void TextRenderer::Initialize(const base::Closure
& ended_cb
) {
36 DCHECK(task_runner_
->BelongsToCurrentThread());
37 DCHECK(!ended_cb
.is_null());
38 DCHECK_EQ(kUninitialized
, state_
) << "state_ " << state_
;
39 DCHECK(text_track_state_map_
.empty());
40 DCHECK_EQ(pending_read_count_
, 0);
41 DCHECK(pending_eos_set_
.empty());
42 DCHECK(ended_cb_
.is_null());
48 void TextRenderer::StartPlaying() {
49 DCHECK(task_runner_
->BelongsToCurrentThread());
50 DCHECK_EQ(state_
, kPaused
) << "state_ " << state_
;
52 for (TextTrackStateMap::iterator itr
= text_track_state_map_
.begin();
53 itr
!= text_track_state_map_
.end(); ++itr
) {
54 TextTrackState
* state
= itr
->second
;
55 if (state
->read_state
== TextTrackState::kReadPending
) {
56 DCHECK_GT(pending_read_count_
, 0);
60 Read(state
, itr
->first
);
66 void TextRenderer::Pause(const base::Closure
& callback
) {
67 DCHECK(task_runner_
->BelongsToCurrentThread());
68 DCHECK(state_
== kPlaying
|| state_
== kEnded
) << "state_ " << state_
;
69 DCHECK_GE(pending_read_count_
, 0);
71 if (pending_read_count_
== 0) {
73 task_runner_
->PostTask(FROM_HERE
, callback
);
78 state_
= kPausePending
;
81 void TextRenderer::Flush(const base::Closure
& callback
) {
82 DCHECK(task_runner_
->BelongsToCurrentThread());
83 DCHECK_EQ(pending_read_count_
, 0);
84 DCHECK(state_
== kPaused
) << "state_ " << state_
;
86 for (TextTrackStateMap::iterator itr
= text_track_state_map_
.begin();
87 itr
!= text_track_state_map_
.end(); ++itr
) {
88 pending_eos_set_
.insert(itr
->first
);
89 itr
->second
->text_ranges_
.Reset();
91 DCHECK_EQ(pending_eos_set_
.size(), text_track_state_map_
.size());
92 task_runner_
->PostTask(FROM_HERE
, callback
);
95 void TextRenderer::AddTextStream(DemuxerStream
* text_stream
,
96 const TextTrackConfig
& config
) {
97 DCHECK(task_runner_
->BelongsToCurrentThread());
98 DCHECK(state_
!= kUninitialized
) << "state_ " << state_
;
99 DCHECK(text_track_state_map_
.find(text_stream
) ==
100 text_track_state_map_
.end());
101 DCHECK(pending_eos_set_
.find(text_stream
) ==
102 pending_eos_set_
.end());
104 AddTextTrackDoneCB done_cb
=
105 BindToCurrentLoop(base::Bind(&TextRenderer::OnAddTextTrackDone
,
106 weak_factory_
.GetWeakPtr(),
109 add_text_track_cb_
.Run(config
, done_cb
);
112 void TextRenderer::RemoveTextStream(DemuxerStream
* text_stream
) {
113 DCHECK(task_runner_
->BelongsToCurrentThread());
115 TextTrackStateMap::iterator itr
= text_track_state_map_
.find(text_stream
);
116 DCHECK(itr
!= text_track_state_map_
.end());
118 TextTrackState
* state
= itr
->second
;
119 DCHECK_EQ(state
->read_state
, TextTrackState::kReadIdle
);
121 text_track_state_map_
.erase(itr
);
123 pending_eos_set_
.erase(text_stream
);
126 bool TextRenderer::HasTracks() const {
127 DCHECK(task_runner_
->BelongsToCurrentThread());
128 return !text_track_state_map_
.empty();
131 void TextRenderer::BufferReady(
132 DemuxerStream
* stream
,
133 DemuxerStream::Status status
,
134 const scoped_refptr
<DecoderBuffer
>& input
) {
135 DCHECK(task_runner_
->BelongsToCurrentThread());
136 DCHECK_NE(status
, DemuxerStream::kConfigChanged
);
138 if (status
== DemuxerStream::kAborted
) {
139 DCHECK(!input
.get());
140 DCHECK_GT(pending_read_count_
, 0);
141 DCHECK(pending_eos_set_
.find(stream
) != pending_eos_set_
.end());
143 TextTrackStateMap::iterator itr
= text_track_state_map_
.find(stream
);
144 DCHECK(itr
!= text_track_state_map_
.end());
146 TextTrackState
* state
= itr
->second
;
147 DCHECK_EQ(state
->read_state
, TextTrackState::kReadPending
);
149 --pending_read_count_
;
150 state
->read_state
= TextTrackState::kReadIdle
;
157 if (pending_read_count_
== 0) {
159 base::ResetAndReturn(&pause_cb_
).Run();
175 if (input
->end_of_stream()) {
176 CueReady(stream
, NULL
);
180 DCHECK_EQ(status
, DemuxerStream::kOk
);
181 DCHECK_GE(input
->side_data_size(), 2);
183 // The side data contains both the cue id and cue settings,
184 // each terminated with a NUL.
185 const char* id_ptr
= reinterpret_cast<const char*>(input
->side_data());
186 size_t id_len
= strlen(id_ptr
);
187 std::string
id(id_ptr
, id_len
);
189 const char* settings_ptr
= id_ptr
+ id_len
+ 1;
190 size_t settings_len
= strlen(settings_ptr
);
191 std::string
settings(settings_ptr
, settings_len
);
193 // The cue payload is stored in the data-part of the input buffer.
194 std::string
text(input
->data(), input
->data() + input
->data_size());
196 scoped_refptr
<TextCue
> text_cue(
197 new TextCue(input
->timestamp(),
203 CueReady(stream
, text_cue
);
206 void TextRenderer::CueReady(
207 DemuxerStream
* text_stream
,
208 const scoped_refptr
<TextCue
>& text_cue
) {
209 DCHECK(task_runner_
->BelongsToCurrentThread());
210 DCHECK_NE(state_
, kUninitialized
);
211 DCHECK_GT(pending_read_count_
, 0);
212 DCHECK(pending_eos_set_
.find(text_stream
) != pending_eos_set_
.end());
214 TextTrackStateMap::iterator itr
= text_track_state_map_
.find(text_stream
);
215 DCHECK(itr
!= text_track_state_map_
.end());
217 TextTrackState
* state
= itr
->second
;
218 DCHECK_EQ(state
->read_state
, TextTrackState::kReadPending
);
219 DCHECK(state
->text_track
);
221 --pending_read_count_
;
222 state
->read_state
= TextTrackState::kReadIdle
;
229 const size_t count
= pending_eos_set_
.erase(text_stream
);
230 DCHECK_EQ(count
, 1U);
232 if (pending_eos_set_
.empty()) {
233 DCHECK_EQ(pending_read_count_
, 0);
235 task_runner_
->PostTask(FROM_HERE
, ended_cb_
);
239 DCHECK_GT(pending_read_count_
, 0);
242 case kPausePending
: {
246 const size_t count
= pending_eos_set_
.erase(text_stream
);
247 DCHECK_EQ(count
, 1U);
249 if (pending_read_count_
> 0) {
250 DCHECK(!pending_eos_set_
.empty());
255 base::ResetAndReturn(&pause_cb_
).Run();
267 base::TimeDelta start
= text_cue
->timestamp();
269 if (state
->text_ranges_
.AddCue(start
)) {
270 base::TimeDelta end
= start
+ text_cue
->duration();
272 state
->text_track
->addWebVTTCue(start
, end
,
275 text_cue
->settings());
278 if (state_
== kPlaying
) {
279 Read(state
, text_stream
);
283 if (pending_read_count_
== 0) {
284 DCHECK_EQ(state_
, kPausePending
) << "state_ " << state_
;
286 base::ResetAndReturn(&pause_cb_
).Run();
290 void TextRenderer::OnAddTextTrackDone(DemuxerStream
* text_stream
,
291 scoped_ptr
<TextTrack
> text_track
) {
292 DCHECK(task_runner_
->BelongsToCurrentThread());
293 DCHECK_NE(state_
, kUninitialized
);
297 scoped_ptr
<TextTrackState
> state(new TextTrackState(text_track
.Pass()));
298 text_track_state_map_
[text_stream
] = state
.release();
299 pending_eos_set_
.insert(text_stream
);
301 if (state_
== kPlaying
)
302 Read(text_track_state_map_
[text_stream
], text_stream
);
305 void TextRenderer::Read(
306 TextTrackState
* state
,
307 DemuxerStream
* text_stream
) {
308 DCHECK_NE(state
->read_state
, TextTrackState::kReadPending
);
310 state
->read_state
= TextTrackState::kReadPending
;
311 ++pending_read_count_
;
313 text_stream
->Read(base::Bind(
314 &TextRenderer::BufferReady
, weak_factory_
.GetWeakPtr(), text_stream
));
317 TextRenderer::TextTrackState::TextTrackState(scoped_ptr
<TextTrack
> tt
)
318 : read_state(kReadIdle
),
319 text_track(tt
.Pass()) {
322 TextRenderer::TextTrackState::~TextTrackState() {