blob: e306ed482093344c32dd6505a81b301f7308f273 [file] [log] [blame]
// Copyright 2016 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/remoteplayback/RemotePlayback.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/HTMLNames.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/events/Event.h"
#include "core/html/HTMLMediaElement.h"
#include "modules/EventTargetModules.h"
#include "modules/remoteplayback/RemotePlaybackAvailability.h"
#include "platform/UserGestureIndicator.h"
namespace blink {
namespace {
const AtomicString& remotePlaybackStateToString(WebRemotePlaybackState state) {
DEFINE_STATIC_LOCAL(const AtomicString, connectedValue, ("connected"));
DEFINE_STATIC_LOCAL(const AtomicString, disconnectedValue, ("disconnected"));
switch (state) {
case WebRemotePlaybackState::Connected:
return connectedValue;
case WebRemotePlaybackState::Disconnected:
return disconnectedValue;
}
ASSERT_NOT_REACHED();
return disconnectedValue;
}
} // anonymous namespace
// static
RemotePlayback* RemotePlayback::create(HTMLMediaElement& element) {
ASSERT(element.document().frame());
RemotePlayback* remotePlayback = new RemotePlayback(element);
element.setRemotePlaybackClient(remotePlayback);
return remotePlayback;
}
RemotePlayback::RemotePlayback(HTMLMediaElement& element)
: ActiveScriptWrappable(this),
m_state(element.isPlayingRemotely()
? WebRemotePlaybackState::Connected
: WebRemotePlaybackState::Disconnected),
m_availability(element.hasRemoteRoutes()),
m_mediaElement(&element) {}
const AtomicString& RemotePlayback::interfaceName() const {
return EventTargetNames::RemotePlayback;
}
ExecutionContext* RemotePlayback::getExecutionContext() const {
return &m_mediaElement->document();
}
ScriptPromise RemotePlayback::getAvailability(ScriptState* scriptState) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
// 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 getAvailability()
// will be resolved asynchronously.
RemotePlaybackAvailability* availability =
RemotePlaybackAvailability::take(resolver, m_availability);
m_availabilityObjects.append(availability);
resolver->resolve(availability);
return promise;
}
ScriptPromise RemotePlayback::prompt(ScriptState* scriptState) {
// TODO(avayvod): implement steps 4, 5, 8, 9 of the algorithm.
// https://crbug.com/647441
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
if (m_mediaElement->fastHasAttribute(HTMLNames::disableremoteplaybackAttr)) {
resolver->reject(DOMException::create(
InvalidStateError, "disableRemotePlayback attribute is present."));
return promise;
}
if (m_promptPromiseResolver) {
resolver->reject(DOMException::create(
OperationError,
"A prompt is already being shown for this media element."));
return promise;
}
if (!UserGestureIndicator::utilizeUserGesture()) {
resolver->reject(DOMException::create(
InvalidAccessError, "RemotePlayback::prompt() requires user gesture."));
return promise;
}
if (m_state == WebRemotePlaybackState::Disconnected) {
m_promptPromiseResolver = resolver;
m_mediaElement->requestRemotePlayback();
} else {
m_mediaElement->requestRemotePlaybackControl();
// TODO(avayvod): Need to keep the resolver until user chooses to stop
// the remote playback (resolve) or dismisses the UI (reject).
// Steps 11 and 12 of the prompt() algorithm.
// https://crbug.com/647441
resolver->resolve();
}
return promise;
}
String RemotePlayback::state() const {
return remotePlaybackStateToString(m_state);
}
bool RemotePlayback::hasPendingActivity() const {
return hasEventListeners() || !m_availabilityObjects.isEmpty() ||
m_promptPromiseResolver;
}
void RemotePlayback::stateChanged(WebRemotePlaybackState state) {
// We may get a "disconnected" state change while in the "disconnected"
// state if initiated connection fails. So cleanup the promise resolvers
// before checking if anything changed.
// TODO(avayvod): cleanup this logic when we implementing the "connecting"
// state.
if (m_promptPromiseResolver) {
if (state != WebRemotePlaybackState::Disconnected)
m_promptPromiseResolver->resolve();
else
m_promptPromiseResolver->reject(DOMException::create(
AbortError, "Failed to connect to the remote device."));
m_promptPromiseResolver = nullptr;
}
if (m_state == state)
return;
m_state = state;
dispatchEvent(Event::create(EventTypeNames::statechange));
}
void RemotePlayback::availabilityChanged(bool available) {
if (m_availability == available)
return;
m_availability = available;
for (auto& availabilityObject : m_availabilityObjects)
availabilityObject->availabilityChanged(available);
}
void RemotePlayback::promptCancelled() {
if (!m_promptPromiseResolver)
return;
m_promptPromiseResolver->resolve();
m_promptPromiseResolver = nullptr;
}
DEFINE_TRACE(RemotePlayback) {
visitor->trace(m_availabilityObjects);
visitor->trace(m_promptPromiseResolver);
visitor->trace(m_mediaElement);
EventTargetWithInlineData::trace(visitor);
}
} // namespace blink