Android 9 system source code_audio management (1) key sound effect source code analysis

Preface

When the user clicks the button of the Android smart device, if it is accompanied by a key sound effect, it will give the user a better interactive experience. In this issue, we will combine the Android system source code to specifically analyze how the control emits key sound effects.

1. The system loads key sound effect resources

1. In the TV version of Android smart devices, we can control the presence or absence of button sound effects by adjusting the switch on the settings page. The system source code corresponding to the settings page is as follows.

packages/apps/TvSettings/Settings/src/com/android/tv/settings/device/sound/SoundFragment.java

public class SoundFragment extends PreferenceControllerFragment implements
        Preference.OnPreferenceChangeListener {
    
    

    private AudioManager mAudioManager;
    private Map<Integer, Boolean> mFormats;

    public static SoundFragment newInstance() {
    
    
        return new SoundFragment();
    }

    @Override
    public void onAttach(Context context) {
    
    
        mAudioManager = context.getSystemService(AudioManager.class);
        mFormats = mAudioManager.getSurroundFormats();
        super.onAttach(context);
    }
    
	//用户的点击行为首先触发此方法
    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
    
    
        if (TextUtils.equals(preference.getKey(), KEY_SOUND_EFFECTS)) {
    
    
            final TwoStatePreference soundPref = (TwoStatePreference) preference;
            //调用setSoundEffectsEnabled来设置按键音的开启与关闭
            setSoundEffectsEnabled(soundPref.isChecked());
        }
        return super.onPreferenceTreeClick(preference);
    }

	//获取按键音效是否开启
    public static boolean getSoundEffectsEnabled(ContentResolver contentResolver) {
    
    
        return Settings.System.getInt(contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 1)
                != 0;
    }

	//设置是否开启按键音效
    private void setSoundEffectsEnabled(boolean enabled) {
    
    
        if (enabled) {
    
    
        	//如果开启按键音,则调用AudioManager的loadSoundEffects方法来加载按键音效资源
            mAudioManager.loadSoundEffects();
        } else {
    
    
            mAudioManager.unloadSoundEffects();
        }
        Settings.System.putInt(getActivity().getContentResolver(),
                Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);
    }

}

When we click the key sound effect switch button on the settings page, the setSoundEffectsEnabled method of SoundFragment will eventually be triggered. This method will determine whether the key sound is turned on. If it is turned on, the loadSoundEffects method of AudioManager will be called to load the key sound effect resource. Otherwise, the unloadSoundEffects method will be called to not load it. Sound effects resources.

2. The loadSoundEffects method of AudioManager is as follows.

frameworks/base/media/java/android/media/AudioManager.java

public class AudioManager {
    
    

    //获取AudioService的代理对象
    private static IAudioService getService()
    {
    
    
        if (sService != null) {
    
    
            return sService;
        }
        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
        sService = IAudioService.Stub.asInterface(b);
        return sService;
    }

    public void loadSoundEffects() {
    
    
        final IAudioService service = getService();
        try {
    
    
        	//调用AudioService服务的loadSoundEffects方法
            service.loadSoundEffects();
        } catch (RemoteException e) {
    
    
            throw e.rethrowFromSystemServer();
        }
    }
    
    public void unloadSoundEffects() {
    
    
        final IAudioService service = getService();
        try {
    
    
            service.unloadSoundEffects();
        } catch (RemoteException e) {
    
    
            throw e.rethrowFromSystemServer();
        }
    }
}

AudioManager first obtains the proxy object of the audio service AudioService through the getService method, and then calls the specific method of the object.

3. The loadSoundEffects method of AudioService is as follows.

