1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// found in the LICENSE file.
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include <gtest/gtest.h>
6e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch#include <stdint.h>
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/bind.h"
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/bind_helpers.h"
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/memory/scoped_ptr.h"
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/test/simple_test_tick_clock.h"
121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/values.h"
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "media/cast/cast_config.h"
14116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "media/cast/net/cast_transport_config.h"
15116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "media/cast/net/cast_transport_sender_impl.h"
16116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "media/cast/net/rtcp/rtcp.h"
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "media/cast/test/fake_single_thread_task_runner.h"
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h"
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)namespace media {
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)namespace cast {
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
2303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)namespace {
2403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)const int64 kStartMillisecond = INT64_C(12345678900000);
2503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)const uint32 kVideoSsrc = 1;
2603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)const uint32 kAudioSsrc = 2;
2703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}  // namespace
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
29116680a4aac90f2aa7413d9095a592090648e557Ben Murdochclass FakePacketSender : public PacketSender {
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public:
3103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  FakePacketSender()
3203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      : paused_(false), packets_sent_(0), bytes_sent_(0) {}
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
340529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE {
3503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    if (paused_) {
3603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      stored_packet_ = packet;
3703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      callback_ = cb;
3803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      return false;
3903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    }
4003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    ++packets_sent_;
4103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    bytes_sent_ += packet->data.size();
420529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return true;
430529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  }
4403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
4503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  virtual int64 GetBytesSent() OVERRIDE {
4603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return bytes_sent_;
4703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  }
4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
4903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  void SetPaused(bool paused) {
5003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    paused_ = paused;
511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if (!paused && stored_packet_.get()) {
5203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      SendPacket(stored_packet_, callback_);
5303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      callback_.Run();
5403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    }
5503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  }
5603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
5703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  int packets_sent() const { return packets_sent_; }
5803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
5903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) private:
6003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  bool paused_;
6103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  base::Closure callback_;
6203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  PacketRef stored_packet_;
6303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  int packets_sent_;
6403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  int64 bytes_sent_;
6503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
6603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(FakePacketSender);
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class CastTransportSenderImplTest : public ::testing::Test {
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) protected:
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  CastTransportSenderImplTest()
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      : num_times_callback_called_(0) {
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    testing_clock_.Advance(
74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        base::TimeDelta::FromMilliseconds(kStartMillisecond));
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    task_runner_ = new test::FakeSingleThreadTaskRunner(&testing_clock_);
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  virtual ~CastTransportSenderImplTest() {}
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  void InitWithoutLogging() {
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    transport_sender_.reset(
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        new CastTransportSenderImpl(NULL,
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    &testing_clock_,
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    net::IPEndPoint(),
851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    make_scoped_ptr(new base::DictionaryValue),
861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    base::Bind(&UpdateCastTransportStatus),
871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    BulkRawEventsCallback(),
881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    base::TimeDelta(),
891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    task_runner_,
901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    &transport_));
911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    task_runner_->RunTasks();
921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  void InitWithOptions() {
951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    scoped_ptr<base::DictionaryValue> options(
961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        new base::DictionaryValue);
971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    options->SetBoolean("DHCP", true);
981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    options->SetBoolean("disable_wifi_scan", true);
991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    options->SetBoolean("media_streaming_mode", true);
1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    options->SetInteger("pacer_target_burst_size", 20);
1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    options->SetInteger("pacer_max_burst_size", 100);
1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    transport_sender_.reset(
1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        new CastTransportSenderImpl(NULL,
1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    &testing_clock_,
1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    net::IPEndPoint(),
1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    options.Pass(),
107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    base::Bind(&UpdateCastTransportStatus),
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    BulkRawEventsCallback(),
109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    base::TimeDelta(),
110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    task_runner_,
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    &transport_));
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    task_runner_->RunTasks();
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  void InitWithLogging() {
116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    transport_sender_.reset(new CastTransportSenderImpl(
117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        NULL,
118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        &testing_clock_,
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        net::IPEndPoint(),
1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        make_scoped_ptr(new base::DictionaryValue),
121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        base::Bind(&UpdateCastTransportStatus),
122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        base::Bind(&CastTransportSenderImplTest::LogRawEvents,
123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                   base::Unretained(this)),
124a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        base::TimeDelta::FromMilliseconds(10),
125a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        task_runner_,
126a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        &transport_));
127a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    task_runner_->RunTasks();
128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
13003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  void InitializeVideo() {
13103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    CastTransportRtpConfig rtp_config;
13203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    rtp_config.ssrc = kVideoSsrc;
13303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    rtp_config.feedback_ssrc = 2;
13403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    rtp_config.rtp_payload_type = 3;
13503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    transport_sender_->InitializeVideo(rtp_config,
13603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                       RtcpCastMessageCallback(),
13703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                       RtcpRttCallback());
13803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  }
13903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
14003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  void InitializeAudio() {
14103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    CastTransportRtpConfig rtp_config;
14203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    rtp_config.ssrc = kAudioSsrc;
14303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    rtp_config.feedback_ssrc = 3;
14403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    rtp_config.rtp_payload_type = 4;
14503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    transport_sender_->InitializeAudio(rtp_config,
14603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                       RtcpCastMessageCallback(),
14703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                       RtcpRttCallback());
14803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  }
14903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  void LogRawEvents(const std::vector<PacketEvent>& packet_events,
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    const std::vector<FrameEvent>& frame_events) {
152a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    num_times_callback_called_++;
153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
155116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  static void UpdateCastTransportStatus(CastTransportStatus status) {
156a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  base::SimpleTestTickClock testing_clock_;
159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_;
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  scoped_ptr<CastTransportSenderImpl> transport_sender_;
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  FakePacketSender transport_;
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  int num_times_callback_called_;
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
164a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
165a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)TEST_F(CastTransportSenderImplTest, InitWithoutLogging) {
166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  InitWithoutLogging();
1675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
168a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  EXPECT_EQ(0, num_times_callback_called_);
169a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
170a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
171a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)TEST_F(CastTransportSenderImplTest, InitWithLogging) {
172a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  InitWithLogging();
1735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
1745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  EXPECT_EQ(5, num_times_callback_called_);
175a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
176a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTEST_F(CastTransportSenderImplTest, InitWithOptions) {
1781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  InitWithOptions();
1791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
1801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  EXPECT_EQ(0, num_times_callback_called_);
1811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
1821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
18303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)TEST_F(CastTransportSenderImplTest, NacksCancelRetransmits) {
18403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitWithoutLogging();
18503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitializeVideo();
18603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
18703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
18803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // A fake frame that will be decomposed into 4 packets.
18903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EncodedFrame fake_frame;
19003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.frame_id = 1;
19103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.rtp_timestamp = 1;
19203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.dependency = EncodedFrame::KEY;
19303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.data.resize(5000, ' ');
19403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
1951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  transport_sender_->InsertFrame(kVideoSsrc, fake_frame);
19603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
19703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(4, transport_.packets_sent());
19803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
19903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend packet 0.
20003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  MissingFramesAndPacketsMap missing_packets;
20103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  missing_packets[1].insert(0);
20203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  missing_packets[1].insert(1);
20303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  missing_packets[1].insert(2);
20403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
20503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(true);
20603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  DedupInfo dedup_info;
20703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(10);
20803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->ResendPackets(
20903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      kVideoSsrc, missing_packets, true, dedup_info);
21003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
21103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
21203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
21303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  RtcpCastMessage cast_message;
21403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.media_ssrc = kVideoSsrc;
21503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.ack_frame_id = 1;
21603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.missing_frames_and_packets[1].insert(3);
21703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->OnReceivedCastMessage(kVideoSsrc,
21803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           RtcpCastMessageCallback(),
21903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           cast_message);
22003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(false);
22103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
22203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
22303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend one packet in the socket when unpaused.
22403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend one more packet from NACK.
22503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(6, transport_.packets_sent());
22603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
22703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
22803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)TEST_F(CastTransportSenderImplTest, CancelRetransmits) {
22903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitWithoutLogging();
23003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitializeVideo();
23103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
23203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
23303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // A fake frame that will be decomposed into 4 packets.
23403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EncodedFrame fake_frame;
23503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.frame_id = 1;
23603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.rtp_timestamp = 1;
23703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.dependency = EncodedFrame::KEY;
23803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.data.resize(5000, ' ');
23903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
2401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  transport_sender_->InsertFrame(kVideoSsrc, fake_frame);
24103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
24203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(4, transport_.packets_sent());
24303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
24403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend all packets for frame 1.
24503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  MissingFramesAndPacketsMap missing_packets;
24603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  missing_packets[1].insert(kRtcpCastAllPacketsLost);
24703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
24803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(true);
24903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  DedupInfo dedup_info;
25003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(10);
25103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->ResendPackets(
25203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      kVideoSsrc, missing_packets, true, dedup_info);
25303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
25403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
25503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  std::vector<uint32> cancel_sending_frames;
25603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cancel_sending_frames.push_back(1);
25703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->CancelSendingFrames(kVideoSsrc,
25803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                         cancel_sending_frames);
25903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(false);
26003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
26103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
26203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend one packet in the socket when unpaused.
26303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(5, transport_.packets_sent());
26403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
26503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
26603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)TEST_F(CastTransportSenderImplTest, Kickstart) {
26703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitWithoutLogging();
26803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitializeVideo();
26903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
27003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
27103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // A fake frame that will be decomposed into 4 packets.
27203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EncodedFrame fake_frame;
27303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.frame_id = 1;
27403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.rtp_timestamp = 1;
27503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.dependency = EncodedFrame::KEY;
27603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_frame.data.resize(5000, ' ');
27703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
27803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(true);
2791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  transport_sender_->InsertFrame(kVideoSsrc, fake_frame);
28003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->ResendFrameForKickstart(kVideoSsrc, 1);
28103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(false);
28203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
28303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(4, transport_.packets_sent());
28403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
28503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend 2 packets for frame 1.
28603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  MissingFramesAndPacketsMap missing_packets;
28703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  missing_packets[1].insert(0);
28803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  missing_packets[1].insert(1);
28903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
29003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(true);
29103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  DedupInfo dedup_info;
29203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(10);
29303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->ResendPackets(
29403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      kVideoSsrc, missing_packets, true, dedup_info);
29503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->ResendFrameForKickstart(kVideoSsrc, 1);
29603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_.SetPaused(false);
29703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
29803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
29903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Resend one packet in the socket when unpaused.
30003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Two more retransmission packets sent.
30103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(7, transport_.packets_sent());
30203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
30303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
30403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)TEST_F(CastTransportSenderImplTest, DedupRetransmissionWithAudio) {
30503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitWithoutLogging();
30603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitializeAudio();
30703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  InitializeVideo();
30803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50));
30903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
31003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Send two audio frames.
31103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EncodedFrame fake_audio;
31203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_audio.frame_id = 1;
31303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_audio.reference_time = testing_clock_.NowTicks();
31403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_audio.dependency = EncodedFrame::KEY;
31503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_audio.data.resize(100, ' ');
3161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  transport_sender_->InsertFrame(kAudioSsrc, fake_audio);
31703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2));
31803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_audio.frame_id = 2;
31903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_audio.reference_time = testing_clock_.NowTicks();
3201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  transport_sender_->InsertFrame(kAudioSsrc, fake_audio);
32103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2));
32203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(2, transport_.packets_sent());
32303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
32403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Ack the first audio frame.
32503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  RtcpCastMessage cast_message;
32603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.media_ssrc = kAudioSsrc;
32703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.ack_frame_id = 1;
32803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->OnReceivedCastMessage(kAudioSsrc,
32903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           RtcpCastMessageCallback(),
33003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           cast_message);
33103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->RunTasks();
33203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(2, transport_.packets_sent());
33303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
33403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Send a fake video frame that will be decomposed into 4 packets.
33503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EncodedFrame fake_video;
33603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_video.frame_id = 1;
33703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_video.dependency = EncodedFrame::KEY;
33803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  fake_video.data.resize(5000, ' ');
3391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  transport_sender_->InsertFrame(kVideoSsrc, fake_video);
34003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->RunTasks();
34103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(6, transport_.packets_sent());
34203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
34303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Retransmission is reject because audio is not acked yet.
34403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.media_ssrc = kVideoSsrc;
34503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.ack_frame_id = 0;
34603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.missing_frames_and_packets[1].insert(3);
34703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10));
34803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->OnReceivedCastMessage(kVideoSsrc,
34903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           RtcpCastMessageCallback(),
35003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           cast_message);
35103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->RunTasks();
35203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(6, transport_.packets_sent());
35303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
35403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Ack the second audio frame.
35503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.media_ssrc = kAudioSsrc;
35603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.ack_frame_id = 2;
35703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.missing_frames_and_packets.clear();
35803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2));
35903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->OnReceivedCastMessage(kAudioSsrc,
36003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           RtcpCastMessageCallback(),
36103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           cast_message);
36203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->RunTasks();
36303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(6, transport_.packets_sent());
36403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
36503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Retransmission of video packet now accepted.
36603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.media_ssrc = kVideoSsrc;
36703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.ack_frame_id = 1;
36803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  cast_message.missing_frames_and_packets[1].insert(3);
36903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2));
37003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  transport_sender_->OnReceivedCastMessage(kVideoSsrc,
37103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           RtcpCastMessageCallback(),
37203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                           cast_message);
37303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  task_runner_->RunTasks();
37403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  EXPECT_EQ(7, transport_.packets_sent());
37503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
37603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
377a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}  // namespace cast
378a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}  // namespace media
379