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(state_ == kUninitialized ||
     31          state_ == kStopped) << "state_ " << state_;
     32   DCHECK_EQ(pending_read_count_, 0);
     33   STLDeleteValues(&text_track_state_map_);
     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::Play(const base::Closure& callback) {
     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   callback.Run();
     66 }
     67 
     68 void TextRenderer::Pause(const base::Closure& callback) {
     69   DCHECK(task_runner_->BelongsToCurrentThread());
     70   DCHECK(state_ == kPlaying || state_ == kEnded) << "state_ " << state_;
     71   DCHECK_GE(pending_read_count_, 0);
     72   pause_cb_ = callback;
     73 
     74   if (pending_read_count_ == 0) {
     75     state_ = kPaused;
     76     base::ResetAndReturn(&pause_cb_).Run();
     77     return;
     78   }
     79 
     80   state_ = kPausePending;
     81 }
     82 
     83 void TextRenderer::Flush(const base::Closure& callback) {
     84   DCHECK(task_runner_->BelongsToCurrentThread());
     85   DCHECK_EQ(pending_read_count_, 0);
     86   DCHECK(state_ == kPaused) << "state_ " << state_;
     87 
     88   for (TextTrackStateMap::iterator itr = text_track_state_map_.begin();
     89        itr != text_track_state_map_.end(); ++itr) {
     90     pending_eos_set_.insert(itr->first);
     91     itr->second->text_ranges_.Reset();
     92   }
     93   DCHECK_EQ(pending_eos_set_.size(), text_track_state_map_.size());
     94   callback.Run();
     95 }
     96 
     97 void TextRenderer::Stop(const base::Closure& cb) {
     98   DCHECK(task_runner_->BelongsToCurrentThread());
     99   DCHECK(!cb.is_null());
    100   DCHECK(state_ == kPlaying ||
    101          state_ == kPausePending ||
    102          state_ == kPaused ||
    103          state_ == kEnded) << "state_ " << state_;
    104   DCHECK_GE(pending_read_count_, 0);
    105 
    106   stop_cb_ = cb;
    107 
    108   if (pending_read_count_ == 0) {
    109     state_ = kStopped;
    110     base::ResetAndReturn(&stop_cb_).Run();
    111     return;
    112   }
    113 
    114   state_ = kStopPending;
    115 }
    116 
    117 void TextRenderer::AddTextStream(DemuxerStream* text_stream,
    118                                  const TextTrackConfig& config) {
    119   DCHECK(task_runner_->BelongsToCurrentThread());
    120   DCHECK(state_ != kUninitialized) << "state_ " << state_;
    121   DCHECK_NE(state_, kStopPending);
    122   DCHECK_NE(state_, kStopped);
    123   DCHECK(text_track_state_map_.find(text_stream) ==
    124          text_track_state_map_.end());
    125   DCHECK(pending_eos_set_.find(text_stream) ==
    126          pending_eos_set_.end());
    127 
    128   AddTextTrackDoneCB done_cb =
    129       BindToCurrentLoop(base::Bind(&TextRenderer::OnAddTextTrackDone,
    130                                    weak_factory_.GetWeakPtr(),
    131                                    text_stream));
    132 
    133   add_text_track_cb_.Run(config, done_cb);
    134 }
    135 
    136 void TextRenderer::RemoveTextStream(DemuxerStream* text_stream) {
    137   DCHECK(task_runner_->BelongsToCurrentThread());
    138 
    139   TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
    140   DCHECK(itr != text_track_state_map_.end());
    141 
    142   TextTrackState* state = itr->second;
    143   DCHECK_EQ(state->read_state, TextTrackState::kReadIdle);
    144   delete state;
    145   text_track_state_map_.erase(itr);
    146 
    147   pending_eos_set_.erase(text_stream);
    148 }
    149 
    150 bool TextRenderer::HasTracks() const {
    151   DCHECK(task_runner_->BelongsToCurrentThread());
    152   return !text_track_state_map_.empty();
    153 }
    154 
    155 void TextRenderer::BufferReady(
    156     DemuxerStream* stream,
    157     DemuxerStream::Status status,
    158     const scoped_refptr<DecoderBuffer>& input) {
    159   DCHECK(task_runner_->BelongsToCurrentThread());
    160   DCHECK_NE(status, DemuxerStream::kConfigChanged);
    161 
    162   if (status == DemuxerStream::kAborted) {
    163     DCHECK(!input);
    164     DCHECK_GT(pending_read_count_, 0);
    165     DCHECK(pending_eos_set_.find(stream) != pending_eos_set_.end());
    166 
    167     TextTrackStateMap::iterator itr = text_track_state_map_.find(stream);
    168     DCHECK(itr != text_track_state_map_.end());
    169 
    170     TextTrackState* state = itr->second;
    171     DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
    172 
    173     --pending_read_count_;
    174     state->read_state = TextTrackState::kReadIdle;
    175 
    176     switch (state_) {
    177       case kPlaying:
    178         return;
    179 
    180       case kPausePending:
    181         if (pending_read_count_ == 0) {
    182           state_ = kPaused;
    183           base::ResetAndReturn(&pause_cb_).Run();
    184         }
    185 
    186         return;
    187 
    188       case kStopPending:
    189         if (pending_read_count_ == 0) {
    190           state_ = kStopped;
    191           base::ResetAndReturn(&stop_cb_).Run();
    192         }
    193 
    194         return;
    195 
    196       case kPaused:
    197       case kStopped:
    198       case kUninitialized:
    199       case kEnded:
    200         NOTREACHED();
    201         return;
    202     }
    203 
    204     NOTREACHED();
    205     return;
    206   }
    207 
    208   if (input->end_of_stream()) {
    209     CueReady(stream, NULL);
    210     return;
    211   }
    212 
    213   DCHECK_EQ(status, DemuxerStream::kOk);
    214   DCHECK_GE(input->side_data_size(), 2);
    215 
    216   // The side data contains both the cue id and cue settings,
    217   // each terminated with a NUL.
    218   const char* id_ptr = reinterpret_cast<const char*>(input->side_data());
    219   size_t id_len = strlen(id_ptr);
    220   std::string id(id_ptr, id_len);
    221 
    222   const char* settings_ptr = id_ptr + id_len + 1;
    223   size_t settings_len = strlen(settings_ptr);
    224   std::string settings(settings_ptr, settings_len);
    225 
    226   // The cue payload is stored in the data-part of the input buffer.
    227   std::string text(input->data(), input->data() + input->data_size());
    228 
    229   scoped_refptr<TextCue> text_cue(
    230       new TextCue(input->timestamp(),
    231                   input->duration(),
    232                   id,
    233                   settings,
    234                   text));
    235 
    236   CueReady(stream, text_cue);
    237 }
    238 
    239 void TextRenderer::CueReady(
    240     DemuxerStream* text_stream,
    241     const scoped_refptr<TextCue>& text_cue) {
    242   DCHECK(task_runner_->BelongsToCurrentThread());
    243   DCHECK(state_ != kUninitialized &&
    244          state_ != kStopped) << "state_ " << state_;
    245   DCHECK_GT(pending_read_count_, 0);
    246   DCHECK(pending_eos_set_.find(text_stream) != pending_eos_set_.end());
    247 
    248   TextTrackStateMap::iterator itr = text_track_state_map_.find(text_stream);
    249   DCHECK(itr != text_track_state_map_.end());
    250 
    251   TextTrackState* state = itr->second;
    252   DCHECK_EQ(state->read_state, TextTrackState::kReadPending);
    253   DCHECK(state->text_track);
    254 
    255   --pending_read_count_;
    256   state->read_state = TextTrackState::kReadIdle;
    257 
    258   switch (state_) {
    259     case kPlaying: {
    260       if (text_cue)
    261         break;
    262 
    263       const size_t count = pending_eos_set_.erase(text_stream);
    264       DCHECK_EQ(count, 1U);
    265 
    266       if (pending_eos_set_.empty()) {
    267         DCHECK_EQ(pending_read_count_, 0);
    268         state_ = kEnded;
    269         ended_cb_.Run();
    270         return;
    271       }
    272 
    273       DCHECK_GT(pending_read_count_, 0);
    274       return;
    275     }
    276     case kPausePending: {
    277       if (text_cue)
    278         break;
    279 
    280       const size_t count = pending_eos_set_.erase(text_stream);
    281       DCHECK_EQ(count, 1U);
    282 
    283       if (pending_read_count_ > 0) {
    284         DCHECK(!pending_eos_set_.empty());
    285         return;
    286       }
    287 
    288       state_ = kPaused;
    289       base::ResetAndReturn(&pause_cb_).Run();
    290 
    291       return;
    292     }
    293     case kStopPending:
    294       if (pending_read_count_ == 0) {
    295         state_ = kStopped;
    296         base::ResetAndReturn(&stop_cb_).Run();
    297       }
    298 
    299       return;
    300 
    301     case kPaused:
    302     case kStopped:
    303     case kUninitialized:
    304     case kEnded:
    305       NOTREACHED();
    306       return;
    307   }
    308 
    309   base::TimeDelta start = text_cue->timestamp();
    310 
    311   if (state->text_ranges_.AddCue(start)) {
    312     base::TimeDelta end = start + text_cue->duration();
    313 
    314     state->text_track->addWebVTTCue(start, end,
    315                                     text_cue->id(),
    316                                     text_cue->text(),
    317                                     text_cue->settings());
    318   }
    319 
    320   if (state_ == kPlaying) {
    321     Read(state, text_stream);
    322     return;
    323   }
    324 
    325   if (pending_read_count_ == 0) {
    326       DCHECK_EQ(state_, kPausePending) << "state_ " << state_;
    327       state_ = kPaused;
    328       base::ResetAndReturn(&pause_cb_).Run();
    329   }
    330 }
    331 
    332 void TextRenderer::OnAddTextTrackDone(DemuxerStream* text_stream,
    333                                       scoped_ptr<TextTrack> text_track) {
    334   DCHECK(task_runner_->BelongsToCurrentThread());
    335   DCHECK(state_ != kUninitialized &&
    336          state_ != kStopped &&
    337          state_ != kStopPending) << "state_ " << state_;
    338   DCHECK(text_stream);
    339   DCHECK(text_track);
    340 
    341   scoped_ptr<TextTrackState> state(new TextTrackState(text_track.Pass()));
    342   text_track_state_map_[text_stream] = state.release();
    343   pending_eos_set_.insert(text_stream);
    344 
    345   if (state_ == kPlaying)
    346     Read(text_track_state_map_[text_stream], text_stream);
    347 }
    348 
    349 void TextRenderer::Read(
    350     TextTrackState* state,
    351     DemuxerStream* text_stream) {
    352   DCHECK_NE(state->read_state, TextTrackState::kReadPending);
    353 
    354   state->read_state = TextTrackState::kReadPending;
    355   ++pending_read_count_;
    356 
    357   text_stream->Read(base::Bind(
    358       &TextRenderer::BufferReady, weak_factory_.GetWeakPtr(), text_stream));
    359 }
    360 
    361 TextRenderer::TextTrackState::TextTrackState(scoped_ptr<TextTrack> tt)
    362     : read_state(kReadIdle),
    363       text_track(tt.Pass()) {
    364 }
    365 
    366 TextRenderer::TextTrackState::~TextTrackState() {
    367 }
    368 
    369 }  // namespace media
    370