public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
    
    
       
     private AudioHandler mAudioHandler;

	 //加载音效资源
     public boolean loadSoundEffects() {
    
    
        int attempts = 3;
        LoadSoundEffectReply reply = new LoadSoundEffectReply();

        synchronized (reply) {
    
    
        	//调用sendMsg发送消息给mAudioHandler。
            sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
            while ((reply.mStatus == 1) && (attempts-- > 0)) {
    
    
                try {
    
    
                    reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
                } catch (InterruptedException e) {
    
    
                    Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
                }
            }
        }
        return (reply.mStatus == 0);
    }
    
    //不加载音效资源
    public void unloadSoundEffects() {
    
    
        sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0);
    }

    private static void sendMsg(Handler handler, int msg,
            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
    
    

        if (existingMsgPolicy == SENDMSG_REPLACE) {
    
    
            handler.removeMessages(msg);
        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
    
    
            return;
        }
        synchronized (mLastDeviceConnectMsgTime) {
    
    
            long time = SystemClock.uptimeMillis() + delay;

            if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
                msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
                msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
                msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
                msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
                msg == MSG_BTA2DP_DOCK_TIMEOUT) {
    
    
                if (mLastDeviceConnectMsgTime >= time) {
    
    
                  // add a little delay to make sure messages are ordered as expected
                  time = mLastDeviceConnectMsgTime + 30;
                }
                mLastDeviceConnectMsgTime = time;
            }
			//调用handler的sendMessageAtTime方法
            handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
        }
    }
    
    private class AudioHandler extends Handler {
    
    
    		//加载音效
            private boolean onLoadSoundEffects() {
    
    
            	...代码暂时省略...
            }
            @Override
        	public void handleMessage(Message msg) {
    
    
            switch (msg.what) {
    
    
            	...代码省略...
            	 case MSG_UNLOAD_SOUND_EFFECTS:
                    onUnloadSoundEffects();//不加载音效
                    break;
            	 case MSG_LOAD_SOUND_EFFECTS://加载音效
                    boolean loaded = onLoadSoundEffects();//调用onLoadSoundEffects加载音效,并将加载结果赋值给loaded
                    if (msg.obj != null) {
    
    
                        LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;
                        synchronized (reply) {
    
    
                            reply.mStatus = loaded ? 0 : -1;
                            reply.notify();
                        }
                    }
                    break;
              	...代码省略...
            }
    }
}

Here we only need to analyze the loadSoundEffects method of AudioService to load sound effect resources. This method will call sendMsg and send msg of type MSG_UNLOAD_SOUND_EFFECTS to mAudioHandler. Then the handleMessage method of AudioHandler will be further triggered, and the message will eventually trigger the onLoadSoundEffects method.

4. The onLoadSoundEffects method of AudioHandler is as follows.

public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
    
    
    //音效资源文件名称
    private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();
    
    private class AudioHandler extends Handler {
    
    
		//加载音效
        private boolean onLoadSoundEffects() {
    
    
            int status;
            synchronized (mSoundEffectsLock) {
    
    
            	//如果系统未启动完毕直接返回
                if (!mSystemReady) {
    
    
                    Log.w(TAG, "onLoadSoundEffects() called before boot complete");
                    return false;
                }
                //如果mSoundPool不为空直接返回
                if (mSoundPool != null) {
    
    
                    return true;
                }
                //加载触摸音效
                loadTouchSoundAssets();
   				...代码暂时省略...
        }
        
    //加载按键音效资源
    private void loadTouchSoundAssets() {
    
    
        XmlResourceParser parser = null;
        //如果音效资源文件列表不为空直接返回
        if (!SOUND_EFFECT_FILES.isEmpty()) {
    
    
            return;
        }
        //加载按键默认的音效资源
        loadTouchSoundAssetDefaults();
       ...代码省略...
	}
	
    private void loadTouchSoundAssetDefaults() {
    
    
    	//在类型为List<String>的SOUND_EFFECT_FILES中添加默认的按键音效资源Effect_Tick.ogg
        SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
        for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
    
    
            SOUND_EFFECT_FILES_MAP[i][0] = 0;
            SOUND_EFFECT_FILES_MAP[i][1] = -1;
        }
    }
}

The onLoadSoundEffects method first determines whether the system has been started, and returns directly if it has not been started; then it determines whether mSoundPool is empty, and returns directly if it is not empty; then it first calls a key method loadTouchSoundAssets, which first determines whether the
sound effect resource file list SOUND_EFFECT_FILES is Empty, return directly if not empty. If none of the above judgments are true, the loadTouchSoundAssetDefaults method will be called to load the default sound effect resource for the button. This method first adds the sound effect resource Effect_Tick.ogg to SOUND_EFFECT_FILES.

5. Continue to look at the onLoadSoundEffects method of AudioHandler.

public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
    
    
    //音效资源文件名称
    private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();
    
    private class AudioHandler extends Handler {
    
    
		//加载音效
        private boolean onLoadSoundEffects() {
    
    
            int status;

            synchronized (mSoundEffectsLock) {
    
    
                if (!mSystemReady) {
    
    
                    Log.w(TAG, "onLoadSoundEffects() called before boot complete");
                    return false;
                }

                if (mSoundPool != null) {
    
    
                    return true;
                }
                //加载触摸音效
                loadTouchSoundAssets();
                //创建SoundPool对象
                mSoundPool = new SoundPool.Builder()
                        .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
                        .setAudioAttributes(new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                            .build())
                        .build();
         		...代码省略...
                int numSamples = 0;
                for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
    
    
                    // Do not load sample if this effect uses the MediaPlayer
                    if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
    
    
                        continue;
                    }
                    if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
    
    
                        //获取音效资源文件路径
                        String filePath = getSoundEffectFilePath(effect);
                        //使用SoundPool加载音效资源文件
                        int sampleId = mSoundPool.load(filePath, 0);
                        if (sampleId <= 0) {
    
    
                            Log.w(TAG, "Soundpool could not load file: "+filePath);
                        } else {
    
    
                            SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
                            poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
                            numSamples++;
                        }
                    } else {
    
    
                        SOUND_EFFECT_FILES_MAP[effect][1] =
                                poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
                    }
                }
			...代码省略...
        }
        
        //获取音效资源文件路径,默认返回的音效资源文件路径为/system/media/audio/ui/Effect_Tick.ogg
        private String getSoundEffectFilePath(int effectType) {
    
    
            //  /product + /media/audio/ui/ + Effect_Tick.ogg
            String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH
                    + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
            if (!new File(filePath).isFile()) {
    
    
                //   /system + /media/audio/ui/ + Effect_Tick.ogg
                filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH
                        + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
            }
            return filePath;
        }
 	 }
 }

