Fix FullScreen crash in Webapp
[platform/framework/web/chromium-efl.git] / media / remoting / renderer_controller.cc
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.
4
5 #include "media/remoting/renderer_controller.h"
6
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"
16
17 #if BUILDFLAG(IS_ANDROID)
18 #include "media/base/android/media_codec_util.h"
19 #endif
20
21 namespace media {
22 namespace remoting {
23
24 using mojom::RemotingSinkAudioCapability;
25 using mojom::RemotingSinkFeature;
26 using mojom::RemotingSinkVideoCapability;
27
28 namespace {
29
30 constexpr int kPixelsPerSec4k = 3840 * 2160 * 30;  // 4k 30fps.
31 constexpr int kPixelsPerSec2k = 1920 * 1080 * 30;  // 1080p 30fps.
32
33 StopTrigger GetStopTrigger(mojom::RemotingStopReason reason) {
34   switch (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:
46       return SERVICE_GONE;
47     case mojom::RemotingStopReason::USER_DISABLED:
48       return 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;
57   }
58
59   return UNKNOWN_STOP_TRIGGER;  // To suppress compiler warning on Windows.
60 }
61
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:
71     case USER_DISABLED:
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:
81     case RPC_INVALID:
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:
92     case START_RACE:
93     case SERVICE_GONE:
94       return MediaObserverClient::ReasonToSwitchToLocal::ROUTE_TERMINATED;
95   }
96
97   // To suppress compiler warning on Windows.
98   return MediaObserverClient::ReasonToSwitchToLocal::ROUTE_TERMINATED;
99 }
100
101 }  // namespace
102
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));
109       }),
110 #else
111     :
112 #endif
113       receiver_(this, std::move(source_receiver)),
114       remoter_(std::move(remoter)),
115       clock_(base::DefaultTickClock::GetInstance()) {
116   DCHECK(remoter_);
117 }
118
119 RendererController::~RendererController() {
120   DCHECK(thread_checker_.CalledOnValidThread());
121   SetClient(nullptr);
122 }
123
124 void RendererController::OnSinkAvailable(
125     mojom::RemotingSinkMetadataPtr metadata) {
126   DCHECK(thread_checker_.CalledOnValidThread());
127   sink_metadata_ = std::move(metadata);
128
129   UpdateAndMaybeSwitch(SINK_AVAILABLE, UNKNOWN_STOP_TRIGGER);
130 }
131
132 void RendererController::OnSinkGone() {
133   DCHECK(thread_checker_.CalledOnValidThread());
134
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;
138 }
139
140 void RendererController::OnStarted() {
141   DCHECK(thread_checker_.CalledOnValidThread());
142
143   VLOG(1) << "Remoting started successively.";
144   if (remote_rendering_started_ && client_) {
145     metrics_recorder_.DidStartSession();
146     client_->SwitchToRemoteRenderer(sink_metadata_->friendly_name);
147   }
148 }
149
150 void RendererController::OnStartFailed(mojom::RemotingStartFailReason reason) {
151   DCHECK(thread_checker_.CalledOnValidThread());
152
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;
159   }
160 }
161
162 void RendererController::OnStopped(mojom::RemotingStopReason reason) {
163   DCHECK(thread_checker_.CalledOnValidThread());
164
165   VLOG(1) << "Remoting stopped: " << reason;
166   is_media_remoting_requested_ = false;
167   OnSinkGone();
168   UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, GetStopTrigger(reason));
169 }
170
171 void RendererController::OnMessageFromSink(
172     const std::vector<uint8_t>& message) {
173   DCHECK(thread_checker_.CalledOnValidThread());
174
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";
180     OnSinkGone();
181     UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, RPC_INVALID);
182     return;
183   }
184
185   rpc_messenger_.ProcessMessageFromRemote(std::move(rpc));
186 #endif
187 }
188
189 void RendererController::OnBecameDominantVisibleContent(bool is_dominant) {
190   DCHECK(thread_checker_.CalledOnValidThread());
191
192   if (is_dominant_content_ == is_dominant)
193     return;
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);
200 }
201
202 void RendererController::OnRemotePlaybackDisabled(bool disabled) {
203   DCHECK(thread_checker_.CalledOnValidThread());
204
205   is_remote_playback_disabled_ = disabled;
206   metrics_recorder_.OnRemotePlaybackDisabled(disabled);
207   UpdateAndMaybeSwitch(ENABLED_BY_PAGE, DISABLED_BY_PAGE);
208 }
209
210 void RendererController::OnMediaRemotingRequested() {
211   is_media_remoting_requested_ = true;
212   UpdateAndMaybeSwitch(REQUESTED_BY_BROWSER, UNKNOWN_STOP_TRIGGER);
213 }
214
215 #if BUILDFLAG(ENABLE_MEDIA_REMOTING_RPC)
216 openscreen::WeakPtr<openscreen::cast::RpcMessenger>
217 RendererController::GetRpcMessenger() {
218   DCHECK(thread_checker_.CalledOnValidThread());
219
220   return rpc_messenger_.GetWeakPtr();
221 }
222 #endif
223
224 void RendererController::StartDataPipe(uint32_t data_pipe_capacity,
225                                        bool audio,
226                                        bool video,
227                                        DataPipeStartCallback done_callback) {
228   DCHECK(thread_checker_.CalledOnValidThread());
229   DCHECK(!done_callback.is_null());
230
231   bool ok = audio || video;
232
233   mojo::ScopedDataPipeProducerHandle audio_producer_handle;
234   mojo::ScopedDataPipeConsumerHandle audio_consumer_handle;
235   if (ok && audio) {
236     ok &= mojo::CreateDataPipe(data_pipe_capacity, audio_producer_handle,
237                                audio_consumer_handle) == MOJO_RESULT_OK;
238   }
239
240   mojo::ScopedDataPipeProducerHandle video_producer_handle;
241   mojo::ScopedDataPipeConsumerHandle video_consumer_handle;
242   if (ok && video) {
243     ok &= mojo::CreateDataPipe(data_pipe_capacity, video_producer_handle,
244                                video_consumer_handle) == MOJO_RESULT_OK;
245   }
246
247   if (!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());
253     return;
254   }
255
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));
267 }
268
269 void RendererController::OnMetadataChanged(const PipelineMetadata& metadata) {
270   DCHECK(thread_checker_.CalledOnValidThread());
271
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);
280
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;
288   }
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;
296   }
297
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();
303   }
304
305   UpdateRemotePlaybackAvailabilityMonitoringState();
306
307   UpdateAndMaybeSwitch(start_trigger, stop_trigger);
308 }
309
310 void RendererController::OnDataSourceInitialized(
311     const GURL& url_after_redirects) {
312   DCHECK(thread_checker_.CalledOnValidThread());
313
314   if (url_after_redirects == url_after_redirects_)
315     return;
316
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;
320
321   UpdateRemotePlaybackAvailabilityMonitoringState();
322 }
323
324 void RendererController::OnHlsManifestDetected() {
325   is_hls_ = true;
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();
331 }
332
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();
343 #else
344   const bool is_media_supported =
345       !pipeline_metadata_.video_decoder_config.is_encrypted() &&
346       !pipeline_metadata_.audio_decoder_config.is_encrypted();
347 #endif
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")) &&
353                              is_media_supported;
354   if (client_)
355     client_->UpdateRemotePlaybackCompatibility(is_source_supported);
356 }
357
358 bool RendererController::IsVideoCodecSupported() const {
359   DCHECK(thread_checker_.CalledOnValidThread());
360   return GetVideoCompatibility() == RemotingCompatibility::kCompatible;
361 }
362
363 bool RendererController::IsAudioCodecSupported() const {
364   DCHECK(thread_checker_.CalledOnValidThread());
365   return GetAudioCompatibility() == RemotingCompatibility::kCompatible;
366 }
367
368 void RendererController::OnPlaying() {
369   DCHECK(thread_checker_.CalledOnValidThread());
370
371   is_paused_ = false;
372   UpdateAndMaybeSwitch(PLAY_COMMAND, UNKNOWN_STOP_TRIGGER);
373   MaybeStartCalculatePixelRateTimer();
374 }
375
376 void RendererController::OnPaused() {
377   DCHECK(thread_checker_.CalledOnValidThread());
378
379   is_paused_ = true;
380   // Cancel the timer since pixel rate cannot be calculated when media is
381   // paused.
382   pixel_rate_timer_.Stop();
383 }
384
385 void RendererController::OnFrozen() {
386   DCHECK(thread_checker_.CalledOnValidThread());
387   DCHECK(is_paused_);
388
389   // If the element is frozen we want to stop remoting.
390   UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, MEDIA_ELEMENT_FROZEN);
391 }
392
393 RemotingCompatibility RendererController::GetVideoCompatibility() const {
394   DCHECK(thread_checker_.CalledOnValidThread());
395   DCHECK(has_video());
396
397   // Media Remoting doesn't support encrypted media.
398   if (pipeline_metadata_.video_decoder_config.is_encrypted())
399     return RemotingCompatibility::kEncryptedVideo;
400
401   bool compatible = false;
402   switch (pipeline_metadata_.video_decoder_config.codec()) {
403     case VideoCodec::kH264:
404       compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_H264);
405       break;
406     case VideoCodec::kVP8:
407       compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_VP8);
408       break;
409     case VideoCodec::kVP9:
410       compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_VP9);
411       break;
412     case VideoCodec::kHEVC:
413       compatible = HasVideoCapability(RemotingSinkVideoCapability::CODEC_HEVC);
414       break;
415     default:
416       VLOG(2) << "Remoting does not support video codec: "
417               << pipeline_metadata_.video_decoder_config.codec();
418   }
419   return compatible ? RemotingCompatibility::kCompatible
420                     : RemotingCompatibility::kIncompatibleVideoCodec;
421 }
422
423 RemotingCompatibility RendererController::GetAudioCompatibility() const {
424   DCHECK(thread_checker_.CalledOnValidThread());
425   DCHECK(has_audio());
426
427   // Media Remoting doesn't support encrypted media.
428   if (pipeline_metadata_.audio_decoder_config.is_encrypted())
429     return RemotingCompatibility::kEncryptedAudio;
430
431   bool compatible = false;
432   switch (pipeline_metadata_.audio_decoder_config.codec()) {
433     case AudioCodec::kAAC:
434       compatible = HasAudioCapability(RemotingSinkAudioCapability::CODEC_AAC);
435       break;
436     case AudioCodec::kOpus:
437       compatible = HasAudioCapability(RemotingSinkAudioCapability::CODEC_OPUS);
438       break;
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:
456       compatible =
457           HasAudioCapability(RemotingSinkAudioCapability::CODEC_BASELINE_SET);
458       break;
459     default:
460       VLOG(2) << "Remoting does not support audio codec: "
461               << pipeline_metadata_.audio_decoder_config.codec();
462   }
463   return compatible ? RemotingCompatibility::kCompatible
464                     : RemotingCompatibility::kIncompatibleAudioCodec;
465 }
466
467 RemotingCompatibility RendererController::GetCompatibility() const {
468   DCHECK(thread_checker_.CalledOnValidThread());
469   DCHECK(client_);
470
471   if (is_remote_playback_disabled_)
472     return RemotingCompatibility::kDisabledByPage;
473
474   if (!has_video() && !has_audio())
475     return RemotingCompatibility::kNoAudioNorVideo;
476
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;
482   }
483
484   if (client_->Duration() <= kMinMediaDurationForSwitchingToRemotingInSec) {
485     return RemotingCompatibility::kDurationBelowThreshold;
486   }
487
488   if (has_video()) {
489     RemotingCompatibility compatibility = GetVideoCompatibility();
490     if (compatibility != RemotingCompatibility::kCompatible)
491       return compatibility;
492   }
493
494   if (has_audio()) {
495     RemotingCompatibility compatibility = GetAudioCompatibility();
496     if (compatibility != RemotingCompatibility::kCompatible)
497       return compatibility;
498   }
499   return RemotingCompatibility::kCompatible;
500 }
501
502 bool RendererController::IsAudioOrVideoSupported() const {
503   return ((has_audio() || has_video()) &&
504           (!has_video() || IsVideoCodecSupported()) &&
505           (!has_audio() || IsAudioCodecSupported()));
506 }
507
508 void RendererController::UpdateAndMaybeSwitch(StartTrigger start_trigger,
509                                               StopTrigger stop_trigger) {
510   DCHECK(thread_checker_.CalledOnValidThread());
511
512   bool should_be_remoting = ShouldBeRemoting();
513
514   if (should_be_remoting == remote_rendering_started_) {
515     return;
516   }
517
518   // Stop Remoting.
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);
524     if (client_)
525       client_->SwitchToLocalRenderer(GetSwitchReason(stop_trigger));
526     VLOG(2) << "Request to stop remoting: stop_trigger=" << stop_trigger;
527     remoter_->Stop(mojom::RemotingStopReason::LOCAL_PLAYBACK);
528     return;
529   }
530
531   // Start remoting. First, check pixel rate compatibility.
532   if (pixels_per_second_ == 0) {
533     MaybeStartCalculatePixelRateTimer();
534     return;
535   }
536
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();
548     } else {
549       remoter_->Start();
550     }
551   }
552 }
553
554 void RendererController::OnRendererFatalError(StopTrigger stop_trigger) {
555   DCHECK(thread_checker_.CalledOnValidThread());
556
557   // Do not act on errors caused by things like Mojo pipes being closed during
558   // shutdown.
559   if (!remote_rendering_started_)
560     return;
561
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;
567     OnSinkGone();
568   }
569
570   UpdateAndMaybeSwitch(UNKNOWN_START_TRIGGER, stop_trigger);
571 }
572
573 void RendererController::SetClient(MediaObserverClient* client) {
574   DCHECK(thread_checker_.CalledOnValidThread());
575
576   client_ = client;
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
579   // content.
580   encountered_renderer_fatal_error_ = false;
581   if (!client_) {
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;
587     }
588   }
589 }
590
591 bool RendererController::HasVideoCapability(
592     mojom::RemotingSinkVideoCapability capability) const {
593   return sink_metadata_ &&
594          base::Contains(sink_metadata_->video_capabilities, capability);
595 }
596
597 bool RendererController::HasAudioCapability(
598     mojom::RemotingSinkAudioCapability capability) const {
599   return sink_metadata_ &&
600          base::Contains(sink_metadata_->audio_capabilities, capability);
601 }
602
603 bool RendererController::HasFeatureCapability(
604     RemotingSinkFeature capability) const {
605   return sink_metadata_ && base::Contains(sink_metadata_->features, capability);
606 }
607
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();
613   }
614   return HasFeatureCapability(RemotingSinkFeature::RENDERING);
615 }
616
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_) {
621     return false;
622   }
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()) {
629     return false;
630   }
631
632   const RemotingCompatibility compatibility = GetCompatibility();
633   metrics_recorder_.RecordCompatibility(compatibility);
634   return compatibility == RemotingCompatibility::kCompatible;
635 }
636
637 void RendererController::MaybeStartCalculatePixelRateTimer() {
638   if (pixel_rate_timer_.IsRunning() || !client_ || is_paused_ ||
639       pixels_per_second_ != 0) {
640     return;
641   }
642
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()));
648 }
649
650 void RendererController::DoCalculatePixelRate(
651     int decoded_frame_count_before_delay,
652     base::TimeTicks delayed_start_time) {
653   DCHECK(client_);
654   DCHECK(!is_paused_);
655
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);
662 }
663
664 PixelRateSupport RendererController::GetPixelRateSupport() const {
665   DCHECK(pixels_per_second_ != 0);
666
667   if (pixels_per_second_ <= kPixelsPerSec2k) {
668     return PixelRateSupport::k2kSupported;
669   }
670   if (pixels_per_second_ <= kPixelsPerSec4k) {
671     if (HasVideoCapability(mojom::RemotingSinkVideoCapability::SUPPORT_4K)) {
672       return PixelRateSupport::k4kSupported;
673     } else {
674       return PixelRateSupport::k4kNotSupported;
675     }
676   }
677   return PixelRateSupport::kOver4kNotSupported;
678 }
679
680 void RendererController::SendMessageToSink(std::vector<uint8_t> message) {
681   DCHECK(thread_checker_.CalledOnValidThread());
682   remoter_->SendMessageToSink(message);
683 }
684
685 #if BUILDFLAG(IS_ANDROID)
686
687 bool RendererController::IsAudioRemotePlaybackSupported() const {
688   DCHECK(thread_checker_.CalledOnValidThread());
689   DCHECK(has_audio());
690
691   if (pipeline_metadata_.audio_decoder_config.is_encrypted())
692     return false;
693
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:
714       return true;
715     default:
716       return false;
717   }
718 }
719
720 bool RendererController::IsVideoRemotePlaybackSupported() const {
721   DCHECK(thread_checker_.CalledOnValidThread());
722   DCHECK(has_video());
723
724   if (pipeline_metadata_.video_decoder_config.is_encrypted())
725     return false;
726
727   switch (pipeline_metadata_.video_decoder_config.codec()) {
728     case VideoCodec::kH264:
729     case VideoCodec::kVP8:
730     case VideoCodec::kVP9:
731     case VideoCodec::kHEVC:
732       return true;
733     default:
734       return false;
735   }
736 }
737
738 bool RendererController::IsRemotePlaybackSupported() const {
739   return ((has_audio() || has_video()) &&
740           (!has_video() || IsVideoRemotePlaybackSupported()) &&
741           (!has_audio() || IsAudioRemotePlaybackSupported()));
742 }
743
744 #endif  // BUILDFLAG(IS_ANDROID)
745
746 }  // namespace remoting
747 }  // namespace media