AudioSessionRoutingArbitratorProxyCocoa.mm [plain text]
/*
* Copyright (C) 2020 Apple 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.
*/
#import "config.h"
#import "AudioSessionRoutingArbitratorProxy.h"
#if ENABLE(ROUTING_ARBITRATION) && HAVE(AVAUDIO_ROUTING_ARBITER)
#import "AudioSessionRoutingArbitratorProxyMessages.h"
#import "Logging.h"
#import "WebPageProxy.h"
#import "WebProcessProxy.h"
#import <WebCore/AudioSession.h>
#import <wtf/NeverDestroyed.h>
#import <pal/cocoa/AVFoundationSoftLink.h>
namespace WebKit {
using namespace WebCore;
class SharedArbitrator {
public:
static SharedArbitrator& sharedInstance();
using RoutingArbitrationError = AudioSessionRoutingArbitrationClient::RoutingArbitrationError;
using DefaultRouteChanged = AudioSessionRoutingArbitrationClient::DefaultRouteChanged;
using ArbitrationCallback = AudioSessionRoutingArbitratorProxy::ArbitrationCallback;
bool isInRoutingArbitrationForArbitrator(AudioSessionRoutingArbitratorProxy&);
void beginRoutingArbitrationForArbitrator(AudioSessionRoutingArbitratorProxy&, ArbitrationCallback&&);
void endRoutingArbitrationForArbitrator(AudioSessionRoutingArbitratorProxy&);
private:
Optional<AudioSession::CategoryType> m_currentCategory { AudioSession::None };
WeakHashSet<AudioSessionRoutingArbitratorProxy> m_arbitrators;
Vector<ArbitrationCallback> m_enqueuedCallbacks;
bool m_setupArbitrationOngoing { false };
};
SharedArbitrator& SharedArbitrator::sharedInstance()
{
static NeverDestroyed<SharedArbitrator> instance;
return instance;
}
bool SharedArbitrator::isInRoutingArbitrationForArbitrator(AudioSessionRoutingArbitratorProxy& proxy)
{
return m_arbitrators.contains(proxy);
}
void SharedArbitrator::beginRoutingArbitrationForArbitrator(AudioSessionRoutingArbitratorProxy& proxy, ArbitrationCallback&& callback)
{
ASSERT(!isInRoutingArbitrationForArbitrator(proxy));
if (m_setupArbitrationOngoing) {
m_enqueuedCallbacks.append([this, weakProxy = makeWeakPtr(proxy), callback = WTFMove(callback)] (RoutingArbitrationError error, DefaultRouteChanged routeChanged) mutable {
if (error == RoutingArbitrationError::None && weakProxy)
m_arbitrators.add(*weakProxy);
callback(error, routeChanged);
});
return;
}
auto requestedCategory = proxy.category();
if (m_currentCategory) {
if (*m_currentCategory >= requestedCategory) {
m_arbitrators.add(proxy);
callback(RoutingArbitrationError::None, DefaultRouteChanged::No);
return;
}
[[PAL::getAVAudioRoutingArbiterClass() sharedRoutingArbiter] leaveArbitration];
}
m_currentCategory = requestedCategory;
AVAudioRoutingArbitrationCategory arbitrationCategory = AVAudioRoutingArbitrationCategoryPlayback;
switch (requestedCategory) {
case AudioSession::MediaPlayback:
arbitrationCategory = AVAudioRoutingArbitrationCategoryPlayback;
break;
case AudioSession::RecordAudio:
arbitrationCategory = AVAudioRoutingArbitrationCategoryPlayAndRecord;
break;
case AudioSession::PlayAndRecord:
arbitrationCategory = AVAudioRoutingArbitrationCategoryPlayAndRecordVoice;
break;
default:
ASSERT_NOT_REACHED();
}
m_setupArbitrationOngoing = true;
m_enqueuedCallbacks.append([this, weakProxy = makeWeakPtr(proxy), callback = WTFMove(callback)] (RoutingArbitrationError error, DefaultRouteChanged routeChanged) mutable {
if (error == RoutingArbitrationError::None && weakProxy)
m_arbitrators.add(*weakProxy);
callback(error, routeChanged);
});
[[PAL::getAVAudioRoutingArbiterClass() sharedRoutingArbiter] beginArbitrationWithCategory:arbitrationCategory completionHandler:[this](BOOL defaultDeviceChanged, NSError * _Nullable error) {
callOnMainRunLoop([this, defaultDeviceChanged, error = retainPtr(error)] {
if (error)
RELEASE_LOG_ERROR(Media, "SharedArbitrator::beginRoutingArbitrationForArbitrator: %s failed with error %s:%s", convertEnumerationToString(*m_currentCategory).ascii().data(), [[error domain] UTF8String], [[error localizedDescription] UTF8String]);
// FIXME: Do we need to reset sample rate and buffer size for the new default device?
if (defaultDeviceChanged)
LOG(Media, "AudioSession::setCategory() - defaultDeviceChanged!");
Vector<ArbitrationCallback> callbacks = WTFMove(m_enqueuedCallbacks);
for (auto& callback : callbacks)
callback(error ? RoutingArbitrationError::Failed : RoutingArbitrationError::None, defaultDeviceChanged ? DefaultRouteChanged::Yes : DefaultRouteChanged::No);
m_setupArbitrationOngoing = false;
});
}];
}
void SharedArbitrator::endRoutingArbitrationForArbitrator(AudioSessionRoutingArbitratorProxy& proxy)
{
ASSERT(isInRoutingArbitrationForArbitrator(proxy));
m_arbitrators.remove(proxy);
if (!m_arbitrators.computesEmpty())
return;
for (auto& callback : m_enqueuedCallbacks)
callback(RoutingArbitrationError::Cancelled, DefaultRouteChanged::No);
m_enqueuedCallbacks.clear();
m_currentCategory.reset();
[[PAL::getAVAudioRoutingArbiterClass() sharedRoutingArbiter] leaveArbitration];
}
AudioSessionRoutingArbitratorProxy::AudioSessionRoutingArbitratorProxy(WebProcessProxy& proxy)
: m_process(proxy)
{
m_process.addMessageReceiver(Messages::AudioSessionRoutingArbitratorProxy::messageReceiverName(), destinationId(), *this);
}
AudioSessionRoutingArbitratorProxy::~AudioSessionRoutingArbitratorProxy()
{
m_process.removeMessageReceiver(Messages::AudioSessionRoutingArbitratorProxy::messageReceiverName(), destinationId());
}
void AudioSessionRoutingArbitratorProxy::processDidTerminate()
{
if (SharedArbitrator::sharedInstance().isInRoutingArbitrationForArbitrator(*this))
endRoutingArbitration();
}
void AudioSessionRoutingArbitratorProxy::beginRoutingArbitrationWithCategory(WebCore::AudioSession::CategoryType category, ArbitrationCallback&& callback)
{
m_category = category;
m_arbitrationStatus = ArbitrationStatus::Pending;
m_arbitrationUpdateTime = WallTime::now();
SharedArbitrator::sharedInstance().beginRoutingArbitrationForArbitrator(*this, [weakThis = makeWeakPtr(*this), callback = WTFMove(callback)] (RoutingArbitrationError error, DefaultRouteChanged routeChanged) mutable {
if (weakThis)
weakThis->m_arbitrationStatus = error == RoutingArbitrationError::None ? ArbitrationStatus::Active : ArbitrationStatus::None;
callback(error, routeChanged);
});
}
void AudioSessionRoutingArbitratorProxy::endRoutingArbitration()
{
SharedArbitrator::sharedInstance().endRoutingArbitrationForArbitrator(*this);
m_arbitrationStatus = ArbitrationStatus::None;
}
}
#endif