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 18namespace media { 19 20TextRenderer::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 29TextRenderer::~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 36void 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 49void 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 67void 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 82void 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 96void 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 113void 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 127bool TextRenderer::HasTracks() const { 128 DCHECK(task_runner_->BelongsToCurrentThread()); 129 return !text_track_state_map_.empty(); 130} 131 132void 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 207void 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 291void 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 306void 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 318TextRenderer::TextTrackState::TextTrackState(scoped_ptr<TextTrack> tt) 319 : read_state(kReadIdle), 320 text_track(tt.Pass()) { 321} 322 323TextRenderer::TextTrackState::~TextTrackState() { 324} 325 326} // namespace media 327