Home | History | Annotate | Download | only in base
      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 
     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 
     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 
     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 
     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 
     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 
     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 
     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 
    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 
    127 bool TextRenderer::HasTracks() const {
    128   DCHECK(task_runner_->BelongsToCurrentThread());
    129   return !text_track_state_map_.empty();
    130 }
    131 
    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 
    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 
    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 
    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 
    318 TextRenderer::TextTrackState::TextTrackState(scoped_ptr<TextTrack> tt)
    319     : read_state(kReadIdle),
    320       text_track(tt.Pass()) {
    321 }
    322 
    323 TextRenderer::TextTrackState::~TextTrackState() {
    324 }
    325 
    326 }  // namespace media
    327