| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // 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/audio_output_devices/html_media_element_audio_output_device.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/web/web_local_frame_client.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.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/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/modules/audio_output_devices/set_sink_id_callbacks.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| class SetSinkIdResolver : public ScriptPromiseResolver { |
| WTF_MAKE_NONCOPYABLE(SetSinkIdResolver); |
| |
| public: |
| static SetSinkIdResolver* Create(ScriptState*, |
| HTMLMediaElement&, |
| const String& sink_id); |
| SetSinkIdResolver(ScriptState*, HTMLMediaElement&, const String& sink_id); |
| ~SetSinkIdResolver() override = default; |
| void StartAsync(); |
| |
| void Trace(blink::Visitor*) override; |
| |
| private: |
| void TimerFired(TimerBase*); |
| |
| Member<HTMLMediaElement> element_; |
| String sink_id_; |
| TaskRunnerTimer<SetSinkIdResolver> timer_; |
| }; |
| |
| SetSinkIdResolver* SetSinkIdResolver::Create(ScriptState* script_state, |
| HTMLMediaElement& element, |
| const String& sink_id) { |
| SetSinkIdResolver* resolver = |
| MakeGarbageCollected<SetSinkIdResolver>(script_state, element, sink_id); |
| resolver->KeepAliveWhilePending(); |
| return resolver; |
| } |
| |
| SetSinkIdResolver::SetSinkIdResolver(ScriptState* script_state, |
| HTMLMediaElement& element, |
| const String& sink_id) |
| : ScriptPromiseResolver(script_state), |
| element_(element), |
| sink_id_(sink_id), |
| timer_(ExecutionContext::From(script_state) |
| ->GetTaskRunner(TaskType::kMiscPlatformAPI), |
| this, |
| &SetSinkIdResolver::TimerFired) {} |
| |
| void SetSinkIdResolver::StartAsync() { |
| timer_.StartOneShot(TimeDelta(), FROM_HERE); |
| } |
| |
| void SetSinkIdResolver::TimerFired(TimerBase* timer) { |
| ExecutionContext* context = GetExecutionContext(); |
| std::unique_ptr<SetSinkIdCallbacks> callbacks = |
| std::make_unique<SetSinkIdCallbacks>(this, *element_, sink_id_); |
| WebMediaPlayer* web_media_player = element_->GetWebMediaPlayer(); |
| if (web_media_player) { |
| // Using release() to transfer ownership because |webMediaPlayer| is a |
| // platform object that takes raw pointers. |
| web_media_player->SetSinkId(sink_id_, std::move(callbacks)); |
| return; |
| } |
| |
| if (!context) { |
| // Detached contexts shouldn't be playing audio. Note that despite this |
| // explicit Reject(), any associated JS callbacks will never be called |
| // because the context is already detached... |
| Reject(DOMException::Create( |
| DOMExceptionCode::kSecurityError, |
| "Impossible to authorize device for detached context")); |
| return; |
| } |
| |
| // This is associated with an HTML element, so the context must be a Document. |
| auto& document = To<Document>(*context); |
| WebLocalFrameImpl* web_frame = |
| WebLocalFrameImpl::FromFrame(document.GetFrame()); |
| web_frame->Client()->CheckIfAudioSinkExistsAndIsAuthorized( |
| sink_id_, std::move(callbacks)); |
| } |
| |
| void SetSinkIdResolver::Trace(blink::Visitor* visitor) { |
| visitor->Trace(element_); |
| ScriptPromiseResolver::Trace(visitor); |
| } |
| |
| } // namespace |
| |
| HTMLMediaElementAudioOutputDevice::HTMLMediaElementAudioOutputDevice() {} |
| |
| String HTMLMediaElementAudioOutputDevice::sinkId(HTMLMediaElement& element) { |
| HTMLMediaElementAudioOutputDevice& aod_element = |
| HTMLMediaElementAudioOutputDevice::From(element); |
| return aod_element.sink_id_; |
| } |
| |
| void HTMLMediaElementAudioOutputDevice::setSinkId(const String& sink_id) { |
| sink_id_ = sink_id; |
| } |
| |
| ScriptPromise HTMLMediaElementAudioOutputDevice::setSinkId( |
| ScriptState* script_state, |
| HTMLMediaElement& element, |
| const String& sink_id) { |
| SetSinkIdResolver* resolver = |
| SetSinkIdResolver::Create(script_state, element, sink_id); |
| ScriptPromise promise = resolver->Promise(); |
| if (sink_id == HTMLMediaElementAudioOutputDevice::sinkId(element)) |
| resolver->Resolve(); |
| else |
| resolver->StartAsync(); |
| |
| return promise; |
| } |
| |
| const char HTMLMediaElementAudioOutputDevice::kSupplementName[] = |
| "HTMLMediaElementAudioOutputDevice"; |
| |
| HTMLMediaElementAudioOutputDevice& HTMLMediaElementAudioOutputDevice::From( |
| HTMLMediaElement& element) { |
| HTMLMediaElementAudioOutputDevice* supplement = |
| Supplement<HTMLMediaElement>::From<HTMLMediaElementAudioOutputDevice>( |
| element); |
| if (!supplement) { |
| supplement = MakeGarbageCollected<HTMLMediaElementAudioOutputDevice>(); |
| ProvideTo(element, supplement); |
| } |
| return *supplement; |
| } |
| |
| void HTMLMediaElementAudioOutputDevice::Trace(blink::Visitor* visitor) { |
| Supplement<HTMLMediaElement>::Trace(visitor); |
| } |
| |
| } // namespace blink |