blob: 87926f2309d2a88112d1b33e532f487f6d2bd826 [file] [log] [blame]
/*
* Copyright (C) 2011, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "modules/webaudio/MediaElementAudioSourceNode.h"
#include "core/dom/ExecutionContextTask.h"
#include "core/html/HTMLMediaElement.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/webaudio/AudioNodeOutput.h"
#include "modules/webaudio/BaseAudioContext.h"
#include "modules/webaudio/MediaElementAudioSourceOptions.h"
#include "platform/audio/AudioUtilities.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "wtf/Locker.h"
#include "wtf/PtrUtil.h"
namespace blink {
MediaElementAudioSourceHandler::MediaElementAudioSourceHandler(
AudioNode& node,
HTMLMediaElement& mediaElement)
: AudioHandler(NodeTypeMediaElementAudioSource,
node,
node.context()->sampleRate()),
m_mediaElement(mediaElement),
m_sourceNumberOfChannels(0),
m_sourceSampleRate(0),
m_passesCurrentSrcCORSAccessCheck(
passesCurrentSrcCORSAccessCheck(mediaElement.currentSrc())),
m_maybePrintCORSMessage(!m_passesCurrentSrcCORSAccessCheck),
m_currentSrcString(mediaElement.currentSrc().getString()) {
DCHECK(isMainThread());
// Default to stereo. This could change depending on what the media element
// .src is set to.
addOutput(2);
initialize();
}
PassRefPtr<MediaElementAudioSourceHandler>
MediaElementAudioSourceHandler::create(AudioNode& node,
HTMLMediaElement& mediaElement) {
return adoptRef(new MediaElementAudioSourceHandler(node, mediaElement));
}
MediaElementAudioSourceHandler::~MediaElementAudioSourceHandler() {
uninitialize();
}
void MediaElementAudioSourceHandler::dispose() {
m_mediaElement->setAudioSourceNode(nullptr);
AudioHandler::dispose();
}
void MediaElementAudioSourceHandler::setFormat(size_t numberOfChannels,
float sourceSampleRate) {
if (numberOfChannels != m_sourceNumberOfChannels ||
sourceSampleRate != m_sourceSampleRate) {
if (!numberOfChannels ||
numberOfChannels > BaseAudioContext::maxNumberOfChannels() ||
!AudioUtilities::isValidAudioBufferSampleRate(sourceSampleRate)) {
// process() will generate silence for these uninitialized values.
DLOG(ERROR) << "setFormat(" << numberOfChannels << ", "
<< sourceSampleRate << ") - unhandled format change";
// Synchronize with process().
Locker<MediaElementAudioSourceHandler> locker(*this);
m_sourceNumberOfChannels = 0;
m_sourceSampleRate = 0;
return;
}
// Synchronize with process() to protect m_sourceNumberOfChannels,
// m_sourceSampleRate, and m_multiChannelResampler.
Locker<MediaElementAudioSourceHandler> locker(*this);
m_sourceNumberOfChannels = numberOfChannels;
m_sourceSampleRate = sourceSampleRate;
if (sourceSampleRate != sampleRate()) {
double scaleFactor = sourceSampleRate / sampleRate();
m_multiChannelResampler =
wrapUnique(new MultiChannelResampler(scaleFactor, numberOfChannels));
} else {
// Bypass resampling.
m_multiChannelResampler.reset();
}
{
// The context must be locked when changing the number of output channels.
BaseAudioContext::AutoLocker contextLocker(context());
// Do any necesssary re-configuration to the output's number of channels.
output(0).setNumberOfChannels(numberOfChannels);
}
}
}
bool MediaElementAudioSourceHandler::passesCORSAccessCheck() {
DCHECK(mediaElement());
return (mediaElement()->webMediaPlayer() &&
mediaElement()->webMediaPlayer()->didPassCORSAccessCheck()) ||
m_passesCurrentSrcCORSAccessCheck;
}
void MediaElementAudioSourceHandler::onCurrentSrcChanged(
const KURL& currentSrc) {
DCHECK(isMainThread());
// Synchronize with process().
Locker<MediaElementAudioSourceHandler> locker(*this);
m_passesCurrentSrcCORSAccessCheck =
passesCurrentSrcCORSAccessCheck(currentSrc);
// Make a note if we need to print a console message and save the |curentSrc| for use in the
// message. Need to wait until later to print the message in case HTMLMediaElement allows
// access.
m_maybePrintCORSMessage = !m_passesCurrentSrcCORSAccessCheck;
m_currentSrcString = currentSrc.getString();
}
bool MediaElementAudioSourceHandler::passesCurrentSrcCORSAccessCheck(
const KURL& currentSrc) {
DCHECK(isMainThread());
return context()->getSecurityOrigin() &&
context()->getSecurityOrigin()->canRequest(currentSrc);
}
void MediaElementAudioSourceHandler::printCORSMessage(const String& message) {
if (context()->getExecutionContext()) {
context()->getExecutionContext()->addConsoleMessage(
ConsoleMessage::create(SecurityMessageSource, InfoMessageLevel,
"MediaElementAudioSource outputs zeroes due to "
"CORS access restrictions for " +
message));
}
}
void MediaElementAudioSourceHandler::process(size_t numberOfFrames) {
AudioBus* outputBus = output(0).bus();
// Use a tryLock() to avoid contention in the real-time audio thread.
// If we fail to acquire the lock then the HTMLMediaElement must be in the middle of
// reconfiguring its playback engine, so we output silence in this case.
MutexTryLocker tryLocker(m_processLock);
if (tryLocker.locked()) {
if (!mediaElement() || !m_sourceNumberOfChannels || !m_sourceSampleRate) {
outputBus->zero();
return;
}
AudioSourceProvider& provider = mediaElement()->getAudioSourceProvider();
// Grab data from the provider so that the element continues to make progress, even if
// we're going to output silence anyway.
if (m_multiChannelResampler.get()) {
DCHECK_NE(m_sourceSampleRate, sampleRate());
m_multiChannelResampler->process(&provider, outputBus, numberOfFrames);
} else {
// Bypass the resampler completely if the source is at the context's sample-rate.
DCHECK_EQ(m_sourceSampleRate, sampleRate());
provider.provideInput(outputBus, numberOfFrames);
}
// Output silence if we don't have access to the element.
if (!passesCORSAccessCheck()) {
if (m_maybePrintCORSMessage) {
// Print a CORS message, but just once for each change in the current media
// element source, and only if we have a document to print to.
m_maybePrintCORSMessage = false;
if (context()->getExecutionContext()) {
context()->getExecutionContext()->postTask(
BLINK_FROM_HERE,
createCrossThreadTask(
&MediaElementAudioSourceHandler::printCORSMessage,
PassRefPtr<MediaElementAudioSourceHandler>(this),
m_currentSrcString));
}
}
outputBus->zero();
}
} else {
// We failed to acquire the lock.
outputBus->zero();
}
}
void MediaElementAudioSourceHandler::lock() {
m_processLock.lock();
}
void MediaElementAudioSourceHandler::unlock() {
m_processLock.unlock();
}
// ----------------------------------------------------------------
MediaElementAudioSourceNode::MediaElementAudioSourceNode(
BaseAudioContext& context,
HTMLMediaElement& mediaElement)
: AudioSourceNode(context) {
setHandler(MediaElementAudioSourceHandler::create(*this, mediaElement));
}
MediaElementAudioSourceNode* MediaElementAudioSourceNode::create(
BaseAudioContext& context,
HTMLMediaElement& mediaElement,
ExceptionState& exceptionState) {
DCHECK(isMainThread());
if (context.isContextClosed()) {
context.throwExceptionForClosedState(exceptionState);
return nullptr;
}
// First check if this media element already has a source node.
if (mediaElement.audioSourceNode()) {
exceptionState.throwDOMException(InvalidStateError,
"HTMLMediaElement already connected "
"previously to a different "
"MediaElementSourceNode.");
return nullptr;
}
MediaElementAudioSourceNode* node =
new MediaElementAudioSourceNode(context, mediaElement);
if (node) {
mediaElement.setAudioSourceNode(node);
// context keeps reference until node is disconnected
context.notifySourceNodeStartedProcessing(node);
}
return node;
}
MediaElementAudioSourceNode* MediaElementAudioSourceNode::create(
BaseAudioContext* context,
const MediaElementAudioSourceOptions& options,
ExceptionState& exceptionState) {
if (!options.hasMediaElement()) {
exceptionState.throwDOMException(NotFoundError,
"mediaElement member is required.");
return nullptr;
}
return create(*context, *options.mediaElement(), exceptionState);
}
DEFINE_TRACE(MediaElementAudioSourceNode) {
AudioSourceProviderClient::trace(visitor);
AudioSourceNode::trace(visitor);
}
MediaElementAudioSourceHandler&
MediaElementAudioSourceNode::mediaElementAudioSourceHandler() const {
return static_cast<MediaElementAudioSourceHandler&>(handler());
}
HTMLMediaElement* MediaElementAudioSourceNode::mediaElement() const {
return mediaElementAudioSourceHandler().mediaElement();
}
void MediaElementAudioSourceNode::setFormat(size_t numberOfChannels,
float sampleRate) {
mediaElementAudioSourceHandler().setFormat(numberOfChannels, sampleRate);
}
void MediaElementAudioSourceNode::onCurrentSrcChanged(const KURL& currentSrc) {
mediaElementAudioSourceHandler().onCurrentSrcChanged(currentSrc);
}
void MediaElementAudioSourceNode::lock() {
mediaElementAudioSourceHandler().lock();
}
void MediaElementAudioSourceNode::unlock() {
mediaElementAudioSourceHandler().unlock();
}
} // namespace blink