OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 "chrome/browser/media/router/discovery/mdns/cast_media_sink_service.h" | |
6 | |
7 #include "chrome/browser/browser_process.h" | |
8 #include "chrome/common/media_router/discovery/media_sink_internal.h" | |
9 #include "components/cast_channel/cast_socket_service.h" | |
10 #include "components/cast_channel/cast_socket_service_factory.h" | |
11 #include "components/net_log/chrome_net_log.h" | |
12 #include "content/public/common/content_client.h" | |
13 #include "net/base/host_port_pair.h" | |
14 #include "net/base/ip_address.h" | |
15 | |
16 namespace { | |
17 // mDNS service types. | |
18 const char kCastServiceType[] = "_googlecast._tcp.local"; | |
19 | |
20 enum ErrorType { | |
21 NONE, | |
22 NOT_CAST_DEVICE, | |
23 MISSING_ID, | |
24 MISSING_FRIENDLY_NAME, | |
25 MISSING_OR_INVALID_IP_ADDRESS, | |
26 MISSING_OR_INVALID_PORT, | |
27 }; | |
28 | |
29 ErrorType CreateCastMediaSink(const media_router::DnsSdService& service, | |
30 int channel_id, | |
31 bool audio_only, | |
32 media_router::MediaSinkInternal* cast_sink) { | |
33 DCHECK(cast_sink); | |
34 if (service.service_name.find(kCastServiceType) == std::string::npos) | |
35 return ErrorType::NOT_CAST_DEVICE; | |
36 | |
37 net::IPAddress ip_address; | |
38 if (!ip_address.AssignFromIPLiteral(service.ip_address)) | |
39 return ErrorType::MISSING_OR_INVALID_IP_ADDRESS; | |
40 | |
41 std::map<std::string, std::string> service_data; | |
42 for (const auto& item : service.service_data) { | |
43 // |item| format should be "id=xxxxxx", etc. | |
44 size_t split_idx = item.find('='); | |
45 if (split_idx == std::string::npos) | |
46 continue; | |
47 | |
48 std::string key = item.substr(0, split_idx); | |
49 std::string val = item.substr(split_idx + 1); | |
50 service_data[key] = val; | |
51 } | |
52 | |
53 // When use this "sink" within browser, please note it will have a different | |
54 // ID when it is sent to the extension, because it derives a different sink ID | |
55 // using the given sink ID. | |
56 std::string unique_id = service_data["id"]; | |
57 if (unique_id.empty()) | |
58 return ErrorType::MISSING_ID; | |
59 std::string friendly_name = service_data["fn"]; | |
60 if (friendly_name.empty()) | |
61 return ErrorType::MISSING_FRIENDLY_NAME; | |
62 media_router::MediaSink sink(unique_id, friendly_name, | |
63 media_router::MediaSink::IconType::CAST); | |
64 | |
65 media_router::CastSinkExtraData extra_data; | |
66 extra_data.ip_address = ip_address; | |
67 extra_data.model_name = service_data["md"]; | |
68 extra_data.capabilities = cast_channel::CastDeviceCapability::AUDIO_OUT; | |
69 if (!audio_only) | |
70 extra_data.capabilities |= cast_channel::CastDeviceCapability::VIDEO_OUT; | |
71 extra_data.cast_channel_id = channel_id; | |
72 | |
73 cast_sink->set_sink(sink); | |
74 cast_sink->set_cast_data(extra_data); | |
75 | |
76 return ErrorType::NONE; | |
77 } | |
78 | |
79 } // namespace | |
80 | |
81 namespace media_router { | |
82 | |
83 CastMediaSinkService::CastMediaSinkService( | |
84 const OnSinksDiscoveredCallback& callback, | |
85 content::BrowserContext* browser_context) | |
86 : MediaSinkServiceBase(callback) { | |
87 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
88 cast_socket_service_ = cast_channel::CastSocketServiceFactory::GetInstance() | |
imcheng
2017/06/15 00:50:59
Would we need to add a DependsOn from MediaRouterF
zhaobin
2017/06/20 02:30:28
It seems fine since we call cast_channel::CastSock
| |
89 ->GetForBrowserContext(browser_context); | |
90 DCHECK(cast_socket_service_); | |
91 } | |
92 | |
93 CastMediaSinkService::~CastMediaSinkService() {} | |
94 | |
95 void CastMediaSinkService::Start() { | |
96 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
97 if (dns_sd_registry_) | |
98 return; | |
99 | |
100 dns_sd_registry_ = test_dns_sd_registry_ ? test_dns_sd_registry_ | |
101 : DnsSdRegistry::GetInstance(); | |
102 dns_sd_registry_->AddObserver(this); | |
103 dns_sd_registry_->RegisterDnsSdListener(kCastServiceType); | |
104 MediaSinkServiceBase::StartTimer(); | |
105 } | |
106 | |
107 void CastMediaSinkService::Stop() { | |
108 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
109 if (!dns_sd_registry_) | |
110 return; | |
111 | |
112 dns_sd_registry_->UnregisterDnsSdListener(kCastServiceType); | |
113 dns_sd_registry_->RemoveObserver(this); | |
114 dns_sd_registry_ = nullptr; | |
115 MediaSinkServiceBase::StopTimer(); | |
116 } | |
117 | |
118 void CastMediaSinkService::SetDnsSdRegistryForTesting(DnsSdRegistry* registry) { | |
119 DCHECK(!test_dns_sd_registry_); | |
120 test_dns_sd_registry_ = registry; | |
121 } | |
122 | |
123 void CastMediaSinkService::SetCastSocketServiceForTesting( | |
124 cast_channel::CastSocketService* cast_socket_service) { | |
125 cast_socket_service_ = cast_socket_service; | |
imcheng
2017/06/15 00:50:56
Add a DCHECK to be consistent with above.
zhaobin
2017/06/20 02:30:28
Moved this to a ctor used by test.
| |
126 } | |
127 | |
128 void CastMediaSinkService::OnDnsSdEvent( | |
129 const std::string& service_type, | |
130 const DnsSdRegistry::DnsSdServiceList& services) { | |
131 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
132 DVLOG(2) << "CastMediaSinkService::OnDnsSdEvent found " << services.size() | |
133 << " services"; | |
134 | |
135 current_sinks_.clear(); | |
136 current_services_ = services; | |
137 | |
138 for (const auto& service : services) { | |
139 net::IPAddress ip_address; | |
140 if (!ip_address.AssignFromIPLiteral(service.ip_address)) { | |
141 DVLOG(2) << "Invalid ip_addresss: " << service.ip_address; | |
142 continue; | |
143 } | |
144 net::HostPortPair host_port_pair = | |
145 net::HostPortPair::FromString(service.service_host_port); | |
146 | |
147 content::BrowserThread::PostTask( | |
148 content::BrowserThread::IO, FROM_HERE, | |
149 base::Bind(&CastMediaSinkService::OpenChannelOnIOThread, this, service, | |
150 net::IPEndPoint(ip_address, host_port_pair.port()))); | |
151 } | |
152 } | |
153 | |
154 void CastMediaSinkService::OpenChannelOnIOThread( | |
155 const DnsSdService& service, | |
156 const net::IPEndPoint& ip_endpoint) { | |
157 cast_socket_service_->OpenSocket( | |
imcheng
2017/06/15 00:50:57
What if the socket already exists (opened by brows
zhaobin
2017/06/20 02:30:28
Handled inside CastSocketService::OpenSocket().
| |
158 ip_endpoint, g_browser_process->net_log(), | |
159 base::Bind(&CastMediaSinkService::OnChannelOpenedOnIOThread, this, | |
160 service)); | |
161 } | |
162 | |
163 void CastMediaSinkService::OnChannelOpenedOnIOThread( | |
164 const DnsSdService& service, | |
165 int channel_id, | |
imcheng
2017/06/15 00:50:56
Would it be possible to pass back the CastSocket i
zhaobin
2017/06/20 02:30:28
DISALLOW_COPY_AND_ASSIGN(CastSocketImpl);
Do not
| |
166 cast_channel::ChannelError channel_error) { | |
167 if (channel_error != cast_channel::ChannelError::NONE) { | |
168 DVLOG(2) << "Fail to open channel " << service.ip_address << ": " | |
169 << service.service_host_port | |
170 << " [ChannelError]: " << (int)channel_error; | |
171 return; | |
172 } | |
173 | |
174 auto* socket = cast_socket_service_->GetSocket(channel_id); | |
175 if (!socket) { | |
176 DVLOG(2) << "Fail to find socket with [channel_id]: " << channel_id; | |
177 return; | |
178 } | |
179 | |
180 content::BrowserThread::PostTask( | |
181 content::BrowserThread::UI, FROM_HERE, | |
182 base::Bind(&CastMediaSinkService::OnChannelOpenedOnUIThread, this, | |
183 service, channel_id, socket->audio_only())); | |
184 } | |
185 | |
186 void CastMediaSinkService::OnChannelOpenedOnUIThread( | |
187 const DnsSdService& service, | |
188 int channel_id, | |
189 bool audio_only) { | |
190 MediaSinkInternal sink; | |
imcheng
2017/06/15 00:50:56
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
zhaobin
2017/06/20 02:30:28
Done.
| |
191 ErrorType error = CreateCastMediaSink(service, channel_id, audio_only, &sink); | |
192 if (error != ErrorType::NONE) { | |
193 DVLOG(2) << "Fail to create Cast device [error]: " << error; | |
194 return; | |
195 } | |
196 | |
197 if (!base::ContainsValue(current_services_, service)) { | |
imcheng
2017/06/15 00:50:56
This check means we would drop the sink if we some
zhaobin
2017/06/20 02:30:28
DialMediaSinkService has similar logic so copied i
| |
198 DVLOG(2) << "Service data not found in current service data list..."; | |
199 return; | |
200 } | |
201 | |
202 DVLOG(2) << "Ading sink to current_sinks_ [id]: " << sink.sink().id(); | |
203 current_sinks_.insert(sink); | |
204 MediaSinkServiceBase::RestartTimer(); | |
205 } | |
206 | |
207 } // namespace media_router | |
OLD | NEW |