onLoadSoundEffects first calls the loadTouchSoundAssets method to load the default sound effect resource file name, then builds a SoundPool instance object, and then calls getSoundEffectFilePath to obtain the key sound effect resource file path. The default sound effect resource file path returned is /system/media/audio/ui/Effect_Tick.ogg. And call SoundPool to load the sound effect resource.

6. Briefly review the above steps.
System loads key sound effect resources

2. Click the control to play the sound effect resource

After the system turns on the key sound effect, when we click on any control, the key sound effect will be triggered. Next, we will combine the system source code of View to sort out the process.

1. When we click on a control, the performClick method of the View will first be triggered.

frameworks/base/core/java/android/view/View.java

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    
	
    public boolean performClick() {
    
    
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
    
    
            playSoundEffect(SoundEffectConstants.CLICK);//播放按键点击音效
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
    
    
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }
    
    public void playSoundEffect(int soundConstant) {
    
    
        //判断mAttachInfo.mRootCallbacks是否为空,以及系统是否开启了按键音效
        if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
    
    
            return;
        }
        //调用mAttachInfo.mRootCallbacks的playSoundEffect方法
        mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
    }
 }

View's performClick method will call the playSoundEffect method. The playSoundEffect method first determines whether mAttachInfo.mRootCallbacks is empty and whether the system has turned on the key sound effect, and then calls the playSoundEffect method of mAttachInfo.mRootCallbacks. We know that WindowManager needs to use the ViewRootImpl class in the process of adding View to the window. For details, please refer to the article Android 9.0 System Source Code_Window Management (2) WindowManager's window management process .

2. mAttachInfo was initially created in the constructor of ViewRootImpl.

frameworks/base/core/java/android/view/ViewRootImpl.java

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    
    
        
    public ViewRootImpl(Context context, Display display) {
    
    
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this);
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        //创建AttachInfo对象,倒数第二个参数就是View的playSoundEffect方法所用到的回调对象
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
		...代码省略...
    }
}

3. After reading the construction method of ViewRootImpl, let’s look at the construction method of AttachInfo.

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    
        
    final static class AttachInfo {
    
    

		//关键回调接口
        interface Callbacks {
    
    
        	//播放音效
            void playSoundEffect(int effectId);
            boolean performHapticFeedback(int effectId, boolean always);
        }

        final Callbacks mRootCallbacks;

        AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
                Context context) {
    
    
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;//View的playSoundEffect方法所用到的回调对象就是这个
            mTreeObserver = new ViewTreeObserver(context);
        }
    }
 }

The last parameter of the AttachInfo construction method is critical, because this is the object called by View's playSoundEffect method. Combining the code of ViewRootImpl, we can know that ViewRootImpl implements this callback.

4. The playSoundEffect method of ViewRootImpl is as follows.

public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    
    
        
    public void playSoundEffect(int effectId) {
    
    
        checkThread();//检测是否是UI线程
        try {
    
    
            final AudioManager audioManager = getAudioManager();

            switch (effectId) {
    
    
                case SoundEffectConstants.CLICK://播放按键点击音效
                    audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
                    return;
                case SoundEffectConstants.NAVIGATION_DOWN:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
                    return;
                case SoundEffectConstants.NAVIGATION_LEFT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
                    return;
                case SoundEffectConstants.NAVIGATION_RIGHT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
                    return;
                case SoundEffectConstants.NAVIGATION_UP:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
                    return;
                default:
                    throw new IllegalArgumentException("unknown effect id " + effectId +
                            " not defined in " + SoundEffectConstants.class.getCanonicalName());
            }
        } catch (IllegalStateException e) {
    
    
            // Exception thrown by getAudioManager() when mView is null
            Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
            e.printStackTrace();
        }
    }
}

