fixed #17685: [android] Audio in game couldn't be mute while a ring or a call is coming (#17686)

* fixed #17685: [android] Audio in game couldn't be mute while a ring or a call is coming

* Updates comments in AudioEngine-inl.cpp and removes extra empty line in javaactivity-android.cpp

* Puts audio focus relative code to another java file named Cocos2dxAudioFocusManager.java
Renames setAudioFocusLost to setAudioFocus.

* Renames JNI function.

* Register audio focus in onResume and unregister it in onPause.
This commit is contained in:
James Chen 2017-04-13 10:44:08 +08:00 committed by minggo
parent 270bcb28a5
commit 57170200dd
15 changed files with 288 additions and 34 deletions

View File

@ -23,8 +23,6 @@
****************************************************************************/
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#define LOG_TAG "cocos2d-x debug info"
#include "audio/android/AudioEngine-inl.h"
#include <unistd.h>
@ -43,6 +41,7 @@
#include "base/CCEventDispatcher.h"
#include "base/CCEventType.h"
#include "base/CCEventListenerCustom.h"
#include "base/ccUTF8.h"
#include "platform/android/CCFileUtils-android.h"
#include "platform/android/jni/Java_org_cocos2dx_lib_Cocos2dxHelper.h"
@ -55,8 +54,14 @@
using namespace cocos2d;
using namespace cocos2d::experimental;
#define DELAY_TIME_TO_REMOVE 0.5f
// Audio focus values synchronized with which in cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java
static const int AUDIOFOCUS_GAIN = 0;
static const int AUDIOFOCUS_LOST = 1;
static const int AUDIOFOCUS_LOST_TRANSIENT = 2;
static const int AUDIOFOCUS_LOST_TRANSIENT_CAN_DUCK = 3;
static int __currentAudioFocus = AUDIOFOCUS_GAIN;
static AudioEngineImpl* __impl = nullptr;
class CallerThreadUtils : public ICallerThreadUtils
{
@ -117,6 +122,7 @@ AudioEngineImpl::AudioEngineImpl()
, _lazyInitLoop(true)
{
__callerThreadUtils.setCallerThreadId(std::this_thread::get_id());
__impl = this;
}
AudioEngineImpl::~AudioEngineImpl()
@ -145,6 +151,8 @@ AudioEngineImpl::~AudioEngineImpl()
{
Director::getInstance()->getEventDispatcher()->removeEventListener(_onResumeListener);
}
__impl = nullptr;
}
bool AudioEngineImpl::init()
@ -225,6 +233,14 @@ void AudioEngineImpl::onEnterForeground(EventCustom* event)
_urlAudioPlayersNeedResume.clear();
}
void AudioEngineImpl::setAudioFocusForAllPlayers(bool isFocus)
{
for (const auto& e : _audioPlayers)
{
e.second->setAudioFocus(isFocus);
}
}
int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume)
{
ALOGV("play2d, _audioPlayers.size=%d", (int)_audioPlayers.size());
@ -274,6 +290,7 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume
player->setLoop(loop);
player->setVolume(volume);
player->setAudioFocus(__currentAudioFocus == AUDIOFOCUS_GAIN);
player->play();
}
else
@ -439,4 +456,31 @@ void AudioEngineImpl::uncacheAll()
}
}
// It's invoked from javaactivity-android.cpp
void cocos_audioengine_focus_change(int focusChange)
{
if (focusChange < AUDIOFOCUS_GAIN || focusChange > AUDIOFOCUS_LOST_TRANSIENT_CAN_DUCK)
{
CCLOGERROR("cocos_audioengine_focus_change: unknown value: %d", focusChange);
return;
}
CCLOG("cocos_audioengine_focus_change: %d", focusChange);
__currentAudioFocus = focusChange;
if (__impl == nullptr)
{
CCLOGWARN("cocos_audioengine_focus_change: AudioEngineImpl isn't ready!");
return;
}
if (__currentAudioFocus == AUDIOFOCUS_GAIN)
{
__impl->setAudioFocusForAllPlayers(true);
}
else
{
__impl->setAudioFocusForAllPlayers(false);
}
}
#endif

