When using Vivox on iOS, for Vivox to have access to the microphone, the AudioSession category must be set to AVAudioSessionCategoryPlayAndRecord. When in this audio category, audio render continues with the Ring/Silent switch set to silent. This category also prevents the render volume from being reduced below 1/16 (0.0625).
A possible workaround is to observe changes in AVAudioSession.outputVolume being set by the user and then mute your game audio output based on the observed value. Example implementations of this workaround can be found in the following sections.
Core
To implement a volume workaround in Core, refer to the following steps, which detail how to create an observer for the volume level in the native iOS code that will callback to a native method. In that callback, you can toggle the audio playback in your application.
Add the CoreVolumeObserver.m file
The CoreVolumeObserver.m
file contains a simple class that adds or removes an observer to the AVFoundation
.
The following code snippet shows the CoreVolumeObserver.m
file for reference, but it is strongly recommended that you use the downloadable version that is located at the end of this article to avoid copy-paste errors.
// File : CoreVolumeObserver.m
#include <AVFoundation/AVFoundation.h>
@interface CoreVolumeObserver : NSObject
+ (id)sharedIntance;
- (void)observeOutputVolumeChanges:(BOOL)addObserver;
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
@end
@implementation CoreVolumeObserver
static void *contextPointer;
+ (id)sharedIntance {
static CoreVolumeObserver *sharedCoreVolumeObserver = nil;
@synchronized(self) {
if (sharedCoreVolumeObserver == nil) {
sharedCoreVolumeObserver = [[self alloc] init];
contextPointer = &sharedCoreVolumeObserver;
}
}
return sharedCoreVolumeObserver;
}
- (void)observeOutputVolumeChanges:(BOOL)addObserver {
if(addObserver) {
[[AVAudioSession sharedInstance] addObserver:self
forKeyPath:@"outputVolume"
options:NSKeyValueObservingOptionNew
context:contextPointer];
} else {
[[AVAudioSession sharedInstance] removeObserver:self
forKeyPath:@"outputVolume"
context:contextPointer];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == contextPointer &&
[keyPath isEqualToString:@"outputVolume"]) {
if([AVAudioSession sharedInstance].outputVolume < 0.1) {
// Add code here to mute your game audio
} else {
// Add code here to unmute your game audio
}
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
@end
Modify the CoreVolumeObserver.m file
In the CoreVolumeObserver.m
file, it is your responsibility to add the appropriate code to mute and unmute your game audio. This code is not provided in the example because different customers might handle audio in different ways.
Start/stop the observer
Starting the volume observer requires getting a shared instance of the CoreVolumeObserver
and then calling the observeOutputVolumeChanges
method. The parameter indicates whether to add or remove the observer.
[[CoreVolumeObserver sharedIntance]
observeOutputVolumeChanges:TRUE];
Unity
To implement a volume workaround in Unity, refer to the following steps, which detail how to create an observer for the volume level in the native iOS code that will callback to a method attached to a Unity game object. In that callback, you can toggle the audio playback in your application.
Add the UnityVolumeObserver.m file
The UnityVolumeObserver.m
file must be placed in the Assets/Plugins/iOS
directory of your Unity project. If the directory does not exist, you need to create it.
The following code snippet shows the UnityVolumeObserver.m
file for reference, but it is strongly recommended that you use the downloadable version that is located at the end of this article to avoid copy-paste errors.
// File : UnityVolumeObserver.m
#include <AVFoundation/AVFoundation.h>
@interface UnityVolumeObserver : NSObject
+ (id)sharedIntance;
- (void)observeOutputVolumeChanges:(BOOL)addObserver;
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
@end
@implementation UnityVolumeObserver
static void *contextPointer;
+ (id)sharedIntance {
static UnityVolumeObserver *sharedUnityVolumeObserver = nil;
@synchronized(self) {
if (sharedUnityVolumeObserver == nil) {
sharedUnityVolumeObserver = [[self alloc] init];
contextPointer = &sharedUnityVolumeObserver;
}
}
return sharedUnityVolumeObserver;
}
- (void)observeOutputVolumeChanges:(BOOL)addObserver {
if(addObserver) {
[[AVAudioSession sharedInstance] addObserver:self
forKeyPath:@"outputVolume"
options:NSKeyValueObservingOptionNew
context:contextPointer];
} else {
[[AVAudioSession sharedInstance] removeObserver:self
forKeyPath:@"outputVolume"
context:contextPointer];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == contextPointer &&
[keyPath isEqualToString:@"outputVolume"]) {
// Replace the 1st parameter with the name of your game object.
UnitySendMessage("REPLACE WITH YOUR GAME OBJECT NAME",
"UnityVolumeObserverCallback",
([AVAudioSession sharedInstance].outputVolume < 0.1
? "MUTE" : "UNMUTE"));
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
@end
void ObserveOutputVolumeChanges(bool addObserver)
{
[[UnityVolumeObserver sharedIntance]
observeOutputVolumeChanges:addObserver];
}
Create the callback method
Create the method as shown in the following example code as part of a script that is attached to a game object. This is the method that is called from the native code when it observes the volume level being changed by the user. If the user has lowered the volume to the minimum, it receives the string "MUTE". Otherwise, it receives the string "UNMUTE".
It is your responsibility to add the appropriate code to mute and unmute your game audio. The code is not provided in the example because different customers might handle audio in different ways.
public void UnityVolumeObserverCallback(string muteState)
{
if(muteState == "MUTE")
{
// Add code here to mute your game audio
}
else
{
// Add code here to unmute your game audio
}
}
Modify the UnityVolumeObserver.m file
In the UnityVolumeObserver.m
file, replace the first parameter of the UnitySendMessage
call with the name of the game object to which the UnityVolumeObserverCallback
method has been added. This game object name must be unique.
For example, you have an Audio Source object called "MyAudioSource", you have added a script to that object, and you have added the UnityVolumeObserverCallback
method to that script. In this scenario, replace the first parameter of the UnitySendMessage
call with the string "MyAudioSource".
Start/stop the observer
Starting the volume observer requires calling the native function in the UnityVolumeObserver.m
file. Do this at a point where the game object in the last step exists and can receive method calls.
Add the following external function declaration to a script in your Unity project. This script could be the script that is attached to the game object, but it is not required.
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void ObserveOutputVolumeChanges(bool addObserver);
#endif
Add the following call to the same script to add the native volume observer. This could be part of any appropriate method (for example, Awake()
).
#if UNITY_IOS && !UNITY_EDITOR
ObserveOutputVolumeChanges(true);
#endif
Add the following call to the same script to remove the native volume observer. This could be part of any appropriate method (for example, OnDestroy()
).
#if UNITY_IOS && !UNITY_EDITOR
ObserveOutputVolumeChanges(false);
#endif
The following is an example of the three code snippets added to a script.
public class BackgroundScript : MonoBehaviour
{
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void ObserveOutputVolumeChanges(bool addObserver);
#endif
void Awake()
{
#if UNITY_IOS && !UNITY_EDITOR
ObserveOutputVolumeChanges(true);
#endif
}
void OnDestroy()
{
#if UNITY_IOS && !UNITY_EDITOR
ObserveOutputVolumeChanges(false);
#endif
}
public void UnityVolumeObserverCallback(string muteState)
{
if (muteState == "MUTE")
{
// Mute logic
}
else
{
// Unmute logic
}
}
}
Comments
0 comments
Article is closed for comments.