text: // 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 "modules/audio_output_devices/HTMLMediaElementAudioOutputDevice.h"
  
  #include "bindings/core/v8/ScriptPromiseResolver.h"
  #include "bindings/core/v8/ScriptState.h"
  #include "core/dom/DOMException.h"
  #include "core/dom/ExecutionContext.h"
  #include "modules/audio_output_devices/AudioOutputDeviceClient.h"
  #include "modules/audio_output_devices/SetSinkIdCallbacks.h"
  #include "public/platform/WebSecurityOrigin.h"
  #include "wtf/PtrUtil.h"
  #include <memory>
  
  namespace blink {
  
  namespace {
  
  class SetSinkIdResolver : public ScriptPromiseResolver {
    WTF_MAKE_NONCOPYABLE(SetSinkIdResolver);
  
   public:
    static SetSinkIdResolver* create(ScriptState*,
                                     HTMLMediaElement&,
                                     const String& sinkId);
    ~SetSinkIdResolver() override = default;
    void startAsync();
  
    DECLARE_VIRTUAL_TRACE();
  
   private:
    SetSinkIdResolver(ScriptState*, HTMLMediaElement&, const String& sinkId);
    void timerFired(TimerBase*);
  
    Member<HTMLMediaElement> m_element;
    String m_sinkId;
    Timer<SetSinkIdResolver> m_timer;
  };
  
  SetSinkIdResolver* SetSinkIdResolver::create(ScriptState* scriptState,
                                               HTMLMediaElement& element,
                                               const String& sinkId) {
    SetSinkIdResolver* resolver =
        new SetSinkIdResolver(scriptState, element, sinkId);
    resolver->suspendIfNeeded();
    resolver->keepAliveWhilePending();
    return resolver;
  }
  
  SetSinkIdResolver::SetSinkIdResolver(ScriptState* scriptState,
                                       HTMLMediaElement& element,
                                       const String& sinkId)
      : ScriptPromiseResolver(scriptState),
        m_element(element),
        m_sinkId(sinkId),
        m_timer(this, &SetSinkIdResolver::timerFired) {}
  
  void SetSinkIdResolver::startAsync() {
    m_timer.startOneShot(0, BLINK_FROM_HERE);
  }
  
  void SetSinkIdResolver::timerFired(TimerBase* timer) {
    ExecutionContext* context = getExecutionContext();
    ASSERT(context && context->isDocument());
    std::unique_ptr<SetSinkIdCallbacks> callbacks =
        wrapUnique(new SetSinkIdCallbacks(this, *m_element, m_sinkId));
    WebMediaPlayer* webMediaPlayer = m_element->webMediaPlayer();
    if (webMediaPlayer) {
      // Using release() to transfer ownership because |webMediaPlayer| is a
      // platform object that takes raw pointers.
      webMediaPlayer->setSinkId(m_sinkId,
                                WebSecurityOrigin(context->getSecurityOrigin()),
                                callbacks.release());
    } else {
      if (AudioOutputDeviceClient* client =
              AudioOutputDeviceClient::from(context)) {
        client->checkIfAudioSinkExistsAndIsAuthorized(context, m_sinkId,
                                                      std::move(callbacks));
      } else {
        // The context has been detached. Impossible to get a security origin to
        // check.
        ASSERT(context->activeDOMObjectsAreStopped());
        reject(DOMException::create(
            SecurityError,
            "Impossible to authorize device for detached context"));
      }
    }
  }
  
  DEFINE_TRACE(SetSinkIdResolver) {
    visitor->trace(m_element);
    ScriptPromiseResolver::trace(visitor);
  }
  
  }  // namespace
  
  HTMLMediaElementAudioOutputDevice::HTMLMediaElementAudioOutputDevice()
      : m_sinkId("") {}
  
  String HTMLMediaElementAudioOutputDevice::sinkId(HTMLMediaElement& element) {
    HTMLMediaElementAudioOutputDevice& aodElement =
        HTMLMediaElementAudioOutputDevice::from(element);
    return aodElement.m_sinkId;
  }
  
  void HTMLMediaElementAudioOutputDevice::setSinkId(const String& sinkId) {
    m_sinkId = sinkId;
  }
  
  ScriptPromise HTMLMediaElementAudioOutputDevice::setSinkId(
      ScriptState* scriptState,
      HTMLMediaElement& element,
      const String& sinkId) {
    SetSinkIdResolver* resolver =
        SetSinkIdResolver::create(scriptState, element, sinkId);
    ScriptPromise promise = resolver->promise();
    if (sinkId == HTMLMediaElementAudioOutputDevice::sinkId(element))
      resolver->resolve();
    else
      resolver->startAsync();
  
    return promise;
  }
  
  const char* HTMLMediaElementAudioOutputDevice::supplementName() {
    return "HTMLMediaElementAudioOutputDevice";
  }
  
  HTMLMediaElementAudioOutputDevice& HTMLMediaElementAudioOutputDevice::from(
      HTMLMediaElement& element) {
    HTMLMediaElementAudioOutputDevice* supplement =
        static_cast<HTMLMediaElementAudioOutputDevice*>(
            Supplement<HTMLMediaElement>::from(element, supplementName()));
    if (!supplement) {
      supplement = new HTMLMediaElementAudioOutputDevice();
      provideTo(element, supplementName(), supplement);
    }
    return *supplement;
  }
  
  DEFINE_TRACE(HTMLMediaElementAudioOutputDevice) {
    Supplement<HTMLMediaElement>::trace(visitor);
  }
  
  }  // namespace blink
  