The playSoundEffect method of ViewRootImpl will first detect whether the current thread is a UI thread, and then determine which sound effect to play based on the effectId type passed in. Because the View's performClick method passes in SoundEffectConstants.CLICK, audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK) will be triggered.

4. The playSoundEffect method of AudioManager is as follows.

public class AudioManager {
    
    
    public void  playSoundEffect(int effectType) {
    
    
        //检测音效类型是否合规
        if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
    
    
            return;
        }
        //确定音效是否可用
        if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) {
    
    
            return;
        }
        //获取AudioService服务
        final IAudioService service = getService();
        try {
    
    
            //调用服务的playSoundEffect方法
            service.playSoundEffect(effectType);
        } catch (RemoteException e) {
    
    
            throw e.rethrowFromSystemServer();
        }
    }
    
    /**
     * Settings has an in memory cache, so this is fast.
     */
    private boolean querySoundEffectsEnabled(int user) {
    
    
        return Settings.System.getIntForUser(getContext().getContentResolver(),
                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
    }
}

AudioManager's playSoundEffect will do some verification. If the verification passes, it will obtain the AudioService service object and call the playSoundEffect method of the object to play the sound effect.

5. The codes related to AudioService and playSoundEffect are as follows.

public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
    
    
            
    /**
     * 播放音效
     * @param effectType
     */
    public void playSoundEffect(int effectType) {
    
    
        playSoundEffectVolume(effectType, -1.0f);
    }

    public void playSoundEffectVolume(int effectType, float volume) {
    
    
        // do not try to play the sound effect if the system stream is muted
        if (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) {
    
    
            return;
        }

        if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {
    
    
            Log.w(TAG, "AudioService effectType value " + effectType + " out of range");
            return;
        }
		//发送播放音效的消息给mAudioHandler
        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
                effectType, (int) (volume * 1000), null, 0);
    }
    
    private class AudioHandler extends Handler {
    
    
    		//加载音效
            private boolean onLoadSoundEffects() {
    
    
            	...代码暂时省略...
            }
            @Override
        	public void handleMessage(Message msg) {
    
    
            switch (msg.what) {
    
    
            	...代码省略...
            	 case MSG_UNLOAD_SOUND_EFFECTS:
                    onUnloadSoundEffects();//不加载音效
                    break;
            	 case MSG_LOAD_SOUND_EFFECTS://加载音效
	               boolean loaded = onLoadSoundEffects();//调用onLoadSoundEffects加载音效,并将加载结果赋值给loaded
            		...代码省略...
                    break;
                case MSG_PLAY_SOUND_EFFECT://播放音效
                    onPlaySoundEffect(msg.arg1, msg.arg2);
                    break;
              	...代码省略...
            }
    }
}

The playSoundEffect method of AudioService further calls playSoundEffectVolume, which sends the message MSG_PLAY_SOUND_EFFECT to play the sound effect to mAudioHandler, and finally triggers the onPlaySoundEffect method.

6. The onPlaySoundEffec method of AudioService is as follows.

public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
    
    
            
        private void onPlaySoundEffect(int effectType, int volume) {
    
    
            synchronized (mSoundEffectsLock) {
    
    

                onLoadSoundEffects();//加载音效

                if (mSoundPool == null) {
    
    
                    return;
                }
                float volFloat;
                // use default if volume is not specified by caller
                if (volume < 0) {
    
    
                    volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
                } else {
    
    
                    volFloat = volume / 1000.0f;
                }

                if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
    
    
                	//通过SoundPool播放音效
                    mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
                                        volFloat, volFloat, 0, 0, 1.0f);
                } else {
    
    
                    //通过MediaPlayer播放音效
                    MediaPlayer mediaPlayer = new MediaPlayer();
                    try {
    
    
                        String filePath = getSoundEffectFilePath(effectType);
                        mediaPlayer.setDataSource(filePath);
                        mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                        mediaPlayer.prepare();
                        mediaPlayer.setVolume(volFloat);
                        mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
    
    
                            public void onCompletion(MediaPlayer mp) {
    
    
                                cleanupPlayer(mp);
                            }
                        });
                        mediaPlayer.setOnErrorListener(new OnErrorListener() {
    
    
                            public boolean onError(MediaPlayer mp, int what, int extra) {
    
    
                                cleanupPlayer(mp);
                                return true;
                            }
                        });
                        mediaPlayer.start();
                    } catch (IOException ex) {
    
    
                        Log.w(TAG, "MediaPlayer IOException: "+ex);
                    } catch (IllegalArgumentException ex) {
    
    
                        Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
                    } catch (IllegalStateException ex) {
    
    
                        Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
                    }
                }
            }
        }
   }

7. Briefly review the above steps.
Click the control to play the sound effect resource

Guess you like

Origin blog.csdn.net/abc6368765/article/details/132025465