<filename>third_party/WebKit/Source/modules/audio_output_devices/HTMLMediaElementAudioOutputDevice.cpp
// 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 =
      WTF::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.
      DCHECK(context->isContextDestroyed());
      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
