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