Android SoundPool:在播放结束时获得通知

24

这听起来很简单,但我想不出为什么找不到答案,哈哈。

我有一个工作的声音池类(多亏了一些教程和我的调整),它运行得很好。

现在的问题是,我希望能够随机更改我的背景音乐。(不要一直循环播放相同的音乐,而是有两个或三个,当其中一个结束时播放另外两个之一)。

问题是我找不到一种方法来通知我音乐已经播放完毕。

有什么想法吗?

Jason

5个回答

19

这是我的做法:

启动时,我使用MediaPlayer获取每个声音点击的长度:

private long getSoundDuration(int rawId){
   MediaPlayer player = MediaPlayer.create(context, rawId);
   int duration = player.getDuration();
   return duration;
}

并将声音和持续时间一起存储在DTO类型对象中。


5
谢谢;+1。请注意在返回之前也要调用player.release()。 - Dan Breslau
谢谢!运行得很好! - Toni Alvarez
也许通过一些离线工具(如ffmpeg)导出音频的持续时间,并在应用程序启动时创建查找表会是一个不错的主意。据我所知,创建MediaPlayer将涉及意外的开销。 - jayatubi

14

据我所知,使用SoundPool无法完成这个操作。

我所知道的唯一能够提供播放完成通知的音频‘播放器’是MediaPlayer。它比SoundPool更加复杂,但允许设置OnCompletionListener以在播放完成时得到通知。


1
这是我得出的结论,但我想确认一下。我肯定需要使用媒体播放器,因为SoundPool无法处理大声音(据我所读,超过100ko)。谢谢。 - Jason Rogers
你说的 ko 是什么意思? - Daniel Gomez Rico
1
@DanielG.R.:我怀疑Jason Rogers只是指100k(即100 KB),但键盘上的字母“o”靠近字母“k”,他可能是不小心按到了它。 - Squonk
@JasonRogers SoundPool 可以处理未压缩流大小达到 1 MB 或 1 MiB 的文件。 - Smartybartfast

7

我有100多个短音频剪辑,SoundPool是最好的选择。我想在一个剪辑播放完后立即播放另一个剪辑。由于找不到与onCompletionListener()等效的方法,我选择实现一个runnable。这对我很有效,因为第一个声音持续1到2秒,所以我将runnable的持续时间设置为2000。希望他们改进这个类,因为它有很多潜力!


如果需要暂停但其持续时间并不重要,那么这是一个不错的选择。 - 18446744073709551615

6

与 SoundPool 相比,MediaPlayer 更加沉重和缓慢,但是 SoundPool 没有 setOnCompletionListener 。为了解决这个问题,我使用了一个自定义类来扩展 SoundPool 并添加了 setOnCompletionListener。可在 此处 找到该自定义类。

用法:与 MediaPlayer 类似。

创建:

SoundPoolPlayer mPlayer = SoundPoolPlayer.create(context, resId);
mPlayer.setOnCompletionListener(
    new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {  //mp will be null here
            Log.d("debug", "completed");
        }
    };
);
mPlayer.play();

暂停:

mPlayer.pause();

停止:

mPlayer.stop();

简历:

mPlayer.resume();

正在播放:

mPlayer.isPlaying();

欢迎任何拉取请求。这里我只实现了我需要的部分。


你会如何循环播放这个音频? - Rawnak Yazdani

2

我曾经遇到过类似的问题,并创建了一个SoundPool队列,将要播放的声音入队并在每个声音完成播放时发出通知。这是Kotlin语言编写的,但应该很容易转换成Java。

class SoundPoolQueue(private val context: Context, maxStreams: Int) {
    companion object {
        private const val LOG_TAG = "SoundPoolQueue"
        private const val SOUND_POOL_HANDLER_THREAD_NAME = "SoundPoolQueueThread"
        private const val ACTION_PLAY_SOUND = 1

        @JvmStatic
        fun getSoundDuration(context: Context, soundResId: Int) : Long {
            val assetsFileDescriptor = context.resources.openRawResourceFd(soundResId)
            val mediaMetadataRetriever = MediaMetadataRetriever()

            mediaMetadataRetriever.setDataSource(
                    assetsFileDescriptor.fileDescriptor,
                    assetsFileDescriptor.startOffset,
                    assetsFileDescriptor.length)

            val durationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
            val duration = durationString.toLong()

            logDebug("SoundPoolQueue::getSoundDuration(): Sound duration millis: $durationString")

            assetsFileDescriptor.close()
            return duration
        }

        @JvmStatic
        private fun logDebug(message: String) {
            if(!BuildConfig.DEBUG) {
                return
            }

            Log.d(LOG_TAG, message)
        }
    }

    private var soundPool = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val attrs = AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                .build()