View File

@ -72,6 +72,7 @@ public:
void uncacheAll();
void preload(const std::string& filePath, const std::function<void(bool)>& callback);
void setAudioFocusForAllPlayers(bool isFocus);
private:
void onEnterBackground(EventCustom* event);

View File

@ -68,6 +68,8 @@ public:
virtual float getVolume() const = 0;
virtual void setAudioFocus(bool isFocus) = 0;
virtual void setLoop(bool isLoop) = 0;
virtual bool isLoop() const = 0;

View File

@ -110,6 +110,11 @@ float PcmAudioPlayer::getVolume() const
return _track->getVolume();
}
void PcmAudioPlayer::setAudioFocus(bool isFocus)
{
_track->setAudioFocus(isFocus);
}
void PcmAudioPlayer::setLoop(bool isLoop)
{
_track->setLoop(isLoop);

View File

@ -62,6 +62,8 @@ public:
virtual float getVolume() const override;
virtual void setAudioFocus(bool isFocus) override;
virtual void setLoop(bool isLoop) override;
virtual bool isLoop() const override;

View File

@ -41,6 +41,7 @@ Track::Track(const PcmData &pcmData)
, _isVolumeDirty(true)
, _isLoop(false)
, _isInitialized(false)
, _isAudioFocus(true)
{
init(_pcmData.pcmBuffer->data(), _pcmData.numFrames, _pcmData.bitsPerSample / 8 * _pcmData.numChannels);
}
@ -52,7 +53,8 @@ Track::~Track()
gain_minifloat_packed_t Track::getVolumeLR()
{
gain_minifloat_t v = gain_from_float(_volume);
float volume = _isAudioFocus ? _volume : 0.0f;
gain_minifloat_t v = gain_from_float(volume);
return gain_minifloat_pack(v, v);
}
@ -83,6 +85,12 @@ float Track::getVolume() const
return _volume;
}
void Track::setAudioFocus(bool isFocus)
{
_isAudioFocus = isFocus;
setVolumeDirty(true);
}
void Track::setState(State state)
{
std::lock_guard<std::mutex> lk(_stateMutex);

View File

@ -62,6 +62,8 @@ public:
void setVolume(float volume);
float getVolume() const;
void setAudioFocus(bool isFocus);
bool setPosition(float pos);
float getPosition() const;
@ -96,6 +98,7 @@ private:
std::mutex _volumeDirtyMutex;
bool _isLoop;
bool _isInitialized;
bool _isAudioFocus;
friend class AudioMixerController;
};

View File

