| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/remoteplayback/remote_playback.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/strcat.h" |
| #include "third_party/blink/public/platform/modules/remoteplayback/remote_playback_source.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_remote_playback_availability_callback.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/html/media/html_media_element.h" |
| #include "third_party/blink/renderer/core/html/media/html_video_element.h" |
| #include "third_party/blink/renderer/core/html/media/remote_playback_observer.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/probe/async_task_context.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/modules/event_target_modules.h" |
| #include "third_party/blink/renderer/modules/presentation/presentation_availability_state.h" |
| #include "third_party/blink/renderer/modules/presentation/presentation_controller.h" |
| #include "third_party/blink/renderer/modules/remoteplayback/availability_callback_wrapper.h" |
| #include "third_party/blink/renderer/modules/remoteplayback/remote_playback_metrics.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/instrumentation/memory_pressure_listener.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/base64.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| const AtomicString& RemotePlaybackStateToString( |
| mojom::blink::PresentationConnectionState state) { |
| DEFINE_STATIC_LOCAL(const AtomicString, connecting_value, ("connecting")); |
| DEFINE_STATIC_LOCAL(const AtomicString, connected_value, ("connected")); |
| DEFINE_STATIC_LOCAL(const AtomicString, disconnected_value, ("disconnected")); |
| |
| switch (state) { |
| case mojom::blink::PresentationConnectionState::CONNECTING: |
| return connecting_value; |
| case mojom::blink::PresentationConnectionState::CONNECTED: |
| return connected_value; |
| case mojom::blink::PresentationConnectionState::CLOSED: |
| case mojom::blink::PresentationConnectionState::TERMINATED: |
| return disconnected_value; |
| default: |
| NOTREACHED(); |
| return disconnected_value; |
| } |
| } |
| |
| void RunRemotePlaybackTask( |
| ExecutionContext* context, |
| base::OnceClosure task, |
| std::unique_ptr<probe::AsyncTaskContext> task_context) { |
| probe::AsyncTask async_task(context, task_context.get()); |
| std::move(task).Run(); |
| } |
| |
| KURL GetAvailabilityUrl(const WebURL& source, bool is_source_supported) { |
| if (source.IsEmpty() || !source.IsValid() || !is_source_supported) |
| return KURL(); |
| |
| // The URL for each media element's source looks like the following: |
| // remote-playback://<encoded-data> where |encoded-data| is base64 URL |
| // encoded string representation of the source URL. |
| std::string source_string = source.GetString().Utf8(); |
| String encoded_source = WTF::Base64URLEncode( |
| source_string.data(), |
| base::checked_cast<unsigned>(source_string.length())); |
| |
| return KURL( |
| base::StrCat({kRemotePlaybackPresentationUrlScheme, "://"}).c_str() + |
| encoded_source); |
| } |
| |
| bool IsBackgroundAvailabilityMonitoringDisabled() { |
| return MemoryPressureListenerRegistry::IsLowEndDevice(); |
| } |
| |
| void RemotingStarting(HTMLMediaElement& media_element) { |
| if (auto* video_element = DynamicTo<HTMLVideoElement>(&media_element)) { |
| // TODO(xjz): Pass the remote device name. |
| video_element->MediaRemotingStarted(WebString()); |
| } |
| media_element.FlingingStarted(); |
| } |
| |
| } // anonymous namespace |
| |
| // static |
| RemotePlayback& RemotePlayback::From(HTMLMediaElement& element) { |
| RemotePlayback* self = |
| static_cast<RemotePlayback*>(RemotePlaybackController::From(element)); |
| if (!self) { |
| self = MakeGarbageCollected<RemotePlayback>(element); |
| RemotePlaybackController::ProvideTo(element, self); |
| } |
| return *self; |
| } |
| |
| RemotePlayback::RemotePlayback(HTMLMediaElement& element) |
| : ExecutionContextLifecycleObserver(element.GetExecutionContext()), |
| ActiveScriptWrappable<RemotePlayback>({}), |
| RemotePlaybackController(element), |
| state_(mojom::blink::PresentationConnectionState::CLOSED), |
| availability_(mojom::ScreenAvailability::UNKNOWN), |
| media_element_(&element), |
| is_listening_(false), |
| presentation_connection_receiver_(this, element.GetExecutionContext()), |
| target_presentation_connection_(element.GetExecutionContext()) {} |
| |
| const AtomicString& RemotePlayback::InterfaceName() const { |
| return event_target_names::kRemotePlayback; |
| } |
| |
| ExecutionContext* RemotePlayback::GetExecutionContext() const { |
| return ExecutionContextLifecycleObserver::GetExecutionContext(); |
| } |
| |
| ScriptPromise RemotePlayback::watchAvailability( |
| ScriptState* script_state, |
| V8RemotePlaybackAvailabilityCallback* callback, |
| ExceptionState& exception_state) { |
| if (media_element_->FastHasAttribute( |
| html_names::kDisableremoteplaybackAttr)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "disableRemotePlayback attribute is present."); |
| return ScriptPromise(); |
| } |
| |
| int id = WatchAvailabilityInternal( |
| MakeGarbageCollected<AvailabilityCallbackWrapper>(callback)); |
| if (id == kWatchAvailabilityNotSupported) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "Availability monitoring is not supported on this device."); |
| return ScriptPromise(); |
| } |
| |
| // TODO(avayvod): Currently the availability is tracked for each media element |
| // as soon as it's created, we probably want to limit that to when the |
| // page/element is visible (see https://crbug.com/597281) and has default |
| // controls. If there are no default controls, we should also start tracking |
| // availability on demand meaning the Promise returned by watchAvailability() |
| // will be resolved asynchronously. |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| resolver->Resolve(id); |
| return promise; |
| } |
| |
| ScriptPromise RemotePlayback::cancelWatchAvailability( |
| ScriptState* script_state, |
| int id, |
| ExceptionState& exception_state) { |
| if (media_element_->FastHasAttribute( |
| html_names::kDisableremoteplaybackAttr)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "disableRemotePlayback attribute is present."); |
| return ScriptPromise(); |
| } |
| |
| if (!CancelWatchAvailabilityInternal(id)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "A callback with the given id is not found."); |
| return ScriptPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| resolver->Resolve(); |
| return promise; |
| } |
| |
| ScriptPromise RemotePlayback::cancelWatchAvailability( |
| ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (media_element_->FastHasAttribute( |
| html_names::kDisableremoteplaybackAttr)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "disableRemotePlayback attribute is present."); |
| return ScriptPromise(); |
| } |
| |
| availability_callbacks_.clear(); |
| StopListeningForAvailability(); |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| resolver->Resolve(); |
| return promise; |
| } |
| |
| ScriptPromise RemotePlayback::prompt(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (media_element_->FastHasAttribute( |
| html_names::kDisableremoteplaybackAttr)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "disableRemotePlayback attribute is present."); |
| return ScriptPromise(); |
| } |
| |
| if (prompt_promise_resolver_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kOperationError, |
| "A prompt is already being shown for this media element."); |
| return ScriptPromise(); |
| } |
| |
| if (!media_element_->DomWindow()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidAccessError, |
| "RemotePlayback::prompt() does not work in a detached window."); |
| return ScriptPromise(); |
| } |
| |
| if (!LocalFrame::HasTransientUserActivation( |
| media_element_->DomWindow()->GetFrame())) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidAccessError, |
| "RemotePlayback::prompt() requires user gesture."); |
| return ScriptPromise(); |
| } |
| |
| if (!RuntimeEnabledFeatures::RemotePlaybackBackendEnabled()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "The RemotePlayback API is disabled on this platform."); |
| return ScriptPromise(); |
| } |
| |
| if (availability_ == mojom::ScreenAvailability::UNAVAILABLE) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError, |
| "No remote playback devices found."); |
| return ScriptPromise(); |
| } |
| |
| if (availability_ == mojom::ScreenAvailability::SOURCE_NOT_SUPPORTED) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "The currentSrc is not compatible with remote playback"); |
| return ScriptPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, exception_state.GetContext()); |
| ScriptPromise promise = resolver->Promise(); |
| prompt_promise_resolver_ = resolver; |
| PromptInternal(); |
| RemotePlaybackMetrics::RecordRemotePlaybackLocation( |
| RemotePlaybackInitiationLocation::kRemovePlaybackAPI); |
| return promise; |
| } |
| |
| String RemotePlayback::state() const { |
| return RemotePlaybackStateToString(state_); |
| } |
| |
| bool RemotePlayback::HasPendingActivity() const { |
| return HasEventListeners() || !availability_callbacks_.empty() || |
| prompt_promise_resolver_; |
| } |
| |
| void RemotePlayback::PromptInternal() { |
| if (!GetExecutionContext()) |
| return; |
| |
| PresentationController* controller = |
| PresentationController::FromContext(GetExecutionContext()); |
| if (controller && !availability_urls_.empty()) { |
| controller->GetPresentationService()->StartPresentation( |
| availability_urls_, |
| WTF::BindOnce(&RemotePlayback::HandlePresentationResponse, |
| WrapPersistent(this))); |
| } else { |
| // TODO(yuryu): Wrapping PromptCancelled with base::OnceClosure as |
| // InspectorInstrumentation requires a globally unique pointer to track |
| // tasks. We can remove the wrapper if InspectorInstrumentation returns a |
| // task id. |
| base::OnceClosure task = |
| WTF::BindOnce(&RemotePlayback::PromptCancelled, WrapPersistent(this)); |
| |
| std::unique_ptr<probe::AsyncTaskContext> task_context = |
| std::make_unique<probe::AsyncTaskContext>(); |
| task_context->Schedule(GetExecutionContext(), "promptCancelled"); |
| GetExecutionContext() |
| ->GetTaskRunner(TaskType::kMediaElementEvent) |
| ->PostTask(FROM_HERE, |
| WTF::BindOnce(RunRemotePlaybackTask, |
| WrapPersistent(GetExecutionContext()), |
| std::move(task), std::move(task_context))); |
| } |
| } |
| |
| int RemotePlayback::WatchAvailabilityInternal( |
| AvailabilityCallbackWrapper* callback) { |
| if (RuntimeEnabledFeatures::RemotePlaybackBackendEnabled() && |
| IsBackgroundAvailabilityMonitoringDisabled()) { |
| return kWatchAvailabilityNotSupported; |
| } |
| |
| if (!GetExecutionContext()) |
| return kWatchAvailabilityNotSupported; |
| |
| int id; |
| do { |
| id = GetExecutionContext()->CircularSequentialID(); |
| } while (!availability_callbacks_.insert(id, callback).is_new_entry); |
| |
| // Report the current availability via the callback. |
| // TODO(yuryu): Wrapping notifyInitialAvailability with base::OnceClosure as |
| // InspectorInstrumentation requires a globally unique pointer to track tasks. |
| // We can remove the wrapper if InspectorInstrumentation returns a task id. |
| base::OnceClosure task = WTF::BindOnce( |
| &RemotePlayback::NotifyInitialAvailability, WrapPersistent(this), id); |
| std::unique_ptr<probe::AsyncTaskContext> task_context = |
| std::make_unique<probe::AsyncTaskContext>(); |
| task_context->Schedule(GetExecutionContext(), "watchAvailabilityCallback"); |
| GetExecutionContext() |
| ->GetTaskRunner(TaskType::kMediaElementEvent) |
| ->PostTask(FROM_HERE, |
| WTF::BindOnce(RunRemotePlaybackTask, |
| WrapPersistent(GetExecutionContext()), |
| std::move(task), std::move(task_context))); |
| |
| MaybeStartListeningForAvailability(); |
| return id; |
| } |
| |
| bool RemotePlayback::CancelWatchAvailabilityInternal(int id) { |
| if (id <= 0) // HashMap doesn't support the cases of key = 0 or key = -1. |
| return false; |
| auto iter = availability_callbacks_.find(id); |
| if (iter == availability_callbacks_.end()) |
| return false; |
| availability_callbacks_.erase(iter); |
| if (availability_callbacks_.empty()) |
| StopListeningForAvailability(); |
| |
| return true; |
| } |
| |
| void RemotePlayback::NotifyInitialAvailability(int callback_id) { |
| // May not find the callback if the website cancels it fast enough. |
| auto iter = availability_callbacks_.find(callback_id); |
| if (iter == availability_callbacks_.end()) |
| return; |
| |
| iter->value->Run(this, RemotePlaybackAvailable()); |
| } |
| |
| void RemotePlayback::StateChanged( |
| mojom::blink::PresentationConnectionState state) { |
| if (prompt_promise_resolver_ && |
| IsInParallelAlgorithmRunnable( |
| prompt_promise_resolver_->GetExecutionContext(), |
| prompt_promise_resolver_->GetScriptState())) { |
| // Changing state to "disconnected" from "disconnected" or "connecting" |
| // means that establishing connection with remote playback device failed. |
| // Changing state to anything else means the state change intended by |
| // prompt() succeeded. |
| ScriptState::Scope script_state_scope( |
| prompt_promise_resolver_->GetScriptState()); |
| |
| if (state_ != mojom::blink::PresentationConnectionState::CONNECTED && |
| state == mojom::blink::PresentationConnectionState::CLOSED) { |
| prompt_promise_resolver_->Reject(V8ThrowDOMException::CreateOrDie( |
| prompt_promise_resolver_->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kAbortError, |
| "Failed to connect to the remote device.")); |
| } else { |
| DCHECK((state_ == mojom::blink::PresentationConnectionState::CLOSED && |
| state == mojom::blink::PresentationConnectionState::CONNECTING) || |
| (state_ == mojom::blink::PresentationConnectionState::CONNECTED && |
| state == mojom::blink::PresentationConnectionState::CLOSED)); |
| prompt_promise_resolver_->Resolve(); |
| } |
| } |
| prompt_promise_resolver_ = nullptr; |
| |
| if (state_ == state) |
| return; |
| |
| state_ = state; |
| if (state_ == mojom::blink::PresentationConnectionState::CONNECTING) { |
| DispatchEvent(*Event::Create(event_type_names::kConnecting)); |
| RemotingStarting(*media_element_); |
| } else if (state_ == mojom::blink::PresentationConnectionState::CONNECTED) { |
| DispatchEvent(*Event::Create(event_type_names::kConnect)); |
| } else if (state_ == mojom::blink::PresentationConnectionState::CLOSED || |
| state_ == mojom::blink::PresentationConnectionState::TERMINATED) { |
| DispatchEvent(*Event::Create(event_type_names::kDisconnect)); |
| if (auto* video_element = |
| DynamicTo<HTMLVideoElement>(media_element_.Get())) { |
| video_element->MediaRemotingStopped( |
| WebMediaPlayerClient::kMediaRemotingStopNoText); |
| } |
| CleanupConnections(); |
| presentation_id_ = ""; |
| presentation_url_ = KURL(); |
| media_element_->FlingingStopped(); |
| } |
| |
| for (auto observer : observers_) |
| observer->OnRemotePlaybackStateChanged(state_); |
| } |
| |
| void RemotePlayback::PromptCancelled() { |
| if (!prompt_promise_resolver_ || |
| !IsInParallelAlgorithmRunnable( |
| prompt_promise_resolver_->GetExecutionContext(), |
| prompt_promise_resolver_->GetScriptState())) { |
| prompt_promise_resolver_ = nullptr; |
| return; |
| } |
| |
| ScriptState::Scope script_state_scope( |
| prompt_promise_resolver_->GetScriptState()); |
| |
| prompt_promise_resolver_->Reject(V8ThrowDOMException::CreateOrDie( |
| prompt_promise_resolver_->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kNotAllowedError, "The prompt was dismissed.")); |
| prompt_promise_resolver_ = nullptr; |
| } |
| |
| void RemotePlayback::SourceChanged(const WebURL& source, |
| bool is_source_supported) { |
| if (IsBackgroundAvailabilityMonitoringDisabled()) |
| return; |
| |
| KURL current_url = |
| availability_urls_.empty() ? KURL() : availability_urls_[0]; |
| KURL new_url = GetAvailabilityUrl(source, is_source_supported); |
| |
| if (new_url == current_url) |
| return; |
| |
| // Tell PresentationController to stop listening for availability before the |
| // URLs vector is updated. |
| StopListeningForAvailability(); |
| |
| availability_urls_.clear(); |
| if (!new_url.IsEmpty()) { |
| availability_urls_.push_back(new_url); |
| |
| if (state_ == mojom::blink::PresentationConnectionState::CONNECTED) { |
| RemotingStarting(*media_element_); |
| presentation_url_ = new_url; |
| } |
| } |
| |
| MaybeStartListeningForAvailability(); |
| } |
| |
| WebString RemotePlayback::GetPresentationId() { |
| return presentation_id_; |
| } |
| |
| void RemotePlayback::AddObserver(RemotePlaybackObserver* observer) { |
| observers_.insert(observer); |
| } |
| |
| void RemotePlayback::RemoveObserver(RemotePlaybackObserver* observer) { |
| observers_.erase(observer); |
| } |
| |
| void RemotePlayback::AvailabilityChangedForTesting(bool screen_is_available) { |
| // AvailabilityChanged() is only normally called when |is_listening_| is true. |
| is_listening_ = true; |
| AvailabilityChanged(screen_is_available |
| ? mojom::blink::ScreenAvailability::AVAILABLE |
| : mojom::blink::ScreenAvailability::UNAVAILABLE); |
| } |
| |
| void RemotePlayback::StateChangedForTesting(bool is_connected) { |
| StateChanged(is_connected |
| ? mojom::blink::PresentationConnectionState::CONNECTED |
| : mojom::blink::PresentationConnectionState::CLOSED); |
| } |
| |
| bool RemotePlayback::RemotePlaybackAvailable() const { |
| if (IsBackgroundAvailabilityMonitoringDisabled() && |
| RuntimeEnabledFeatures::RemotePlaybackBackendEnabled() && |
| !media_element_->currentSrc().IsEmpty()) { |
| return true; |
| } |
| |
| return availability_ == mojom::ScreenAvailability::AVAILABLE; |
| } |
| |
| void RemotePlayback::RemotePlaybackDisabled() { |
| if (prompt_promise_resolver_) { |
| prompt_promise_resolver_->Reject(V8ThrowDOMException::CreateOrDie( |
| prompt_promise_resolver_->GetScriptState()->GetIsolate(), |
| DOMExceptionCode::kInvalidStateError, |
| "disableRemotePlayback attribute is present.")); |
| prompt_promise_resolver_ = nullptr; |
| } |
| |
| availability_callbacks_.clear(); |
| StopListeningForAvailability(); |
| |
| if (state_ == mojom::blink::PresentationConnectionState::CLOSED || |
| state_ == mojom::blink::PresentationConnectionState::TERMINATED) { |
| return; |
| } |
| |
| auto* controller = PresentationController::FromContext(GetExecutionContext()); |
| if (controller) { |
| controller->GetPresentationService()->CloseConnection(presentation_url_, |
| presentation_id_); |
| } |
| } |
| |
| void RemotePlayback::CleanupConnections() { |
| target_presentation_connection_.reset(); |
| presentation_connection_receiver_.reset(); |
| } |
| |
| void RemotePlayback::AvailabilityChanged( |
| mojom::blink::ScreenAvailability availability) { |
| DCHECK(is_listening_); |
| DCHECK_NE(availability, mojom::ScreenAvailability::UNKNOWN); |
| DCHECK_NE(availability, mojom::ScreenAvailability::DISABLED); |
| |
| if (availability_ == availability) |
| return; |
| |
| bool old_availability = RemotePlaybackAvailable(); |
| availability_ = availability; |
| bool new_availability = RemotePlaybackAvailable(); |
| if (new_availability == old_availability) |
| return; |
| |
| // Copy the callbacks to a temporary vector to prevent iterator invalidations, |
| // in case the JS callbacks invoke watchAvailability(). |
| HeapVector<Member<AvailabilityCallbackWrapper>> callbacks; |
| CopyValuesToVector(availability_callbacks_, callbacks); |
| |
| for (auto& callback : callbacks) |
| callback->Run(this, new_availability); |
| } |
| |
| const Vector<KURL>& RemotePlayback::Urls() const { |
| // TODO(avayvod): update the URL format and add frame url, mime type and |
| // response headers when available. |
| return availability_urls_; |
| } |
| |
| void RemotePlayback::OnConnectionSuccess( |
| mojom::blink::PresentationConnectionResultPtr result) { |
| presentation_id_ = std::move(result->presentation_info->id); |
| presentation_url_ = std::move(result->presentation_info->url); |
| |
| StateChanged(mojom::blink::PresentationConnectionState::CONNECTING); |
| |
| DCHECK(!presentation_connection_receiver_.is_bound()); |
| auto* presentation_controller = |
| PresentationController::FromContext(GetExecutionContext()); |
| if (!presentation_controller) |
| return; |
| |
| // Note: Messages on |connection_receiver| are ignored. |
| target_presentation_connection_.Bind( |
| std::move(result->connection_remote), |
| GetExecutionContext()->GetTaskRunner(TaskType::kMediaElementEvent)); |
| presentation_connection_receiver_.Bind( |
| std::move(result->connection_receiver), |
| GetExecutionContext()->GetTaskRunner(TaskType::kMediaElementEvent)); |
| RemotePlaybackMetrics::RecordRemotePlaybackStartSessionResult( |
| GetExecutionContext(), true); |
| } |
| |
| void RemotePlayback::OnConnectionError( |
| const mojom::blink::PresentationError& error) { |
| // This is called when: |
| // (1) A request to start a presentation failed. |
| // (2) A PresentationRequest is cancelled. i.e. the user closed the device |
| // selection or the route controller dialog. |
| |
| if (error.error_type == |
| mojom::blink::PresentationErrorType::PRESENTATION_REQUEST_CANCELLED) { |
| PromptCancelled(); |
| return; |
| } |
| |
| presentation_id_ = ""; |
| presentation_url_ = KURL(); |
| |
| StateChanged(mojom::blink::PresentationConnectionState::CLOSED); |
| RemotePlaybackMetrics::RecordRemotePlaybackStartSessionResult( |
| GetExecutionContext(), false); |
| } |
| |
| void RemotePlayback::HandlePresentationResponse( |
| mojom::blink::PresentationConnectionResultPtr result, |
| mojom::blink::PresentationErrorPtr error) { |
| if (result) { |
| OnConnectionSuccess(std::move(result)); |
| } else { |
| OnConnectionError(*error); |
| } |
| } |
| |
| void RemotePlayback::OnMessage( |
| mojom::blink::PresentationConnectionMessagePtr message) { |
| // Messages are ignored. |
| } |
| |
| void RemotePlayback::DidChangeState( |
| mojom::blink::PresentationConnectionState state) { |
| StateChanged(state); |
| } |
| |
| void RemotePlayback::DidClose( |
| mojom::blink::PresentationConnectionCloseReason reason) { |
| StateChanged(mojom::blink::PresentationConnectionState::CLOSED); |
| } |
| |
| void RemotePlayback::StopListeningForAvailability() { |
| if (!is_listening_) |
| return; |
| |
| availability_ = mojom::ScreenAvailability::UNKNOWN; |
| PresentationController* controller = |
| PresentationController::FromContext(GetExecutionContext()); |
| if (!controller) |
| return; |
| |
| controller->RemoveAvailabilityObserver(this); |
| is_listening_ = false; |
| } |
| |
| void RemotePlayback::MaybeStartListeningForAvailability() { |
| if (IsBackgroundAvailabilityMonitoringDisabled()) |
| return; |
| |
| if (is_listening_) |
| return; |
| |
| if (availability_urls_.empty() || availability_callbacks_.empty()) |
| return; |
| |
| PresentationController* controller = |
| PresentationController::FromContext(GetExecutionContext()); |
| if (!controller) |
| return; |
| |
| controller->AddAvailabilityObserver(this); |
| is_listening_ = true; |
| } |
| |
| void RemotePlayback::Trace(Visitor* visitor) const { |
| visitor->Trace(availability_callbacks_); |
| visitor->Trace(prompt_promise_resolver_); |
| visitor->Trace(media_element_); |
| visitor->Trace(presentation_connection_receiver_); |
| visitor->Trace(target_presentation_connection_); |
| visitor->Trace(observers_); |
| EventTargetWithInlineData::Trace(visitor); |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| RemotePlaybackController::Trace(visitor); |
| } |
| |
| } // namespace blink |