11320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci// Copyright 2014 The Chromium Authors. All rights reserved.
21320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci// Use of this source code is governed by a BSD-style license that can be
31320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci// found in the LICENSE file.
41320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
51320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "chromecast/media/cma/filters/demuxer_stream_adapter.h"
61320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
71320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/bind.h"
81320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/callback_helpers.h"
91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/single_thread_task_runner.h"
101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "chromecast/media/cma/base/cma_logging.h"
121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "chromecast/media/cma/base/media_task_runner.h"
141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "media/base/bind_to_current_loop.h"
151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "media/base/buffers.h"
161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "media/base/decoder_buffer.h"
171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "media/base/demuxer_stream.h"
181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccinamespace chromecast {
201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccinamespace media {
211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccinamespace {
231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciclass DummyMediaTaskRunner : public MediaTaskRunner {
251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci public:
261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DummyMediaTaskRunner(
271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // MediaTaskRunner implementation.
301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  virtual bool PostMediaTask(
311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      const tracked_objects::Location& from_here,
321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      const base::Closure& task,
331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      base::TimeDelta timestamp) OVERRIDE;
341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci private:
361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  virtual ~DummyMediaTaskRunner();
371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DISALLOW_COPY_AND_ASSIGN(DummyMediaTaskRunner);
411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci};
421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciDummyMediaTaskRunner::DummyMediaTaskRunner(
441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  : task_runner_(task_runner) {
461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciDummyMediaTaskRunner::~DummyMediaTaskRunner() {
491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccibool DummyMediaTaskRunner::PostMediaTask(
521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const tracked_objects::Location& from_here,
531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const base::Closure& task,
541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    base::TimeDelta timestamp) {
551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return task_runner_->PostTask(from_here, task);
561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}  // namespace
591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciDemuxerStreamAdapter::DemuxerStreamAdapter(
611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const scoped_refptr<BalancedMediaTaskRunnerFactory>&
631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    media_task_runner_factory,
641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    ::media::DemuxerStream* demuxer_stream)
651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    : task_runner_(task_runner),
661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      media_task_runner_factory_(media_task_runner_factory),
671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      media_task_runner_(new DummyMediaTaskRunner(task_runner)),
681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      demuxer_stream_(demuxer_stream),
691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      is_pending_read_(false),
701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      is_pending_demuxer_read_(false),
711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      weak_factory_(this),
721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      weak_this_(weak_factory_.GetWeakPtr()) {
731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  ResetMediaTaskRunner();
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  thread_checker_.DetachFromThread();
751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciDemuxerStreamAdapter::~DemuxerStreamAdapter() {
781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Needed since we use weak pointers:
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // weak pointers must be invalidated on the same thread.
801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(thread_checker_.CalledOnValidThread());
811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DemuxerStreamAdapter::Read(const ReadCB& read_cb) {
841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(thread_checker_.CalledOnValidThread());
851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(flush_cb_.is_null());
871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Support only one read at a time.
891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(!is_pending_read_);
901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  is_pending_read_ = true;
911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  ReadInternal(read_cb);
921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DemuxerStreamAdapter::ReadInternal(const ReadCB& read_cb) {
951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  bool may_run_in_future = media_task_runner_->PostMediaTask(
961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      FROM_HERE,
971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      base::Bind(&DemuxerStreamAdapter::RequestBuffer, weak_this_, read_cb),
981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      max_pts_);
991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(may_run_in_future);
1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DemuxerStreamAdapter::Flush(const base::Closure& flush_cb) {
1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(thread_checker_.CalledOnValidThread());
1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  CMALOG(kLogControl) << __FUNCTION__;
1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Flush cancels any pending read.
1071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  is_pending_read_ = false;
1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Reset the decoder configurations.
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  audio_config_ = ::media::AudioDecoderConfig();
1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  video_config_ = ::media::VideoDecoderConfig();
1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Create a new media task runner for the upcoming media timeline.
1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  ResetMediaTaskRunner();
1151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(flush_cb_.is_null());
1171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (is_pending_demuxer_read_) {
1181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // If there is a pending demuxer read, the implicit contract
1191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // is that the pending read must be completed before invoking the
1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // flush callback.
1211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    flush_cb_ = flush_cb;
1221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return;
1231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // At this point, there is no more pending demuxer read,
1261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // so all the previous tasks associated with the current timeline
1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // can be cancelled.
1281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  weak_factory_.InvalidateWeakPtrs();
1291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  weak_this_ = weak_factory_.GetWeakPtr();
1301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  CMALOG(kLogControl) << "Flush done";
1321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  flush_cb.Run();
1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DemuxerStreamAdapter::ResetMediaTaskRunner() {
1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(thread_checker_.CalledOnValidThread());
1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  max_pts_ = ::media::kNoTimestamp();
1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (media_task_runner_factory_.get()) {
1401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    media_task_runner_ =
1411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        media_task_runner_factory_->CreateMediaTaskRunner(task_runner_);
1421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
1441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DemuxerStreamAdapter::RequestBuffer(const ReadCB& read_cb) {
1461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(thread_checker_.CalledOnValidThread());
1471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  is_pending_demuxer_read_ = true;
1481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  demuxer_stream_->Read(::media::BindToCurrentLoop(
1491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      base::Bind(&DemuxerStreamAdapter::OnNewBuffer, weak_this_, read_cb)));
1501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
1511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DemuxerStreamAdapter::OnNewBuffer(
1531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const ReadCB& read_cb,
1541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    ::media::DemuxerStream::Status status,
1551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    const scoped_refptr< ::media::DecoderBuffer>& input) {
1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(thread_checker_.CalledOnValidThread());
1571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  is_pending_demuxer_read_ = false;
1591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Just discard the buffer in the flush stage.
1611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (!flush_cb_.is_null()) {
1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    CMALOG(kLogControl) << "Flush done";
1631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    base::ResetAndReturn(&flush_cb_).Run();
1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return;
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (status == ::media::DemuxerStream::kAborted) {
1681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    DCHECK(input.get() == NULL);
1691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return;
1701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (status == ::media::DemuxerStream::kConfigChanged) {
1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    DCHECK(input.get() == NULL);
1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if (demuxer_stream_->type() == ::media::DemuxerStream::VIDEO)
1751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      video_config_ = demuxer_stream_->video_decoder_config();
1761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if (demuxer_stream_->type() == ::media::DemuxerStream::AUDIO)
1771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      audio_config_ = demuxer_stream_->audio_decoder_config();
1781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // Got a new config, but we still need to get a frame.
1801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    ReadInternal(read_cb);
1811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return;
1821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK_EQ(status, ::media::DemuxerStream::kOk);
1851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Updates the timestamp used for task scheduling.
1871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (!input->end_of_stream() &&
1881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      input->timestamp() != ::media::kNoTimestamp() &&
1891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      (max_pts_ == ::media::kNoTimestamp() || input->timestamp() > max_pts_)) {
1901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    max_pts_ = input->timestamp();
1911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Provides the buffer as well as possibly valid audio and video configs.
1941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  is_pending_read_ = false;
1951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  scoped_refptr<DecoderBufferBase> buffer(new DecoderBufferAdapter(input));
1961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  read_cb.Run(buffer, audio_config_, video_config_);
1971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Back to the default audio/video config:
1991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // an invalid audio/video config means there is no config update.
2001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (audio_config_.IsValidConfig())
2011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    audio_config_ = ::media::AudioDecoderConfig();
2021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (video_config_.IsValidConfig())
2031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    video_config_ = ::media::VideoDecoderConfig();
2041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
2051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}  // namespace media
2071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}  // namespace chromecast
208