@ -60,7 +60,7 @@ UrlAudioPlayer::UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObjec
: _engineItf(engineItf), _outputMixObj(outputMixObject),
_callerThreadUtils(callerThreadUtils), _id(-1), _assetFd(nullptr),
_playObj(nullptr), _playItf(nullptr), _seekItf(nullptr), _volumeItf(nullptr),
_volume(0.0f), _duration(0.0f), _isLoop(false), _state(State::INVALID),
_volume(0.0f), _duration(0.0f), _isLoop(false), _isAudioFocus(true), _state(State::INVALID),
_playEventCallback(nullptr), _isDestroyed(std::make_shared<bool>(false))
{
std::call_once(__onceFlag, [](){
@ -215,16 +215,36 @@ void UrlAudioPlayer::play()
}
}
void UrlAudioPlayer::setVolume(float volume)
void UrlAudioPlayer::setVolumeToSLPlayer(float volume)
{
_volume = volume;
int dbVolume = 2000 * log10(volume);
if (dbVolume < SL_MILLIBEL_MIN)
{
dbVolume = SL_MILLIBEL_MIN;
}
SLresult r = (*_volumeItf)->SetVolumeLevel(_volumeItf, dbVolume);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setVolume %d failed", dbVolume);
SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setVolumeToSLPlayer %d failed", dbVolume);
}
void UrlAudioPlayer::setVolume(float volume)
{
_volume = volume;
if (_isAudioFocus)
{
setVolumeToSLPlayer(_volume);
}
}
float UrlAudioPlayer::getVolume() const
{
return _volume;
}
void UrlAudioPlayer::setAudioFocus(bool isFocus)
{
_isAudioFocus = isFocus;
float volume = _isAudioFocus ? _volume : 0.0f;
setVolumeToSLPlayer(volume);
}
float UrlAudioPlayer::getDuration() const
@ -362,11 +382,6 @@ void UrlAudioPlayer::rewind()
// Not supported currently. since cocos audio engine will new -> prepare -> play again.
}
float UrlAudioPlayer::getVolume() const
{
return _volume;
}
void UrlAudioPlayer::setLoop(bool isLoop)
{
_isLoop = isLoop;

View File

@ -68,6 +68,8 @@ public:
virtual float getVolume() const override;
virtual void setAudioFocus(bool isFocus) override;
virtual void setLoop(bool isLoop) override;
virtual bool isLoop() const override;
@ -97,6 +99,8 @@ private:
void playEventCallback(SLPlayItf caller, SLuint32 playEvent);
void setVolumeToSLPlayer(float volume);
private:
SLEngineItf _engineItf;
SLObjectItf _outputMixObj;
@ -115,6 +119,7 @@ private:
float _volume;
float _duration;
bool _isLoop;
bool _isAudioFocus;
State _state;
PlayEventCallback _playEventCallback;

View File

@ -33,16 +33,13 @@ import android.media.AudioManager;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager.OnActivityResultListener;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import org.cocos2dx.lib.Cocos2dxHelper.Cocos2dxHelperListener;
@ -74,7 +71,7 @@ public abstract class Cocos2dxActivity extends Activity implements Cocos2dxHelpe
public Cocos2dxGLSurfaceView getGLSurfaceView(){
return mGLSurfaceView;
}
public static Context getContext() {
return sContext;
}
@ -135,12 +132,13 @@ public abstract class Cocos2dxActivity extends Activity implements Cocos2dxHelpe
Window window = this.getWindow();
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
// Audio configuration
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
//native method,call GLViewImpl::getGLContextAttrs() to get the OpenGL ES context attributions
private static native int[] getGLContextAttrs();
// ===========================================================
// Getter & Setter
// ===========================================================
@ -153,6 +151,7 @@ public abstract class Cocos2dxActivity extends Activity implements Cocos2dxHelpe
protected void onResume() {
Log.d(TAG, "onResume()");
super.onResume();
Cocos2dxAudioFocusManager.registerAudioFocusListener(this);
this.hideVirtualButton();
resumeIfHasFocus();
}
@ -178,12 +177,14 @@ public abstract class Cocos2dxActivity extends Activity implements Cocos2dxHelpe
protected void onPause() {
Log.d(TAG, "onPause()");
super.onPause();
Cocos2dxAudioFocusManager.unregisterAudioFocusListener(this);
Cocos2dxHelper.onPause();
mGLSurfaceView.onPause();
}
@Override
protected void onDestroy() {
Cocos2dxAudioFocusManager.unregisterAudioFocusListener(this);
super.onDestroy();
}

View File

@ -0,0 +1,131 @@
/****************************************************************************
* Copyright (c) 2017 Chukong Technologies Inc.
*
* http://www.cocos2d-x.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import android.content.Context;
import android.media.AudioManager;
import android.util.Log;
class Cocos2dxAudioFocusManager {
private final static String TAG = "AudioFocusManager";
// Audio focus values synchronized with which in cocos/platform/android/javaactivity-android.cpp
private final static int AUDIOFOCUS_GAIN = 0;
private final static int AUDIOFOCUS_LOST = 1;
private final static int AUDIOFOCUS_LOST_TRANSIENT = 2;
private final static int AUDIOFOCUS_LOST_TRANSIENT_CAN_DUCK = 3;
private static AudioManager.OnAudioFocusChangeListener sAfChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "onAudioFocusChange: " + focusChange + ", thread: " + Thread.currentThread().getName());
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// Permanent loss of audio focus
// Pause playback immediately
Log.d(TAG, "Pause music by AUDIOFOCUS_LOSS");
Cocos2dxHelper.runOnGLThread(new Runnable() {
@Override
public void run() {
nativeOnAudioFocusChange(AUDIOFOCUS_LOST);
Cocos2dxHelper.setAudioFocus(false);
}
});
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
Log.d(TAG, "Pause music by AUDIOFOCUS_LOSS_TRANSILENT");
Cocos2dxHelper.runOnGLThread(new Runnable() {
@Override
public void run() {
nativeOnAudioFocusChange(AUDIOFOCUS_LOST_TRANSIENT);
Cocos2dxHelper.setAudioFocus(false);
}
});
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume, keep playing
Log.d(TAG, "Lower the volume, keep playing by AUDIOFOCUS_LOSS_TRANSILENT_CAN_DUCK");
Cocos2dxHelper.runOnGLThread(new Runnable() {
@Override
public void run() {
nativeOnAudioFocusChange(AUDIOFOCUS_LOST_TRANSIENT_CAN_DUCK);
Cocos2dxHelper.setAudioFocus(false);
}
});
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Your app has been granted audio focus again
// Raise volume to normal, restart playback if necessary
Log.d(TAG, "Resume music by AUDIOFOCUS_GAIN");
Cocos2dxHelper.runOnGLThread(new Runnable() {
@Override
public void run() {
nativeOnAudioFocusChange(AUDIOFOCUS_GAIN);
Cocos2dxHelper.setAudioFocus(true);
}
});
}
}
};
static boolean registerAudioFocusListener(Context context) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// Request audio focus for playback
int result = am.requestAudioFocus(sAfChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "requestAudioFocus succeed");
return true;
}
Log.e(TAG, "requestAudioFocus failed!");
return false;
}
static void unregisterAudioFocusListener(Context context) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int result = am.abandonAudioFocus(sAfChangeListener);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "abandonAudioFocus succeed!");
} else {
Log.e(TAG, "abandonAudioFocus failed!");
}
Cocos2dxHelper.runOnGLThread(new Runnable() {
@Override
public void run() {
Cocos2dxHelper.setAudioFocus(true);
nativeOnAudioFocusChange(AUDIOFOCUS_GAIN);
}
});
}
private static native void nativeOnAudioFocusChange(int focusChange);
}

View File

@ -47,7 +47,6 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.hardware.SensorManager;
import com.android.vending.expansion.zipfile.APKExpansionSupport;
import com.android.vending.expansion.zipfile.ZipResourceFile;
@ -438,6 +437,11 @@ public class Cocos2dxHelper {
Cocos2dxHelper.sCocos2dSound.stopAllEffects();
}
static void setAudioFocus(boolean isAudioFocus) {
sCocos2dMusic.setAudioFocus(isAudioFocus);
sCocos2dSound.setAudioFocus(isAudioFocus);
}
public static void end() {
Cocos2dxHelper.sCocos2dMusic.end();
Cocos2dxHelper.sCocos2dSound.end();

View File

@ -51,6 +51,7 @@ public class Cocos2dxMusic {
private boolean mPaused; // whether music is paused state.
private boolean mIsLoop = false;
private boolean mManualPaused = false; // whether music is paused manually before the program is switched to the background.
private boolean mIsAudioFocus = true;
private String mCurrentPath;
// ===========================================================
@ -226,7 +227,7 @@ public class Cocos2dxMusic {
}
this.mLeftVolume = this.mRightVolume = volume;
if (this.mBackgroundMediaPlayer != null) {
if (this.mBackgroundMediaPlayer != null && mIsAudioFocus) {
this.mBackgroundMediaPlayer.setVolume(this.mLeftVolume, this.mRightVolume);
}
}
@ -298,6 +299,16 @@ public class Cocos2dxMusic {
return mediaPlayer;
}
void setAudioFocus(boolean isFocus) {
mIsAudioFocus = isFocus;
if (mBackgroundMediaPlayer != null) {
float lVolume = mIsAudioFocus ? mLeftVolume : 0.0f;
float rVolume = mIsAudioFocus ? mRightVolume : 0.0f;
mBackgroundMediaPlayer.setVolume(lVolume, rVolume);
}
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================

View File

@ -52,6 +52,7 @@ public class Cocos2dxSound {
private SoundPool mSoundPool;
private float mLeftVolume;
private float mRightVolume;
private boolean mIsAudioFocus = true;
// sound path and stream ids map
// a file may be played many times at the same time
@ -255,6 +256,13 @@ public class Cocos2dxSound {
this.mLeftVolume = this.mRightVolume = volume;
if (!mIsAudioFocus)
return;
setEffectsVolumeInternal(mLeftVolume, mRightVolume);
}
private void setEffectsVolumeInternal(float left, float right) {
synchronized (mLockPathStreamIDsMap) {
// change the volume of playing sounds
if (!this.mPathStreamIDsMap.isEmpty()) {
@ -262,7 +270,7 @@ public class Cocos2dxSound {
while (iter.hasNext()) {
final Entry<String, ArrayList<Integer>> entry = iter.next();
for (final int steamID : entry.getValue()) {
this.mSoundPool.setVolume(steamID, this.mLeftVolume, this.mRightVolume);
this.mSoundPool.setVolume(steamID, left, right);
}
}
}
@ -346,6 +354,14 @@ public class Cocos2dxSound {
this.mSoundPool.autoResume();
}
void setAudioFocus(boolean isFocus) {
mIsAudioFocus = isFocus;
float leftVolume = mIsAudioFocus ? mLeftVolume : 0.0f;
float rightVolume = mIsAudioFocus ? mRightVolume : 0.0f;
setEffectsVolumeInternal(leftVolume, rightVolume);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================

View File

@ -46,6 +46,8 @@ THE SOFTWARE.
void cocos_android_app_init(JNIEnv* env) __attribute__((weak));
void cocos_audioengine_focus_change(int focusChange);
using namespace cocos2d;
extern "C"
@ -56,21 +58,20 @@ extern "C"
#if __ANDROID_API__ > 19
#include <signal.h>
#include <dlfcn.h>
typedef __sighandler_t (*bsd_signal_func_t)(int, __sighandler_t);
bsd_signal_func_t bsd_signal_func = NULL;
typedef __sighandler_t (*bsd_signal_func_t)(int, __sighandler_t);
bsd_signal_func_t bsd_signal_func = NULL;
__sighandler_t bsd_signal(int s, __sighandler_t f) {
if (bsd_signal_func == NULL) {
// For now (up to Android 7.0) this is always available
bsd_signal_func = (bsd_signal_func_t) dlsym(RTLD_DEFAULT, "bsd_signal");
__sighandler_t bsd_signal(int s, __sighandler_t f) {
if (bsd_signal_func == NULL) {
// For now (up to Android 7.0) this is always available
bsd_signal_func = (bsd_signal_func_t) dlsym(RTLD_DEFAULT, "bsd_signal");
if (bsd_signal_func == NULL) {
__android_log_assert("", "bsd_signal_wrapper", "bsd_signal symbol not found!");
}
if (bsd_signal_func == NULL) {
__android_log_assert("", "bsd_signal_wrapper", "bsd_signal symbol not found!");
}
}
return bsd_signal_func(s, f);
}
return bsd_signal_func(s, f);
}
#endif // __ANDROID_API__ > 19
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
@ -123,6 +124,11 @@ JNIEXPORT jintArray Java_org_cocos2dx_lib_Cocos2dxActivity_getGLContextAttrs(JNI
return glContextAttrsJava;
}
JNIEXPORT void Java_org_cocos2dx_lib_Cocos2dxAudioFocusManager_nativeOnAudioFocusChange(JNIEnv* env, jobject thiz, jint focusChange)
{
cocos_audioengine_focus_change(focusChange);
}
JNIEXPORT void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeOnSurfaceChanged(JNIEnv* env, jobject thiz, jint w, jint h)
{
cocos2d::Application::getInstance()->applicationScreenSizeChanged(w, h);