前言
MediaPlayer 作为Android自带的Player目前还是存在很多不好使用问题,但实际开发中,还是有不少使用场景,本文针对多次seek产生杂音的问题进行分析讨论,自己遇到了进行记录,目前底层也不好解决和轻易改动原生代码,只能通过应用层兼容
1.为什么会产生杂音
从图中可以看出,是因为产生的两次start,那我们就得研究这两次start如何产生的
2.单次Seek场景
pause - seek - resume(恢复pause前记录的状态)
resume即代表了你之前播放就是继续播放,之前暂停则会继续暂停
3.多次seek的场景
setOnCompletionListener
这是我实现视频播放器多次seek的自定义日志
日志TAG解释
**pauseByEvent 主动暂停**
startTouch 开始按住(手势滑动 或者seekbar)
seek 拖动进度,或者手按住的时候抖动
stopTouch 松开 (手势滑动 或者seekbar)
**resumeByEvetn 主动**
seekComplete 监听seek结束
- 这里得解释下为什么会有pauseByEvent 主动暂停和resumeByEvetn 主动恢复,根据【2.单次Seek场景】,单次seek是自动会记录,暂停和恢复的,但是实际场景中,你在拖动过程中是不能有声音的,它单次seek完就出声了,显然是不满足需求的
问题最后一次就出现了回调了两个seekComplete,根据【2.单次Seek场景】,自然就出现了多次带间隔的pause和start杂音就产生了
4.溯本求源
/aosp13/frameworks/av/media/libmedia/mediaplayer.cpp
// cache duration
577 mCurrentPosition = msec;
578 mCurrentSeekMode = mode;
579 if (mSeekPosition < 0) {
580 mSeekPosition = msec;
581 mSeekMode = mode;
582 return mPlayer->seekTo(msec, mode);
---------------------------------------------------------
930 case MEDIA_SEEK_COMPLETE:
931 ALOGV("Received seek complete");
932 if (mSeekPosition != mCurrentPosition || (mSeekMode != mCurrentSeekMode)) {
933 ALOGV("Executing queued seekTo(%d, %d)", mCurrentPosition, mCurrentSeekMode);
934 mSeekPosition = -1;
935 mSeekMode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC;
936 seekTo_l(mCurrentPosition, mCurrentSeekMode);
937 }
938 else {
939 ALOGV("All seeks complete - return to regularly scheduled program");
940 mCurrentPosition = mSeekPosition = -1;
941 mCurrentSeekMode = mSeekMode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC;
942 }
943 break;
查看源码,我们可以看到无论多少次seek,cache最多有两个seek,mSeekPosition 小于0则不会执行seek,complete不相等则执行新的mSeekPosition 则不会为-1
5.记录seek缓存及其执行次数
根据log实际分析再结合源码,我们得到以下简单的工具类
/**
* @author rex
* @date 2023/6/9 17:13
* 解决多次seek杂音的问题
* seek 在mediaplayer中最多缓存两次seek(MAX_CACHE_SEEK),再松开seek后计数为0则适合resume-play
*/
object HandlerSeekNum {
const val TAG = "HandlerSeekNum"
const val MAX_CACHE_SEEK = 2
const val FINISH = 0
var isOnStartTrackingTouch = false
var seekNum = FINISH
fun reset() {
seekNum = FINISH
SLog.d(TAG, "reset 0")
}
fun add() {
seekNum++
if (seekNum > MAX_CACHE_SEEK) {
seekNum = MAX_CACHE_SEEK
}
SLog.d(TAG, "add $seekNum")
}
fun reduce() {
seekNum--
if (seekNum < FINISH) {
seekNum = FINISH
}
SLog.d(TAG, "reduce $seekNum")
}
fun isFinish(): Boolean {
return seekNum == FINISH
}
}
6.将工具类用于seek事件
[seekBar.setOnSeekBarChangeListener]
override fun onStartTrackingTouch(seekBar: SeekBar?) {
SLog.e(TAG, "onStartTrackingTouch")
isOnStartTrackingTouch = true
HandlerSeekNum.reset()
mOnOverlayCallback?.pauseByEvent()
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
isOnStartTrackingTouch = false
SLog.e(TAG, "onStopTrackingTouch")
mOnOverlayCallback?.seek(progress)
// 此处为seek完成的较快抬手回调马上结束了 此时也可以直接播放
if (HandlerSeekNum.isFinish()) {
mOnOverlayCallback?.resumeByEvent()
}
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
HandlerSeekNum.add()
SLog.i(TAG, "seek position $position")
PlayerManager.getInstance().seekTo(position)
}
7.监听seekComplete
[mediaPlayer.setOnSeekCompleteListener ]
1.判断seek彻底结束
2.手已经松开
3.恢复播放
setOnSeekCompleteListener {
SLog.d(TAG, "seekComplete:${player?.currentPosition}")
HandlerSeekNum.reduce()
if (HandlerSeekNum.isFinish() && !HandlerSeekNum.isOnStartTrackingTouch) {
PlayerManager.getInstance().resumeByEvent()
}
}