1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/remoting/renderer_controller.h"
7 #include "base/containers/contains.h"
8 #include "base/functional/bind.h"
9 #include "base/logging.h"
10 #include "base/time/default_tick_clock.h"
11 #include "base/time/tick_clock.h"
12 #include "base/time/time.h"
13 #include "build/build_config.h"
14 #include "media/remoting/metrics.h"
15 #include "media/remoting/remoting_constants.h"
17 #if BUILDFLAG(IS_ANDROID)
18 #include "media/base/android/media_codec_util.h"
24 using mojom::RemotingSinkAudioCapability;
25 using mojom::RemotingSinkFeature;
26 using mojom::RemotingSinkVideoCapability;
30 constexpr int kPixelsPerSec4k = 3840 * 2160 * 30; // 4k 30fps.
31 constexpr int kPixelsPerSec2k = 1920 * 1080 * 30; // 1080p 30fps.
33 StopTrigger GetStopTrigger(mojom::RemotingStopReason reason) {
35 case mojom::RemotingStopReason::ROUTE_TERMINATED:
36 return ROUTE_TERMINATED;
37 case mojom::RemotingStopReason::SOURCE_GONE:
38 return MEDIA_ELEMENT_DESTROYED;
39 case mojom::RemotingStopReason::MESSAGE_SEND_FAILED:
40 return MESSAGE_SEND_FAILED;
41 case mojom::RemotingStopReason::DATA_SEND_FAILED:
42 return DATA_SEND_FAILED;
43 case mojom::RemotingStopReason::UNEXPECTED_FAILURE:
44 return UNEXPECTED_FAILURE;
45 case mojom::RemotingStopReason::SERVICE_GONE:
47 case mojom::RemotingStopReason::USER_DISABLED:
49 case mojom::RemotingStopReason::LOCAL_PLAYBACK:
50 // This RemotingStopReason indicates the RendererController initiated the
51 // session shutdown in the immediate past, and the trigger for that should
52 // have already been recorded in the metrics. Here, this is just duplicate
53 // feedback from the sink for that same event. Return UNKNOWN_STOP_TRIGGER
54 // because this reason can not be a stop trigger and it would be a logic
55 // flaw for this value to be recorded in the metrics.
56 return UNKNOWN_STOP_TRIGGER;
59 return UNKNOWN_STOP_TRIGGER; // To suppress compiler warning on Windows.
62 MediaObserverClient::ReasonToSwitchToLocal GetSwitchReason(
63 StopTrigger stop_trigger) {
64 switch (stop_trigger) {
65 case FRAME_DROP_RATE_HIGH:
66 case PACING_TOO_SLOWLY:
67 return MediaObserverClient::ReasonToSwitchToLocal::POOR_PLAYBACK_QUALITY;
68 case EXITED_FULLSCREEN:
69 case BECAME_AUXILIARY_CONTENT:
70 case DISABLED_BY_PAGE:
72 case UNKNOWN_STOP_TRIGGER:
73 return MediaObserverClient::ReasonToSwitchToLocal::NORMAL;
74 case UNSUPPORTED_AUDIO_CODEC:
75 case UNSUPPORTED_VIDEO_CODEC:
76 case UNSUPPORTED_AUDIO_AND_VIDEO_CODECS:
77 case DECRYPTION_ERROR:
78 case RECEIVER_INITIALIZE_FAILED:
79 case RECEIVER_PIPELINE_ERROR:
80 case PEERS_OUT_OF_SYNC:
82 case DATA_PIPE_CREATE_ERROR:
83 case DATA_PIPE_WRITE_ERROR:
84 case MESSAGE_SEND_FAILED:
85 case DATA_SEND_FAILED:
86 case UNEXPECTED_FAILURE:
87 return MediaObserverClient::ReasonToSwitchToLocal::PIPELINE_ERROR;
88 case MOJO_DISCONNECTED:
89 case ROUTE_TERMINATED:
90 case MEDIA_ELEMENT_DESTROYED:
91 case MEDIA_ELEMENT_FROZEN:
94 return MediaObserverClient::ReasonToSwitchToLocal::ROUTE_TERMINATED;
97 // To suppress compiler warning on Windows.
98 return MediaObserverClient::ReasonToSwitchToLocal::ROUTE_TERMINATED;
103 RendererController::RendererController(
104 mojo::PendingReceiver<mojom::RemotingSource> source_receiver,
105 mojo::PendingRemote<mojom::Remoter> remoter)
106 #if BUILDFLAG(ENABLE_MEDIA_REMOTING_RPC)
107 : rpc_messenger_([this](std::vector<uint8_t> message) {
108 SendMessageToSink(std::move(message));
113 receiver_(this, std::move(source_receiver)),
114 remoter_(std::move(remoter)),
115 clock_(base::DefaultTickClock::GetInstance()) {
119 RendererController::~RendererController() {
120 DCHECK(thread_checker_.CalledOnValidThread());
124 void RendererController::OnSinkAvailable(
125 mojom::RemotingSinkMetadataPtr metadata) {
126 DCHECK(thread_checker_.CalledOnValidThread());
127 sink_metadata_ = std::move(metadata);
129 UpdateAndMaybeSwitch(SINK_AVAILABLE, UNKNOWN_STOP_TRIGGER);
132 void RendererController::OnSinkGone() {
133 DCHECK(thread_checker_.CalledOnValidThread());
135 // Prevent the clients to start any future remoting sessions. Won't affect the
136 // behavior of the currently-running session (if any).
137 sink_metadata_ = nullptr;
140 void RendererController::OnStarted() {
141 DCHECK(thread_checker_.CalledOnValidThread());
143 VLOG(1) << "Remoting started successively.";
144 if (remote_rendering_started_ && client_) {
145 metrics_recorder_.DidStartSession();
146 client_->SwitchToRemoteRenderer(sink_metadata_->friendly_name);
150 void RendererController::OnStartFailed(mojom::RemotingStartFailReason reason) {
151 DCHECK(thread_checker_.CalledOnValidThread());
153 VLOG(1) << "Failed to start remoting:" << reason;
154 is_media_remoting_requested_ = false;
155 if (remote_rendering_started_) {
156 metrics_recorder_.WillStopSession(START_RACE);
157 metrics_recorder_.StartSessionFailed(reason);
158 remote_rendering_started_ = false;
162 void RendererController::OnStopped(mojom::RemotingStopReason reason) {
163 DCHECK(thread_checker_.CalledOnValidThread());
165 VLOG(1) << "Remoting stopped: " << reason;
166 is_media_remoting_requested_ = false;
168 UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, GetStopTrigger(reason));
171 void RendererController::OnMessageFromSink(
172 const std::vector<uint8_t>& message) {
173 DCHECK(thread_checker_.CalledOnValidThread());
175 #if BUILDFLAG(ENABLE_MEDIA_REMOTING_RPC)
176 std::unique_ptr<openscreen::cast::RpcMessage> rpc(
177 new openscreen::cast::RpcMessage());
178 if (!rpc->ParseFromArray(message.data(), message.size())) {
179 VLOG(1) << "corrupted Rpc message";
181 UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, RPC_INVALID);
185 rpc_messenger_.ProcessMessageFromRemote(std::move(rpc));
189 void RendererController::OnBecameDominantVisibleContent(bool is_dominant) {
190 DCHECK(thread_checker_.CalledOnValidThread());
192 if (is_dominant_content_ == is_dominant)
194 is_dominant_content_ = is_dominant;
195 // Reset the errors when the media element stops being the dominant visible
196 // content in the tab.
197 if (!is_dominant_content_)
198 encountered_renderer_fatal_error_ = false;
199 UpdateAndMaybeSwitch(BECAME_DOMINANT_CONTENT, BECAME_AUXILIARY_CONTENT);
202 void RendererController::OnRemotePlaybackDisabled(bool disabled) {
203 DCHECK(thread_checker_.CalledOnValidThread());
205 is_remote_playback_disabled_ = disabled;
206 metrics_recorder_.OnRemotePlaybackDisabled(disabled);
207 UpdateAndMaybeSwitch(ENABLED_BY_PAGE, DISABLED_BY_PAGE);
210 void RendererController::OnMediaRemotingRequested() {
211 is_media_remoting_requested_ = true;
212 UpdateAndMaybeSwitch(REQUESTED_BY_BROWSER, UNKNOWN_STOP_TRIGGER);
215 #if BUILDFLAG(ENABLE_MEDIA_REMOTING_RPC)
216 openscreen::WeakPtr<openscreen::cast::RpcMessenger>
217 RendererController::GetRpcMessenger() {
218 DCHECK(thread_checker_.CalledOnValidThread());
220 return rpc_messenger_.GetWeakPtr();
224 void RendererController::StartDataPipe(uint32_t data_pipe_capacity,
227 DataPipeStartCallback done_callback) {
228 DCHECK(thread_checker_.CalledOnValidThread());
229 DCHECK(!done_callback.is_null());
231 bool ok = audio || video;
233 mojo::ScopedDataPipeProducerHandle audio_producer_handle;
234 mojo::ScopedDataPipeConsumerHandle audio_consumer_handle;
236 ok &= mojo::CreateDataPipe(data_pipe_capacity, audio_producer_handle,
237 audio_consumer_handle) == MOJO_RESULT_OK;
240 mojo::ScopedDataPipeProducerHandle video_producer_handle;
241 mojo::ScopedDataPipeConsumerHandle video_consumer_handle;
243 ok &= mojo::CreateDataPipe(data_pipe_capacity, video_producer_handle,
244 video_consumer_handle) == MOJO_RESULT_OK;
248 VLOG(1) << "No audio nor video to establish data pipe";
249 std::move(done_callback)
250 .Run(mojo::NullRemote(), mojo::NullRemote(),
251 mojo::ScopedDataPipeProducerHandle(),
252 mojo::ScopedDataPipeProducerHandle());
256 mojo::PendingRemote<mojom::RemotingDataStreamSender> audio_stream_sender;
257 mojo::PendingRemote<mojom::RemotingDataStreamSender> video_stream_sender;
258 remoter_->StartDataStreams(
259 std::move(audio_consumer_handle), std::move(video_consumer_handle),
260 audio ? audio_stream_sender.InitWithNewPipeAndPassReceiver()
261 : mojo::NullReceiver(),
262 video ? video_stream_sender.InitWithNewPipeAndPassReceiver()
263 : mojo::NullReceiver());
264 std::move(done_callback)
265 .Run(std::move(audio_stream_sender), std::move(video_stream_sender),
266 std::move(audio_producer_handle), std::move(video_producer_handle));
269 void RendererController::OnMetadataChanged(const PipelineMetadata& metadata) {
270 DCHECK(thread_checker_.CalledOnValidThread());
272 const bool was_audio_codec_supported = has_audio() && IsAudioCodecSupported();
273 const bool was_video_codec_supported = has_video() && IsVideoCodecSupported();
274 const bool natural_size_changed =
275 pipeline_metadata_.natural_size != metadata.natural_size;
276 pipeline_metadata_ = metadata;
277 const bool is_audio_codec_supported = has_audio() && IsAudioCodecSupported();
278 const bool is_video_codec_supported = has_video() && IsVideoCodecSupported();
279 metrics_recorder_.OnPipelineMetadataChanged(metadata);
281 StartTrigger start_trigger = UNKNOWN_START_TRIGGER;
282 if (!was_audio_codec_supported && is_audio_codec_supported)
283 start_trigger = SUPPORTED_AUDIO_CODEC;
284 if (!was_video_codec_supported && is_video_codec_supported) {
285 start_trigger = start_trigger == SUPPORTED_AUDIO_CODEC
286 ? SUPPORTED_AUDIO_AND_VIDEO_CODECS
287 : SUPPORTED_VIDEO_CODEC;
289 StopTrigger stop_trigger = UNKNOWN_STOP_TRIGGER;
290 if (was_audio_codec_supported && !is_audio_codec_supported)
291 stop_trigger = UNSUPPORTED_AUDIO_CODEC;
292 if (was_video_codec_supported && !is_video_codec_supported) {
293 stop_trigger = stop_trigger == UNSUPPORTED_AUDIO_CODEC
294 ? UNSUPPORTED_AUDIO_AND_VIDEO_CODECS
295 : UNSUPPORTED_VIDEO_CODEC;
298 // Reset and calculate pixel rate again when video size changes.
299 if (natural_size_changed) {
300 pixel_rate_timer_.Stop();
301 pixels_per_second_ = 0;
302 MaybeStartCalculatePixelRateTimer();
305 UpdateRemotePlaybackAvailabilityMonitoringState();
307 UpdateAndMaybeSwitch(start_trigger, stop_trigger);
310 void RendererController::OnDataSourceInitialized(
311 const GURL& url_after_redirects) {
312 DCHECK(thread_checker_.CalledOnValidThread());
314 if (url_after_redirects == url_after_redirects_)
317 // TODO(avayvod): Does WMPI update MediaObserver when metadata becomes
318 // invalid or should we reset it here?
319 url_after_redirects_ = url_after_redirects;
321 UpdateRemotePlaybackAvailabilityMonitoringState();
324 void RendererController::OnHlsManifestDetected() {
326 // TODO(crbug.com/1266991) Android used to rely solely on MediaPlayer for HLS
327 // playback, but now there is an alternative native player. Should we still
328 // be doing this in all cases? It does work in its current state, on both
329 // android and desktop, but it is not thoroughly tested.
330 UpdateRemotePlaybackAvailabilityMonitoringState();
333 void RendererController::UpdateRemotePlaybackAvailabilityMonitoringState() {
334 // Currently RemotePlayback-initated media remoting only supports URL flinging
335 // thus the source is supported when the URL is either http or https, video and
336 // audio codecs are supported by the remote playback device; HLS is playable by
337 // Chrome on Android (which is not detected by the pipeline metadata atm).
338 // On Desktop, `sink_metadata_` is empty until a streaming session has been
339 // established. So it's not possible to check if the receiver device supports
340 // the media's codec.
341 #if BUILDFLAG(IS_ANDROID)
342 const bool is_media_supported = is_hls_ || IsRemotePlaybackSupported();
344 const bool is_media_supported =
345 !pipeline_metadata_.video_decoder_config.is_encrypted() &&
346 !pipeline_metadata_.audio_decoder_config.is_encrypted();
348 // TODO(avayvod): add a check for CORS.
349 bool is_source_supported = url_after_redirects_.has_scheme() &&
350 (url_after_redirects_.SchemeIs("http") ||
351 url_after_redirects_.SchemeIs("https") ||
352 url_after_redirects_.SchemeIs("file")) &&
355 client_->UpdateRemotePlaybackCompatibility(is_source_supported);
358 bool RendererController::IsVideoCodecSupported() const {
359 DCHECK(thread_checker_.CalledOnValidThread());
360 return GetVideoCompatibility() == RemotingCompatibility::kCompatible;
363 bool RendererController::IsAudioCodecSupported() const {
364 DCHECK(thread_checker_.CalledOnValidThread());
365 return GetAudioCompatibility() == RemotingCompatibility::kCompatible;
368 void RendererController::OnPlaying() {
369 DCHECK(thread_checker_.CalledOnValidThread());
372 UpdateAndMaybeSwitch(PLAY_COMMAND, UNKNOWN_STOP_TRIGGER);
373 MaybeStartCalculatePixelRateTimer();
376 void RendererController::OnPaused() {
377 DCHECK(thread_checker_.CalledOnValidThread());
380 // Cancel the timer since pixel rate cannot be calculated when media is
382 pixel_rate_timer_.Stop();
385 void RendererController::OnFrozen() {
386 DCHECK(thread_checker_.CalledOnValidThread());
389 // If the element is frozen we want to stop remoting.
390 UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, MEDIA_ELEMENT_FROZEN);
393 RemotingCompatibility RendererController::GetVideoCompatibility() const {
394 DCHECK(thread_checker_.CalledOnValidThread());
397 // Media Remoting doesn't support encrypted media.
398 if (pipeline_metadata_.video_decoder_config.is_encrypted())
399 return RemotingCompatibility::kEncryptedVideo;
401 bool compatible = false;
402 switch (pipeline_metadata_.video_decoder_config.codec()) {
403 case VideoCodec::kH264:
404 compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_H264);
406 case VideoCodec::kVP8:
407 compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_VP8);
409 case VideoCodec::kVP9:
410 compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_VP9);
412 case VideoCodec::kHEVC:
413 compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_HEVC);
416 VLOG(2) << "Remoting does not support video codec: "
417 << pipeline_metadata_.video_decoder_config.codec();
419 return compatible ? RemotingCompatibility::kCompatible
420 : RemotingCompatibility::kIncompatibleVideoCodec;
423 RemotingCompatibility RendererController::GetAudioCompatibility() const {
424 DCHECK(thread_checker_.CalledOnValidThread());
427 // Media Remoting doesn't support encrypted media.
428 if (pipeline_metadata_.audio_decoder_config.is_encrypted())
429 return RemotingCompatibility::kEncryptedAudio;
431 bool compatible = false;
432 switch (pipeline_metadata_.audio_decoder_config.codec()) {
433 case AudioCodec::kAAC:
434 compatible = HasAudioCapability(RemotingSinkAudioCapability::CODEC_AAC);
436 case AudioCodec::kOpus:
437 compatible = HasAudioCapability(RemotingSinkAudioCapability::CODEC_OPUS);
439 case AudioCodec::kMP3:
440 case AudioCodec::kPCM:
441 case AudioCodec::kVorbis:
442 case AudioCodec::kFLAC:
443 case AudioCodec::kAMR_NB:
444 case AudioCodec::kAMR_WB:
445 case AudioCodec::kPCM_MULAW:
446 case AudioCodec::kGSM_MS:
447 case AudioCodec::kPCM_S16BE:
448 case AudioCodec::kPCM_S24BE:
449 case AudioCodec::kEAC3:
450 case AudioCodec::kPCM_ALAW:
451 case AudioCodec::kALAC:
452 case AudioCodec::kAC3:
453 case AudioCodec::kDTS:
454 case AudioCodec::kDTSXP2:
455 case AudioCodec::kDTSE:
457 HasAudioCapability(RemotingSinkAudioCapability::CODEC_BASELINE_SET);
460 VLOG(2) << "Remoting does not support audio codec: "
461 << pipeline_metadata_.audio_decoder_config.codec();
463 return compatible ? RemotingCompatibility::kCompatible
464 : RemotingCompatibility::kIncompatibleAudioCodec;
467 RemotingCompatibility RendererController::GetCompatibility() const {
468 DCHECK(thread_checker_.CalledOnValidThread());
471 if (is_remote_playback_disabled_)
472 return RemotingCompatibility::kDisabledByPage;
474 if (!has_video() && !has_audio())
475 return RemotingCompatibility::kNoAudioNorVideo;
477 // When `is_media_remoting_requested_`, it is guaranteed that the sink is
478 // compatible and the media element meets the minimum duration requirement. So
479 // there's no need to check for compatibilities.
480 if (is_media_remoting_requested_) {
481 return RemotingCompatibility::kCompatible;
484 if (client_->Duration() <= kMinMediaDurationForSwitchingToRemotingInSec) {
485 return RemotingCompatibility::kDurationBelowThreshold;
489 RemotingCompatibility compatibility = GetVideoCompatibility();
490 if (compatibility != RemotingCompatibility::kCompatible)
491 return compatibility;
495 RemotingCompatibility compatibility = GetAudioCompatibility();
496 if (compatibility != RemotingCompatibility::kCompatible)
497 return compatibility;
499 return RemotingCompatibility::kCompatible;
502 bool RendererController::IsAudioOrVideoSupported() const {
503 return ((has_audio() || has_video()) &&
504 (!has_video() || IsVideoCodecSupported()) &&
505 (!has_audio() || IsAudioCodecSupported()));
508 void RendererController::UpdateAndMaybeSwitch(StartTrigger start_trigger,
509 StopTrigger stop_trigger) {
510 DCHECK(thread_checker_.CalledOnValidThread());
512 bool should_be_remoting = ShouldBeRemoting();
514 if (should_be_remoting == remote_rendering_started_) {
519 if (!should_be_remoting) {
520 remote_rendering_started_ = false;
521 is_media_remoting_requested_ = false;
522 DCHECK_NE(UNKNOWN_STOP_TRIGGER, stop_trigger);
523 metrics_recorder_.WillStopSession(stop_trigger);
525 client_->SwitchToLocalRenderer(GetSwitchReason(stop_trigger));
526 VLOG(2) << "Request to stop remoting: stop_trigger=" << stop_trigger;
527 remoter_->Stop(mojom::RemotingStopReason::LOCAL_PLAYBACK);
531 // Start remoting. First, check pixel rate compatibility.
532 if (pixels_per_second_ == 0) {
533 MaybeStartCalculatePixelRateTimer();
537 auto pixel_rate_support = GetPixelRateSupport();
538 metrics_recorder_.RecordVideoPixelRateSupport(pixel_rate_support);
539 if (pixel_rate_support == PixelRateSupport::k2kSupported ||
540 pixel_rate_support == PixelRateSupport::k4kSupported) {
541 remote_rendering_started_ = true;
542 DCHECK_NE(UNKNOWN_START_TRIGGER, start_trigger);
543 metrics_recorder_.WillStartSession(start_trigger);
544 // |MediaObserverClient::SwitchToRemoteRenderer()| will be called after
545 // remoting is started successfully.
546 if (is_media_remoting_requested_) {
547 remoter_->StartWithPermissionAlreadyGranted();
554 void RendererController::OnRendererFatalError(StopTrigger stop_trigger) {
555 DCHECK(thread_checker_.CalledOnValidThread());
557 // Do not act on errors caused by things like Mojo pipes being closed during
559 if (!remote_rendering_started_)
562 // MOJO_DISCONNECTED means the streaming session has stopped, which is not a
563 // fatal error and should not prevent future sessions.
564 // Clean sinks so that `UpdateAndMaybeSwitch` will stop remoting.
565 if (stop_trigger != StopTrigger::MOJO_DISCONNECTED) {
566 encountered_renderer_fatal_error_ = true;
570 UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, stop_trigger);
573 void RendererController::SetClient(MediaObserverClient* client) {
574 DCHECK(thread_checker_.CalledOnValidThread());
577 // Reset `encountered_renderer_fatal_error_` when the media element changes so
578 // that the previous renderer fatal error won't prevent Remoting the new
580 encountered_renderer_fatal_error_ = false;
582 pixel_rate_timer_.Stop();
583 if (remote_rendering_started_) {
584 metrics_recorder_.WillStopSession(MEDIA_ELEMENT_DESTROYED);
585 remoter_->Stop(mojom::RemotingStopReason::UNEXPECTED_FAILURE);
586 remote_rendering_started_ = false;
591 bool RendererController::HasVideoCapability(
592 mojom::RemotingSinkVideoCapability capability) const {
593 return sink_metadata_ &&
594 base::Contains(sink_metadata_->video_capabilities, capability);
597 bool RendererController::HasAudioCapability(
598 mojom::RemotingSinkAudioCapability capability) const {
599 return sink_metadata_ &&
600 base::Contains(sink_metadata_->audio_capabilities, capability);
603 bool RendererController::HasFeatureCapability(
604 RemotingSinkFeature capability) const {
605 return sink_metadata_ && base::Contains(sink_metadata_->features, capability);
608 bool RendererController::SinkSupportsRemoting() const {
609 // when `is_media_remoting_requested_` is true, all discovered sinks are
610 // compatible with this media content.
611 if (is_media_remoting_requested_) {
612 return !sink_metadata_.is_null();
614 return HasFeatureCapability(RemotingSinkFeature::RENDERING);
617 bool RendererController::ShouldBeRemoting() {
618 // Starts remote rendering when the media is the dominant content or the
619 // browser has sent an explicit request.
620 if (!is_dominant_content_ && !is_media_remoting_requested_) {
623 // Only switch to remoting when media is playing. Since the renderer is
624 // created when video starts loading, the receiver would display a black
625 // screen if switching to remoting while paused. Thus, the user experience is
626 // improved by not starting remoting until playback resumes.
627 if (is_paused_ || encountered_renderer_fatal_error_ || !client_ ||
628 !SinkSupportsRemoting()) {
632 const RemotingCompatibility compatibility = GetCompatibility();
633 metrics_recorder_.RecordCompatibility(compatibility);
634 return compatibility == RemotingCompatibility::kCompatible;
637 void RendererController::MaybeStartCalculatePixelRateTimer() {
638 if (pixel_rate_timer_.IsRunning() || !client_ || is_paused_ ||
639 pixels_per_second_ != 0) {
643 pixel_rate_timer_.Start(
644 FROM_HERE, base::Seconds(kPixelRateCalInSec),
645 base::BindOnce(&RendererController::DoCalculatePixelRate,
646 base::Unretained(this), client_->DecodedFrameCount(),
647 clock_->NowTicks()));
650 void RendererController::DoCalculatePixelRate(
651 int decoded_frame_count_before_delay,
652 base::TimeTicks delayed_start_time) {
656 base::TimeDelta elapsed = clock_->NowTicks() - delayed_start_time;
657 const double frame_rate =
658 (client_->DecodedFrameCount() - decoded_frame_count_before_delay) /
659 elapsed.InSecondsF();
660 pixels_per_second_ = frame_rate * pipeline_metadata_.natural_size.GetArea();
661 UpdateAndMaybeSwitch(PIXEL_RATE_READY, UNKNOWN_STOP_TRIGGER);
664 PixelRateSupport RendererController::GetPixelRateSupport() const {
665 DCHECK(pixels_per_second_ != 0);
667 if (pixels_per_second_ <= kPixelsPerSec2k) {
668 return PixelRateSupport::k2kSupported;
670 if (pixels_per_second_ <= kPixelsPerSec4k) {
671 if (HasVideoCapability(mojom::RemotingSinkVideoCapability::SUPPORT_4K)) {
672 return PixelRateSupport::k4kSupported;
674 return PixelRateSupport::k4kNotSupported;
677 return PixelRateSupport::kOver4kNotSupported;
680 void RendererController::SendMessageToSink(std::vector<uint8_t> message) {
681 DCHECK(thread_checker_.CalledOnValidThread());
682 remoter_->SendMessageToSink(message);
685 #if BUILDFLAG(IS_ANDROID)
687 bool RendererController::IsAudioRemotePlaybackSupported() const {
688 DCHECK(thread_checker_.CalledOnValidThread());
691 if (pipeline_metadata_.audio_decoder_config.is_encrypted())
694 switch (pipeline_metadata_.audio_decoder_config.codec()) {
695 case AudioCodec::kAAC:
696 case AudioCodec::kOpus:
697 case AudioCodec::kMP3:
698 case AudioCodec::kPCM:
699 case AudioCodec::kVorbis:
700 case AudioCodec::kFLAC:
701 case AudioCodec::kAMR_NB:
702 case AudioCodec::kAMR_WB:
703 case AudioCodec::kPCM_MULAW:
704 case AudioCodec::kGSM_MS:
705 case AudioCodec::kPCM_S16BE:
706 case AudioCodec::kPCM_S24BE:
707 case AudioCodec::kEAC3:
708 case AudioCodec::kPCM_ALAW:
709 case AudioCodec::kALAC:
710 case AudioCodec::kAC3:
711 case AudioCodec::kDTS:
712 case AudioCodec::kDTSXP2:
713 case AudioCodec::kDTSE:
720 bool RendererController::IsVideoRemotePlaybackSupported() const {
721 DCHECK(thread_checker_.CalledOnValidThread());
724 if (pipeline_metadata_.video_decoder_config.is_encrypted())
727 switch (pipeline_metadata_.video_decoder_config.codec()) {
728 case VideoCodec::kH264:
729 case VideoCodec::kVP8:
730 case VideoCodec::kVP9:
731 case VideoCodec::kHEVC:
738 bool RendererController::IsRemotePlaybackSupported() const {
739 return ((has_audio() || has_video()) &&
740 (!has_video() || IsVideoRemotePlaybackSupported()) &&
741 (!has_audio() || IsAudioRemotePlaybackSupported()));
744 #endif // BUILDFLAG(IS_ANDROID)
746 } // namespace remoting