1// Copyright 2014 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 "chromecast/media/cma/base/buffering_controller.h"
6
7#include "base/bind.h"
8#include "base/location.h"
9#include "base/message_loop/message_loop_proxy.h"
10#include "chromecast/media/cma/base/buffering_state.h"
11#include "chromecast/media/cma/base/cma_logging.h"
12#include "media/base/buffers.h"
13
14namespace chromecast {
15namespace media {
16
17BufferingController::BufferingController(
18    const scoped_refptr<BufferingConfig>& config,
19    const BufferingNotificationCB& buffering_notification_cb)
20    : config_(config),
21      buffering_notification_cb_(buffering_notification_cb),
22      is_buffering_(false),
23      begin_buffering_time_(base::Time()),
24      weak_factory_(this) {
25  weak_this_ = weak_factory_.GetWeakPtr();
26  thread_checker_.DetachFromThread();
27}
28
29BufferingController::~BufferingController() {
30  // Some weak pointers might possibly be invalidated here.
31  DCHECK(thread_checker_.CalledOnValidThread());
32}
33
34void BufferingController::UpdateHighLevelThreshold(
35    base::TimeDelta high_level_threshold) {
36  // Can only decrease the high level threshold.
37  if (high_level_threshold > config_->high_level())
38    return;
39  CMALOG(kLogControl) << "High buffer threshold: "
40                      << high_level_threshold.InMilliseconds();
41  config_->set_high_level(high_level_threshold);
42
43  // Make sure the low level threshold is somewhat consistent.
44  // Currently, we set it to one third of the high level threshold:
45  // this value could be adjusted in the future.
46  base::TimeDelta low_level_threshold = high_level_threshold / 3;
47  if (low_level_threshold <= config_->low_level()) {
48    CMALOG(kLogControl) << "Low buffer threshold: "
49                        << low_level_threshold.InMilliseconds();
50    config_->set_low_level(low_level_threshold);
51  }
52
53  // Signal all the streams the config has changed.
54  for (StreamList::iterator it = stream_list_.begin();
55       it != stream_list_.end(); ++it) {
56    (*it)->OnConfigChanged();
57  }
58
59  // Once all the streams have been notified, the buffering state must be
60  // updated (no notification is received from the streams).
61  OnBufferingStateChanged(false, false);
62}
63
64scoped_refptr<BufferingState> BufferingController::AddStream() {
65  DCHECK(thread_checker_.CalledOnValidThread());
66
67  // Add a new stream to the list of streams being monitored.
68  scoped_refptr<BufferingState> buffering_state(new BufferingState(
69      config_,
70      base::Bind(&BufferingController::OnBufferingStateChanged, weak_this_,
71                 false, false),
72      base::Bind(&BufferingController::UpdateHighLevelThreshold, weak_this_)));
73  stream_list_.push_back(buffering_state);
74
75  // Update the state and force a notification to the streams.
76  // TODO(damienv): Should this be a PostTask ?
77  OnBufferingStateChanged(true, false);
78
79  return buffering_state;
80}
81
82void BufferingController::SetMediaTime(base::TimeDelta time) {
83  for (StreamList::iterator it = stream_list_.begin();
84       it != stream_list_.end(); ++it) {
85    (*it)->SetMediaTime(time);
86  }
87}
88
89base::TimeDelta BufferingController::GetMaxRenderingTime() const {
90  base::TimeDelta max_rendering_time(::media::kNoTimestamp());
91  for (StreamList::const_iterator it = stream_list_.begin();
92       it != stream_list_.end(); ++it) {
93    base::TimeDelta max_stream_rendering_time =
94        (*it)->GetMaxRenderingTime();
95    if (max_stream_rendering_time == ::media::kNoTimestamp())
96      return ::media::kNoTimestamp();
97    if (max_rendering_time == ::media::kNoTimestamp() ||
98        max_stream_rendering_time < max_rendering_time) {
99      max_rendering_time = max_stream_rendering_time;
100    }
101  }
102  return max_rendering_time;
103}
104
105void BufferingController::Reset() {
106  DCHECK(thread_checker_.CalledOnValidThread());
107
108  is_buffering_ = false;
109  stream_list_.clear();
110}
111
112void BufferingController::OnBufferingStateChanged(
113    bool force_notification, bool buffering_timeout) {
114  DCHECK(thread_checker_.CalledOnValidThread());
115
116  // Log the state of each stream.
117  DumpState();
118
119  bool is_low_buffering = IsLowBufferLevel();
120  bool is_high_buffering = !is_low_buffering;
121  if (!buffering_timeout) {
122    // Hysteresis:
123    // - to leave buffering, not only should we leave the low buffer level state
124    //   but we should go to the high buffer level state (medium is not enough).
125    is_high_buffering = IsHighBufferLevel();
126  }
127
128  bool is_buffering_prv = is_buffering_;
129  if (is_buffering_) {
130    if (is_high_buffering)
131      is_buffering_ = false;
132  } else {
133    if (is_low_buffering)
134      is_buffering_ = true;
135  }
136
137  // Start buffering.
138  if (is_buffering_ && !is_buffering_prv) {
139    begin_buffering_time_ = base::Time::Now();
140  }
141
142  // End buffering.
143  if (is_buffering_prv && !is_buffering_) {
144    // TODO(damienv): |buffering_user_time| could be a UMA histogram.
145    base::Time current_time = base::Time::Now();
146    base::TimeDelta buffering_user_time = current_time - begin_buffering_time_;
147    CMALOG(kLogControl)
148        << "Buffering took: "
149        << buffering_user_time.InMilliseconds() << "ms";
150  }
151
152  if (is_buffering_prv != is_buffering_ || force_notification)
153    buffering_notification_cb_.Run(is_buffering_);
154}
155
156bool BufferingController::IsHighBufferLevel() {
157  if (stream_list_.empty())
158    return true;
159
160  bool is_high_buffering = true;
161  for (StreamList::iterator it = stream_list_.begin();
162       it != stream_list_.end(); ++it) {
163    BufferingState::State stream_state = (*it)->GetState();
164    is_high_buffering = is_high_buffering &&
165        ((stream_state == BufferingState::kHighLevel) ||
166         (stream_state == BufferingState::kEosReached));
167  }
168  return is_high_buffering;
169}
170
171bool BufferingController::IsLowBufferLevel() {
172  if (stream_list_.empty())
173    return false;
174
175  for (StreamList::iterator it = stream_list_.begin();
176       it != stream_list_.end(); ++it) {
177    BufferingState::State stream_state = (*it)->GetState();
178    if (stream_state == BufferingState::kLowLevel)
179      return true;
180  }
181
182  return false;
183}
184
185void BufferingController::DumpState() const {
186  CMALOG(kLogControl) << __FUNCTION__;
187  for (StreamList::const_iterator it = stream_list_.begin();
188       it != stream_list_.end(); ++it) {
189    CMALOG(kLogControl) << (*it)->ToString();
190  }
191}
192
193}  // namespace media
194}  // namespace chromecast
195