blob: 0efd897174cb955acd6d8ca58bab502dea9e7aca [file] [log] [blame] [edit]
/*
* 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 "config.h"
#if ENABLE(WEB_AUDIO)
#include "modules/webaudio/MediaElementAudioSourceNode.h"
#include "core/dom/CrossThreadTask.h"
#include "core/frame/ConsoleTypes.h"
#include "core/html/HTMLMediaElement.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/webaudio/AbstractAudioContext.h"
#include "modules/webaudio/AudioNodeOutput.h"
#include "platform/Logging.h"
#include "platform/audio/AudioUtilities.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "wtf/Locker.h"
#include "wtf/MainThread.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().string())
{
ASSERT(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 > AbstractAudioContext::maxNumberOfChannels() || !AudioUtilities::isValidAudioBufferSampleRate(sourceSampleRate)) {
// process() will generate silence for these uninitialized values.
WTF_LOG(Media, "MediaElementAudioSourceNode::setFormat(%u, %f) - unhandled format change", static_cast<unsigned>(numberOfChannels), sourceSampleRate);
// 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 = adoptPtr(new MultiChannelResampler(scaleFactor, numberOfChannels));
} else {
// Bypass resampling.
m_multiChannelResampler.clear();
}
{
// The context must be locked when changing the number of output channels.
AbstractAudioContext::AutoLocker contextLocker(context());
// Do any necesssary re-configuration to the output's number of channels.
output(0).setNumberOfChannels(numberOfChannels);
}
}
}
bool MediaElementAudioSourceHandler::passesCORSAccessCheck()
{
ASSERT(mediaElement());
return (mediaElement()->webMediaPlayer() && mediaElement()->webMediaPlayer()->didPassCORSAccessCheck())
|| m_passesCurrentSrcCORSAccessCheck;
}
void MediaElementAudioSourceHandler::onCurrentSrcChanged(const KURL& currentSrc)
{
ASSERT(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.string();
}
bool MediaElementAudioSourceHandler::passesCurrentSrcCORSAccessCheck(const KURL& currentSrc)
{
ASSERT(isMainThread());
return context()->securityOrigin() && context()->securityOrigin()->canRequest(currentSrc);
}
void MediaElementAudioSourceHandler::printCORSMessage(const String& message)
{
if (context()->executionContext()) {
context()->executionContext()->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()->audioSourceProvider();
// 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()) {
ASSERT(m_sourceSampleRate != sampleRate());
m_multiChannelResampler->process(&provider, outputBus, numberOfFrames);
} else {
// Bypass the resampler completely if the source is at the context's sample-rate.
ASSERT(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()->executionContext()) {
context()->executionContext()->postTask(FROM_HERE,
createCrossThreadTask(&MediaElementAudioSourceHandler::printCORSMessage,
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(AbstractAudioContext& context, HTMLMediaElement& mediaElement)
: AudioSourceNode(context)
{
setHandler(MediaElementAudioSourceHandler::create(*this, mediaElement));
}
MediaElementAudioSourceNode* MediaElementAudioSourceNode::create(AbstractAudioContext& context, HTMLMediaElement& mediaElement)
{
return new MediaElementAudioSourceNode(context, mediaElement);
}
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
#endif // ENABLE(WEB_AUDIO)