Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 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/remoting/remoting_controller.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/logging.h" | |
| 9 #include "base/single_thread_task_runner.h" | |
| 10 #include "media/remoting/remoting_cdm.h" | |
| 11 | |
| 12 namespace media { | |
| 13 | |
| 14 RemotingController::RemotingController( | |
| 15 mojom::RemotingSourceRequest source_request, | |
| 16 mojom::RemoterPtr remoter) | |
| 17 : binding_(this, std::move(source_request)), | |
| 18 remoter_(std::move(remoter)), | |
| 19 task_runner_(base::ThreadTaskRunnerHandle::Get()), | |
| 20 weak_factory_(this) {} | |
| 21 | |
| 22 RemotingController::~RemotingController() {} | |
| 23 | |
| 24 void RemotingController::OnSinkAvailable() { | |
| 25 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 26 | |
| 27 is_sink_available_ = true; | |
| 28 UpdateAndMaybeSwitch(); | |
| 29 } | |
| 30 | |
| 31 void RemotingController::OnSinkGone() { | |
| 32 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 33 | |
| 34 is_sink_available_ = false; | |
| 35 UpdateAndMaybeSwitch(); | |
| 36 } | |
| 37 | |
| 38 void RemotingController::OnStarted() { | |
| 39 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 40 | |
| 41 VLOG(1) << "Remoting started successively."; | |
| 42 | |
| 43 // |switch_renderer_cb_| not being set indicates that this remoting session | |
| 44 // is started for creating CDM. | |
| 45 if (switch_renderer_cb_.is_null()) { | |
| 46 is_remoting_cdm_ = true; | |
| 47 DCHECK(!cdm_check_cb_.is_null()); | |
| 48 cdm_check_cb_.Run(true); | |
| 49 } else if (is_remoting_) { | |
| 50 switch_renderer_cb_.Run(); | |
| 51 } else { | |
| 52 remoter_->Stop(mojom::RemotingStopReason::LOCAL_PLAYBACK); | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 void RemotingController::OnStartFailed(mojom::RemotingStartFailReason reason) { | |
| 57 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 58 | |
| 59 VLOG(1) << "Failed to start remoting:" << reason; | |
| 60 | |
| 61 is_remoting_ = false; | |
| 62 if (switch_renderer_cb_.is_null()) { | |
| 63 DCHECK(!cdm_check_cb_.is_null()); | |
| 64 is_remoting_cdm_ = false; | |
| 65 cdm_check_cb_.Run(false); | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 void RemotingController::OnMessageFromSink( | |
| 70 const std::vector<uint8_t>& message) { | |
| 71 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 72 | |
| 73 // TODO(xjz): Merge with Eric's CL to handle the RPC messages here. | |
| 74 NOTIMPLEMENTED(); | |
| 75 } | |
| 76 | |
| 77 void RemotingController::OnStopped(mojom::RemotingStopReason reason) { | |
| 78 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 79 | |
| 80 VLOG(1) << "Remoting stopped: " << reason; | |
| 81 | |
| 82 is_remoting_ = false; | |
| 83 if (is_remoting_cdm_) | |
| 84 is_remoting_failed_ = true; | |
| 85 } | |
| 86 | |
| 87 void RemotingController::OnEnteredFullscreen() { | |
| 88 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 89 | |
| 90 is_fullscreen_ = true; | |
| 91 UpdateAndMaybeSwitch(); | |
| 92 } | |
| 93 | |
| 94 void RemotingController::OnExitedFullscreen() { | |
| 95 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 96 | |
| 97 is_fullscreen_ = false; | |
| 98 UpdateAndMaybeSwitch(); | |
| 99 } | |
| 100 | |
| 101 void RemotingController::OnSetCdm(CdmContext* cdm_context) { | |
| 102 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 103 | |
| 104 if (auto* cdm = RemotingCdm::From(cdm_context)) { | |
|
miu
2016/10/08 01:06:16
Possible edge-case issue: What if a remoting sessi
xjz
2016/10/20 21:25:28
This seems not a problem now. If a remoting sessio
| |
| 105 cdm_remoting_controller_ = cdm->remoting_controller(); | |
| 106 if (!cdm_remoting_controller_) | |
| 107 return; | |
| 108 cdm_remoting_controller_->SetSwitchRendererCallback(switch_renderer_cb_); | |
| 109 // This may trigger the switching renderer call in | |
| 110 // |cdm_remoting_controller_|. | |
| 111 cdm_remoting_controller_->OnMetadataChanged(metadata_); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 void RemotingController::SetSwitchRendererCallback( | |
| 116 const SwitchRendererCallback& cb) { | |
| 117 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 118 DCHECK(!cb.is_null()); | |
| 119 | |
| 120 switch_renderer_cb_ = cb; | |
| 121 } | |
| 122 | |
| 123 void RemotingController::OnMetadataChanged(const PipelineMetadata& metadata) { | |
| 124 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 125 | |
| 126 if (cdm_remoting_controller_) { | |
| 127 cdm_remoting_controller_->OnMetadataChanged(metadata); | |
| 128 return; | |
| 129 } | |
| 130 | |
| 131 metadata_ = metadata; | |
| 132 has_video_ = metadata.has_video; | |
| 133 has_audio_ = metadata.has_audio; | |
| 134 if (!has_video_ && !has_audio_) | |
| 135 return; | |
| 136 | |
| 137 if (has_video_) { | |
| 138 DCHECK(metadata.video_decoder_config.IsValidConfig()); | |
| 139 video_decoder_config_ = metadata.video_decoder_config; | |
| 140 is_encrypted_ |= video_decoder_config_.is_encrypted(); | |
| 141 } | |
| 142 if (has_audio_) { | |
| 143 DCHECK(metadata.audio_decoder_config.IsValidConfig()); | |
| 144 audio_decoder_config_ = metadata.audio_decoder_config; | |
| 145 is_encrypted_ |= audio_decoder_config_.is_encrypted(); | |
| 146 } | |
| 147 UpdateAndMaybeSwitch(); | |
| 148 } | |
| 149 | |
| 150 bool RemotingController::IsVideoCodecSupported() { | |
| 151 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 152 DCHECK(has_video_); | |
| 153 | |
| 154 switch (video_decoder_config_.codec()) { | |
| 155 case VideoCodec::kCodecH264: | |
| 156 case VideoCodec::kCodecVP8: | |
| 157 return true; | |
| 158 default: | |
| 159 VLOG(2) << "Remoting does not support video codec: " | |
| 160 << video_decoder_config_.codec(); | |
| 161 return false; | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 bool RemotingController::IsAudioCodecSupported() { | |
| 166 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 167 DCHECK(has_audio_); | |
| 168 | |
| 169 switch (audio_decoder_config_.codec()) { | |
| 170 case AudioCodec::kCodecAAC: | |
| 171 case AudioCodec::kCodecMP3: | |
| 172 case AudioCodec::kCodecPCM: | |
| 173 case AudioCodec::kCodecVorbis: | |
| 174 case AudioCodec::kCodecFLAC: | |
| 175 case AudioCodec::kCodecAMR_NB: | |
| 176 case AudioCodec::kCodecAMR_WB: | |
| 177 case AudioCodec::kCodecPCM_MULAW: | |
| 178 case AudioCodec::kCodecGSM_MS: | |
| 179 case AudioCodec::kCodecPCM_S16BE: | |
| 180 case AudioCodec::kCodecPCM_S24BE: | |
| 181 case AudioCodec::kCodecOpus: | |
| 182 case AudioCodec::kCodecEAC3: | |
| 183 case AudioCodec::kCodecPCM_ALAW: | |
| 184 case AudioCodec::kCodecALAC: | |
| 185 case AudioCodec::kCodecAC3: | |
| 186 return true; | |
| 187 default: | |
| 188 VLOG(2) << "Remoting does not support audio codec: " | |
| 189 << audio_decoder_config_.codec(); | |
| 190 return false; | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 bool RemotingController::ShouldBeRemoting() { | |
| 195 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 196 | |
| 197 if (has_video_ && !IsVideoCodecSupported()) | |
| 198 return false; | |
| 199 if (has_audio_ && !IsAudioCodecSupported()) | |
| 200 return false; | |
| 201 | |
| 202 if (is_encrypted_) { | |
| 203 DCHECK(!cdm_remoting_controller_); | |
| 204 return is_remoting_cdm_ && is_sink_available_; | |
| 205 } | |
| 206 | |
| 207 if (!is_sink_available_) | |
| 208 return false; | |
| 209 if (!is_fullscreen_) | |
| 210 return false; | |
| 211 return true; | |
| 212 } | |
| 213 | |
| 214 void RemotingController::UpdateAndMaybeSwitch() { | |
| 215 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 216 | |
| 217 // Demuxer is not initialized yet. | |
| 218 if (!has_audio_ && !has_video_) | |
| 219 return; | |
| 220 | |
| 221 // This RemotingController is created when creating CDM, and is not attached | |
| 222 // to media element yet. | |
| 223 if (switch_renderer_cb_.is_null()) | |
| 224 return; | |
| 225 | |
| 226 // When cdm is attached, renderer switching will only happen in | |
| 227 // |cdm_remoting_controller_| instead of here. | |
| 228 if (cdm_remoting_controller_) | |
| 229 return; | |
| 230 | |
| 231 // Remoting was failed and is not able to be restarted until user explicitly | |
| 232 // reloads the page. | |
| 233 if (is_remoting_failed_) | |
| 234 return; | |
| 235 | |
| 236 bool should_be_remoting = ShouldBeRemoting(); | |
| 237 if (is_remoting_ == should_be_remoting) | |
| 238 return; | |
| 239 | |
| 240 // Switch between local renderer and remoting renderer. | |
| 241 is_remoting_ = should_be_remoting; | |
| 242 if (is_remoting_) { | |
| 243 if (!is_remoting_cdm_) | |
| 244 // |swithc_renderer_cb_.Run()| will be called after remoting is started | |
| 245 // successfully for non-encrypted contents. | |
| 246 remoter_->Start(); | |
| 247 else | |
| 248 // Remoting was already started for encrypted contents. Calling | |
| 249 // |switch_renderer_cb_| to switch to remoting renderer. | |
| 250 switch_renderer_cb_.Run(); | |
| 251 } else { | |
| 252 switch_renderer_cb_.Run(); | |
| 253 remoter_->Stop(mojom::RemotingStopReason::LOCAL_PLAYBACK); | |
| 254 if (is_remoting_cdm_) | |
| 255 // This is also set in OnStopped(). | |
| 256 is_remoting_failed_ = true; | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 void RemotingController::ShouldCreateRemotingCdm(const CdmCheckCallback& cb) { | |
| 261 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 262 DCHECK(!cb.is_null()); | |
| 263 DCHECK(switch_renderer_cb_.is_null()); | |
| 264 | |
| 265 cdm_check_cb_ = cb; | |
| 266 if (!is_sink_available_) { | |
| 267 cdm_check_cb_.Run(false); | |
|
miu
2016/10/08 01:06:16
What if the sink becomes available later? Say, in
xjz
2016/10/20 21:25:28
Can we just consider simple case? If the sink is n
| |
| 268 is_remoting_cdm_ = false; | |
| 269 } else { | |
| 270 // Will run |cdm_check_cb_| in OnStarted() or OnStartFailed. | |
| 271 remoter_->Start(); | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 } // namespace media | |
| OLD | NEW |