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/demuxer_stream.h"
16 #include "media/base/text_cue.h"
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());
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);
61 Read(state
, itr
->first
);
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) {
74 task_runner_
->PostTask(FROM_HERE
, 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(),
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
);
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
) {
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
;
158 if (pending_read_count_
== 0) {
160 base::ResetAndReturn(&pause_cb_
).Run();
176 if (input
->end_of_stream()) {
177 CueReady(stream
, NULL
);
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(),
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
;
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);
236 task_runner_
->PostTask(FROM_HERE
, ended_cb_
);
240 DCHECK_GT(pending_read_count_
, 0);
243 case kPausePending
: {
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());
256 base::ResetAndReturn(&pause_cb_
).Run();
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
,
276 text_cue
->settings());
279 if (state_
== kPlaying
) {
280 Read(state
, text_stream
);
284 if (pending_read_count_
== 0) {
285 DCHECK_EQ(state_
, kPausePending
) << "state_ " << state_
;
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
);
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() {