        SoundPool.Builder()
                .setMaxStreams(maxStreams)
                .setAudioAttributes(attrs)
                .build()
    }
    else {
        @Suppress("DEPRECATION")
        SoundPool(maxStreams, AudioManager.STREAM_NOTIFICATION, 0)
    }

    var soundPoolQueueListener : SoundPoolQueueListener? = null

    private val soundPoolHandlerThread = SoundPoolQueueThread().apply { start() }
    private val soundPoolSoundsSparseArray = SparseArray<SoundPoolSound>()
    private val soundPoolSoundsQueue = LinkedList<SoundPoolSound>()

    fun addSound(soundResId: Int, leftVolume: Float, rightVolume: Float, priority: Int, loop: Boolean, rate: Float) {
        val durationMillis = getSoundDuration(context = context, soundResId = soundResId)
        val soundId = soundPool.load(context, soundResId, priority)

        soundPoolSoundsSparseArray.put(soundResId,
                SoundPoolSound(durationMillis, soundResId, soundId, leftVolume, rightVolume, priority, loop, rate))
    }

    fun playSound(soundResId: Int) {
        logDebug("SoundPoolQueue::playSound()")
        soundPoolSoundsQueue.add(soundPoolSoundsSparseArray[soundResId])
        soundPoolHandlerThread.handler?.sendEmptyMessage(ACTION_PLAY_SOUND)
    }

    inner class SoundPoolQueueThread : HandlerThread(SOUND_POOL_HANDLER_THREAD_NAME) {
        var handler: Handler? = null

        override fun onLooperPrepared() {
            super.onLooperPrepared()

            handler = object : Handler(looper) {
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)

                    if(msg.what == ACTION_PLAY_SOUND && handler!!.hasMessages(ACTION_PLAY_SOUND)) {
                        return
                    }

                    if(soundPoolSoundsQueue.isEmpty()) {
                        logDebug("SoundPoolHandlerThread: queue is empty.")
                        handler!!.removeMessages(ACTION_PLAY_SOUND)
                        return
                    }

                    logDebug("SoundPoolHandlerThread: Playing sound!")
                    logDebug("SoundPoolHandlerThread: ${soundPoolSoundsQueue.size} sounds left for playing.")

                    val soundPoolSound = soundPoolSoundsQueue.pop()
                    soundPool.play(soundPoolSound.soundPoolSoundId,
                            soundPoolSound.leftVolume,
                            soundPoolSound.rightVolume,
                            soundPoolSound.priority,
                            if(soundPoolSound.loop) { 1 } else { 0 },
                            soundPoolSound.rate)

                    try {
                        Thread.sleep(soundPoolSound.duration)
                    }
                    catch (ex: InterruptedException) { }

                    //soundPoolQueueListener?.onSoundPlaybackCompleted(soundPoolSound.soundResId)
                    sendEmptyMessage(0)
                }
            }
        }
    }

    interface SoundPoolQueueListener {
        fun onSoundPlaybackCompleted(soundResId: Int)
    }
}

伴随的数据类
data class SoundPoolSound(val duration: Long,
                      val soundResId: Int,
                      val soundPoolSoundId: Int,
                      val leftVolume: Float,
                      val rightVolume: Float,
                      val priority: Int,
                      val loop: Boolean,
                      val rate: Float)

当声音播放完成时,您将收到通知。

onSoundPlaybackCompleted(soundResId: Int)

带有已完成播放声音的资源ID。

使用示例:

 private class SoundPoolRunnable implements Runnable {
        @Override
        public void run() {
            LogUtils.debug(SerializableNames.LOG_TAG, "SoundPoolRunnable:run(): Initializing sounds!");

            m_soundPoolPlayer = new SoundPoolQueue(GSMSignalMonitorApp.this, 1);
            m_soundPoolPlayer.setSoundPoolQueueListener(new SoundPoolQueue.SoundPoolQueueListener() {
                @Override
                public void onSoundPlaybackCompleted(int soundResId)
                {
                    LogUtils.debug(SerializableNames.LOG_TAG, "onSoundPlaybackCompleted() " + soundResId);
                }
            });

            m_soundPoolPlayer.addSound(R.raw.gsm_signal_lost, 0.2f, 0.2f, 1, false, 1.0f);
            m_soundPoolPlayer.addSound(R.raw.gsm_signal_restored, 0.2f, 0.2f, 1, false, 1.0f);
            m_soundPoolPlayer.addSound(R.raw.gsm_signal_low, 0.2f, 0.2f, 1, false, 1.0f);

            m_soundPoolPlayer.addSound(R.raw.gsm_signal_lost_ru, 0.2f, 0.2f, 1, false, 1.0f);
            m_soundPoolPlayer.addSound(R.raw.gsm_signal_restored_ru, 0.2f, 0.2f, 1, false, 1.0f);
            m_soundPoolPlayer.addSound(R.raw.gsm_signal_low_ru, 0.2f, 0.2f, 1, false, 1.0f);
        }
    }

希望这能帮到你 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接