Android中播放音频六种方式_云梦九章的博客-程序员信息网_android 音频播放

技术标签: android  

一. MediaPlayer:

MediaPlayer确实强大,提供了对音频播放的各种控制,生命周期:
在这里插入图片描述

1. MediaPlayer支持:AAC、AMR、FLAC、MP3、MIDI、OGG、PCM等格式
2. 播放Raw下的元数据
mMediaPlayer=MediaPlayer.create(this, R.raw.audio);
mMediaPlayer.start();
3. MediaPlayer设置播放源的4中方式
  1. setDataSource (String path)
//从sd卡中加载音乐
mMediaPlayer.setDataSource("../music/samsara.mp3") ;
//从网路加载音乐
mMediaPlayer.setDataSource("http://..../xxx.mp3") ;
//需使用异步缓冲
mMediaPlayer.prepareAsync() ;
  1. setDataSource (FileDescriptor fd)
//需将资源文件放在assets文件夹
 AssetFileDescriptor fd = getAssets().openFd("samsara.mp3");
 mMediaPlayer.setDataSource(fd)
 mMediaPlayer.prepare() ;
 Ps:此方法系统需大于等于android 
  1. setDataSource (Context context, Uri uri)
    这个方法没什么好说的,一般通过ContentProvider获取Android系统提供的共享music获取uri,然后设置数据播放
  2. setDataSource (FileDescriptor fd, long offset, long length)
 //需将资源文件放在assets文件夹
 AssetFileDescriptor fd = getAssets().openFd("samsara.mp3");
 mMediaPlayer.setDataSource(fd, fd.getStartOffset(), fd.getLegth())
 mMediaPlayer.prepare() ;
注意点

设置完数据源,不要忘记prepare(),尽量使用异步prepareAync(),这样不会阻塞UI线程。
播放完毕即使释放资源

mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;

不足:资源占用量较高、延迟时间较长、不支持多个音频同时播放等

demo:

//创建MediaPlayer和设置监听
mPlayer = new MediaPlayer() ;
mSeekBar.setOnSeekBarChangeListener(new MySeekBarChangeListener());
mPlayer.setOnPreparedListener(new MyOnPrepareListener());
mPlayer.setOnCompletionListener(new MyOnCompletionListener());


/**
    * 从assets资源文件夹中播放
     * @param name
     */
    private void playSoundFromA(String name) {
    
        if (mPlayer.isPlaying()) {
    
            mPlayer.stop();
        }
        // 设置当前播放歌曲的名字
        title.setText(names[current]);
        mPlayer.reset();
        AssetFileDescriptor afd = getAssetFileDescriptor(name);
        try {
    
            mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            hasResource = true;
            mPlayer.prepareAsync();
        } catch (IOException e) {
    
            e.printStackTrace();
        }
    }

二. SoundPool播放音频

SoundPool支持多个音频文件同时播放(组合音频也是有上限的),延时短,比较适合短促、密集的场景,是游戏开发中音效播放的福音。

1. SoundPool实例化方式
  1. new SoundPool(适用与5.0以下)
SoundPool(int maxStreams, int streamType, int srcQuality)
从android5.0开始此方法被标记为过时,稍微说以下几个参数。
1.maxStreams :允许同时播放的流的最大值
2.streamType :音频流的类型描述,
               在Audiomanager中有种类型声明,游戏应用通常会使用流媒体音乐。
2. srcQuality:采样率转化质量
  1. SoundPool.Builder(从5.0开始支持)
//设置描述音频流信息的属性
AudioAttributes abs = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build() ;
SoundPool mSoundPoll =  new SoundPool.Builder()
                .setMaxStreams(100)   //设置允许同时播放的流的最大值
                .setAudioAttributes(abs)   //完全可以设置为null
                .build() ;
  1. 重要的方法
// 几个load方法和上文提到的MediaPlayer基本一致,不做多的解释
//------------------------------------------------------------

int load(AssetFileDescriptor afd, int priority)

int load(Context context, int resId, int priority)

int load(String path, int priority)

int load(FileDescriptor fd, long offset, long length, int priority)

//-------------------------------------------------------------

// 通过流id暂停播放
final void pause(int streamID)

// 播放声音,soundID:音频id; left/rightVolume:左右声道(默认1,1);loop:循环次数(-1无限循环);rate:播放速率(1为标准)
final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)

//释放资源(很重要)
final void release()

//恢复播放
final void resume(int streamID)

//设置指定id的音频循环播放次数
final void setLoop(int streamID, int loop)

//设置加载监听(因为加载是异步的,需要监听加载,完成后再播放)
void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)

//设置优先级(同时播放个数超过最大值时,优先级低的先被移除)
final void setPriority(int streamID, int priority)

//设置指定音频的播放速率,0.5~2.0(rate>1:加快播放,反之慢速播放)
final void setRate(int streamID, float rate)

//停止指定音频播放
final void stop(int streamID)

//卸载指定音频
final boolean unload(int soundID)

//暂停所有音频的播放
final void autoPause()

//恢复所有暂停的音频播放
final void autoResum()

以上方法基本上是SoundPool的所有方法了,也都很常用。

  1. 2个概念
    看了Sounpool的api,是不是感觉对 streamID 和 soundID 一脸懵逼?
    1. soundID:加载音乐资源时的返回值,int load(String path, int priority),这个int返回值就是soundID
    2. streamID:播放时返回的值,即play()方法的返回值
  2. demo
注:我把SoundPool做了简单封装,SoundPoolUtil,会在文末上传,
有兴趣可下载看一下,时间比较急,还有很多不足的地方
//初始化SoundPool
if(Build.VERSION.SDK_INT >=  Build.VERSION_CODES.LOLLIPOP){
    
            AudioAttributes aab = new AudioAttributes.Builder()
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .build() ;
            mSoundPool = new SoundPool.Builder()
                    .setMaxStreams(10)
                    .setAudioAttributes(aab)
                    .build() ;
        }else{
    
            mSoundPool = new SoundPool(60, AudioManager.STREAM_MUSIC,8) ;
        }
        mSoundPool = new SoundPool(60, AudioManager.STREAM_MUSIC,8) ;
        //设置资源加载监听
        mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener());

//加载资源
/**
     * 加载指定路径列表的资源
     * @param map
     */
    public void loadR(Map<String, String> map){
    
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for(Map.Entry<String, String> entry : entries){
    
            String key = entry.getKey() ;
            if(checkSoundPool()){
    
                if(!idCache.containsKey(key)){
    
                    idCache.put(key, mSoundPool.load(entry.getValue(),1)) ;
                }
            }
        }
    }

    /**
     * 播放指定音频,并返用于停止、暂停、恢复的StreamId
     * @param name
     * @param times
     * @return
     */
    public int play(String name, int times){
    
        return this.play(name,1,1,1,times,1) ;
    }

三. AudioTrack播放音频

AudioTrack属于更偏底层的音频播放,MediaPlayerService的内部就是使用了AudioTrack。

AudioTrack用于单个音频播放和管理,相比于MediaPlayer具有:精炼、高效的优点。
更适合实时产生播放数据的情况,如加密的音频,
MediaPlayer是束手无策的,AudioTrack却可以。

AudioTrack用于播放PCM(PCM无压缩的音频格式)音乐流的回放,
如果需要播放其它格式音频,需要响应的解码器,
这也是AudioTrack用的比较少的原因,需要自己解码音频。

1. AudioTreack的2种播放模式

静态模式—static

静态的言下之意就是数据一次性交付给接收方。好处是简单高效,只需要进行一次操作就完成了数据的传递;缺点当然也很明显,对于数据量较大的音频回放,显然它是无法胜任的,因而通常只用于播放铃声、系统提醒等对内存小的操作

流模式streaming

流模式和网络上播放视频是类似的,即数据是按照一定规律不断地传递给接收方的。理论上它可用于任何音频播放的场景,不过我们一般在以下情况下采用:

音频文件过大

音频属性要求高,比如采样率高、深度大的数据

音频数据是实时产生的,这种情况就只能用流模式了

通过write(byte[], int, int), write(short[], int, int)
write(float[], int, int, int)等方法推送解码数据到AudioTrack

Demo

private void jetPlayStream(){
    
        new Thread(new Runnable() {
    
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void run() {
    
                // 获取最小缓冲区
                int bufSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
                // 实例化AudioTrack(设置缓冲区为最小缓冲区的2倍,至少要等于最小缓冲区)
                AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO,
                        AudioFormat.ENCODING_PCM_16BIT, bufSize*2, AudioTrack.MODE_STREAM);
                // 设置音量
                audioTrack.setVolume(2f) ;
                // 设置播放频率
                audioTrack.setPlaybackRate(10) ;
                audioTrack.play();
                // 获取音乐文件输入流
                InputStream is = getResources().openRawResource(R.raw.zbc);
                byte[] buffer = new byte[bufSize*2] ;
                int len ;
                try {
    
                    while((len=is.read(buffer,0,buffer.length)) != -1){
    
                        System.out.println("读取数据中...");
                        // 将读取的数据,写入Audiotrack
                        audioTrack.write(buffer,0,buffer.length) ;
                    }
                    is.close();
                } catch (Exception e) {
    
                    e.printStackTrace();
                }
            }
        }).start();
    }

四. AsyncPlayer播放音频

从名字就可看出AsyncPlayer属于异步播放器,官方给出的说明是:所有工作都在子线程进行,不影响调用线程任何操作。

AsyncPlayer就是对MediaPlayer的一次简单的封装,对MediaPlaer所有的操作都在新开线程中执行。

AsyncPlayer只适合简单的异步播放,不能控制进度,只能开始或停止播放。如果播放在此调用play()方法,AsyncPlayer会停止当前播放,开始新的播放。

五. JetPlayer播放音频

Jet是由OHA联盟成员SONiVOX开发的一个交互音乐引擎。其包括两部分:JET播放器和JET引擎。JET常用于控制游戏的声音特效,采用MIDI(Musical Instrument Digital Interface)格式。
获取实例

//获取JetPlayer播放器
JetPlayer mJet = JetPlayer.getJetPlayer() ;

重要方法

// 清空分段队列,并清除所有要进行播放的剪辑。
1. boolean clearQueue()  //每次播放前,记得做一次清空操作

// 加载jet文件的方法
2. boolean loadJetFile(String path)
   boolean loadJetFile(AssetFileDescriptor afd)

// 开始播放
3. boolean play()

// 暂停播放
4. boolean pause()

// 释放资源
5. void release()

// 指定jet队列的播放序列(调用play()前需要调用此方法)
6. boolean queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)

Demo

private void jetPlayer(){
    
        // 获取JetPlayer播放器
        JetPlayer mJet = JetPlayer.getJetPlayer() ;
        //清空播放队列
        mJet.clearQueue() ;
        //绑定事件监听
        mJet.setEventListener(new JetPlayer.OnJetEventListener() {
    
            //播放次数记录
            int playNum = 1 ;
            @Override
            public void onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value) {
    
                Log.i(TAG,"----->onJetEvent") ;
            }

            @Override
            public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {
    
                Log.i(TAG,"----->onJetUserIdUpdate") ;
            }

            @Override
            public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {
    
                Log.i(TAG,"----->onJetNumQueuedSegmentUpdate") ;
            }

            @Override
            public void onJetPauseUpdate(JetPlayer player, int paused) {
    
                Log.i(TAG,"----->onJetPauseUpdate") ;
                if(playNum == 2){
    
                    playNum = -1 ;
                    //释放资源,并关闭jet文件
                    player.release();
                    player.closeJetFile() ;
                }else{
    
                    playNum++ ;
                }
            }
        });
        //加载资源
        mJet.loadJetFile(getResources().openRawResourceFd(R.raw.level1)) ;
        byte sSegmentID = 0 ;
        //指定播放序列
        mJet.queueJetSegment(0, 0, 0, 0, 0, sSegmentID);
        mJet.queueJetSegment(1, 0, 1, 0, 0, sSegmentID);
        //开始播放
        mJet.play() ;
    }

六. Ringtone

Ringtone为铃声、通知和其他类似声音提供快速播放的方法,这里还不得不提到一个管理类”RingtoneManager”,提供系统铃声列表检索方法,并且,Ringtone实例需要从RingtoneManager获取。

获取实例

获取实例方法,均为RingtoneManager类提供

//通过铃声uri获取
static Ringtone getRingtone(Context context, Uri ringtoneUri)

//通过铃声检索位置获取
Ringtone getRingtone(int position)

其实,Rington这个类比较简单,只需要掌握,播放、停止(paly(),stop())等方法就可以了,而RingtoneManager却是比较重要的。
重要方法

1. // 两个构造方法
RingtoneManager(Activity activity)
RingtoneManager(Context context)

2. // 获取指定声音类型(铃声、通知、闹铃等)的默认声音的Uri
static Uri getDefaultUri(int type)

3. // 获取系统所有Ringtone的cursor
Cursor getCursor()

4. // 获取cursor指定位置的Ringtone uri
Uri getRingtoneUri(int position)

5. // 判断指定Uri是否为默认铃声
static boolean isDefault(Uri ringtoneUri)

6. //获取指定uri的所属类型
static int getDefaultType(Uri defaultRingtoneUri)

7. //将指定Uri设置为指定声音类型的默认声音
static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)

从api看,Ringtone和RingtoneManager还是比较简单的,不多做解释了,直接放上一段使用代码。

 /**
 * 播放来电铃声的默认音乐
*/
private void playRingtoneDefault(){
    
    Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) ;
    Ringtone mRingtone = RingtoneManager.getRingtone(this,uri);
    mRingtone.play();
}


/**
* 随机播放一个Ringtone(有可能是提示音、铃声等)
*/
private void ShufflePlayback(){
    
    RingtoneManager manager = new RingtoneManager(this) ;
    Cursor cursor = manager.getCursor();
    int count = cursor.getCount() ;
    int position = (int)(Math.random()*count) ;
    Ringtone mRingtone = manager.getRingtone(position) ;
    mRingtone.play();
    }

    //记得添加下面两个权限
    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

筛选方案:

对于延迟度要求不高,并且希望能够更全面的控制音乐的播放,MediaPlayer比较适合

声音短小,延迟度小,并且需要几种声音同时播放的场景,适合使用SoundPool

对于简单的播放,不需要复杂控制的播放,可以给使用AsyncPlayer,所有操作均在子线程不阻塞UI

播放大文件音乐,如WAV无损音频和PCM无压缩音频,可使用更底层的播放方式AudioTrack。它支持流式播放,可以读取(可来自本地和网络)音频流,却播放延迟较小。

ps:据我测试AudioTrack直接支持WAV和PCM,其他音频需要解码成PCM格式才能播放。(其他无损格式没有尝试,有兴趣可以使本文提供的例子测试一下)

.jet的音频比较少见(有的游戏中在使用),可使用专门的播放器JetPlayer播放

对于系统类声音的播放和操作,Ringtone更适合(主要是掌握好RingtoneManager)

android.media包中提供的播放音频的方式,远不止这些,本文只是参考api和其他大牛的博客做一些研究和记录,android.media种还有很多只是等着我们探索……

MediaPlay demo

  1. 接口api
import androidx.annotation.RawRes;
public interface IPlayerApi {
    
    /**
     * 加载媒体资源
     *
     * @param musiUrl
     */
    void loadMedia(String musiUrl, OnPrepareCompletedListener listener);

    /**
     * 加载元数据媒体资源
     *
     * @param musicRawId
     */
    void loadMedia(@RawRes int musicRawId, OnPrepareCompletedListener listener);

    /**
     * 释放资源
     */
    void release();

    /**
     * 判断是否在播放
     *
     * @return
     */
    boolean isPlaying();

    /**
     * 开始播放
     */
    void play();

    /**
     * 重置
     */
    void reset();

    /**
     * 暂停
     */
    void pause();

    /**
     * 滑动到某个位置
     */
    void seekTo(int position);
}
  1. 资源准备完成
public interface OnPrepareCompletedListener {
    
    void onComplete();
}
  1. 播放监听
public interface PlaybackInfoListener {
    
    void onTotalDuration(int duration);//总时长

    void onPositionChanged(int position);//当前时长进度

    void onStateChanged(int state);//记录当前的状态

    void onPlayCompleted();//播放完成回调
}
  1. mediaplay适配
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.RawRes;

import com.tuya.smart.api.MicroContext;

import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MediaPlayerAdpater implements IPlayerApi {
    
    public static int STATUS_PALYINGP = 0;//正在播放
    public static int STATUS_STOP = 1;//暂停播放
    public static int STATUS_RESET = 2;//重置
    public static int STATUS_PLAY_COMPLETE = 3;//播放完成
    public static int STATUS_PREPER_COMPLETE = 4;//媒体流装载完成
    public static int STATUS_PREPER_ING = 5;//媒体流加载中
    public static int STATUS_ERROR = -1;//错误

    public int PLAYBACK_POSITION_REFRESH_INTERVAL_MS = 500;
    private final String TAG = "MediaPlayerHolder";
    private MediaPlayer mMediaPlayer;
    /**
     * 开启线程
     */
    private ScheduledExecutorService mExecutor;
    private PlaybackInfoListener mPlaybackInfoListener;
    private Runnable mSeekbarPositionUpdateTask;
//    private String musiUrl;//音乐地址,可以是本地的音乐,可以是网络的音乐


    public void setmPlaybackInfoListener(PlaybackInfoListener mPlaybackInfoListener) {
    
        this.mPlaybackInfoListener = mPlaybackInfoListener;
    }


    /**
     * 初始化MediaPlayer
     */
    private void initializeMediaPlayer() {
    

        //注册,播放完成后的监听
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
    
                stopUpdatingCallbackWithPosition(true);
                if (mPlaybackInfoListener != null) {
    
                    mPlaybackInfoListener.onStateChanged(STATUS_PLAY_COMPLETE);
                    mPlaybackInfoListener.onPlayCompleted();
                }
            }
        });

        //监听媒体流是否装载完成
        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    
            @Override
            public void onPrepared(MediaPlayer mp) {
    
                medisaPreparedCompled();
                if (mOnPrepareCompletedListener != null) {
    
                    mOnPrepareCompletedListener.onComplete();
                }
            }
        });

        // 监听媒体错误信息
        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
    
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
    
                if (mPlaybackInfoListener != null) {
    
                    mPlaybackInfoListener.onStateChanged(STATUS_ERROR);
                }
                Log.d(TAG, "OnError - Error code: " + what + " Extra code: " + extra);
                switch (what) {
    
                    case -1004:
                        Log.d(TAG, "MEDIA_ERROR_IO");
                        break;
                    case -1007:
                        Log.d(TAG, "MEDIA_ERROR_MALFORMED");
                        break;
                    case 200:
                        Log.d(TAG, "MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK");
                        break;
                    case 100:
                        Log.d(TAG, "MEDIA_ERROR_SERVER_DIED");
                        break;
                    case -110:
                        Log.d(TAG, "MEDIA_ERROR_TIMED_OUT");
                        break;
                    case 1:
                        Log.d(TAG, "MEDIA_ERROR_UNKNOWN");
                        break;
                    case -1010:
                        Log.d(TAG, "MEDIA_ERROR_UNSUPPORTED");
                        break;
                }
                switch (extra) {
    
                    case 800:
                        Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING");
                        break;
                    case 702:
                        Log.d(TAG, "MEDIA_INFO_BUFFERING_END");
                        break;
                    case 701:
                        Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE");
                        break;
                    case 802:
                        Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE");
                        break;
                    case 801:
                        Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE");
                        break;
                    case 1:
                        Log.d(TAG, "MEDIA_INFO_UNKNOWN");
                        break;
                    case 3:
                        Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START");
                        break;
                    case 700:
                        Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING");
                        break;
                }
                return false;
            }
        });
    }

    private int mMusicRawId = 0;

    private String mMusicUrl = null;
    private OnPrepareCompletedListener mOnPrepareCompletedListener;

    /**
     * 加载媒体资源
     *
     * @param musiUrl String:音乐地址,可以是本地的音乐,可以是网络的音乐
     **/
    @Override
    public void loadMedia(String musiUrl, OnPrepareCompletedListener listener) {
    
        if (TextUtils.isEmpty(musiUrl)) {
    
            Log.i(TAG, "地址为空");
            return;
        }
        mOnPrepareCompletedListener = listener;
        mMusicUrl = musiUrl;

        if (mPlaybackInfoListener != null) {
    
            mPlaybackInfoListener.onStateChanged(STATUS_PREPER_ING);
        }

        mMediaPlayer = new MediaPlayer();
        initializeMediaPlayer();
        try {
    
            //防止再次添加进来出现崩溃信息
            mMediaPlayer.reset();
            mMediaPlayer.setDataSource(musiUrl);
            mMediaPlayer.prepareAsync();
        } catch (IOException e) {
    
            Log.e(TAG, e.getMessage().toString());
        }
    }

    /**
     * 加载媒体资源
     **/
    @Override
    public void loadMedia(@RawRes int musicRawId, OnPrepareCompletedListener listener) {
    
        try {
    
            AssetFileDescriptor afd = MicroContext.getApplication().getResources().openRawResourceFd(musicRawId);
            if (afd == null) {
    
                Log.e(TAG, "afd == null");
                return;
            }

            mOnPrepareCompletedListener = listener;

            if (mPlaybackInfoListener != null) {
    
                mPlaybackInfoListener.onStateChanged(STATUS_PREPER_ING);
            }

            mMediaPlayer = new MediaPlayer();
            initializeMediaPlayer();
            //防止再次添加进来出现崩溃信息
            mMediaPlayer.reset();
            mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            mMediaPlayer.prepareAsync();
        } catch (IOException e) {
    
            Log.e(TAG, e.getMessage().toString());
        }
    }


    /**
     * 释放媒体资源
     **/
    @Override
    public void release() {
    
        if (mMediaPlayer != null) {
    
            stopUpdatingCallbackWithPosition(false);
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            mMusicUrl = null;
            mMusicRawId = 0;
        }
    }

    /**
     * 判断是否正在播放
     **/
    @Override
    public boolean isPlaying() {
    
        if (mMediaPlayer != null) {
    
            return mMediaPlayer.isPlaying();
        }
        return false;
    }

    /**
     * 播放开始
     **/
    @Override
    public void play() {
    
        if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
    
            mMediaPlayer.start();
            if (mPlaybackInfoListener != null) {
    
                mPlaybackInfoListener.onStateChanged(STATUS_PALYINGP);
            }
            startUpdatingCallbackWithPosition();
        }
    }

    /**
     * 开启线程,获取当前播放的进度
     **/
    private void startUpdatingCallbackWithPosition() {
    
        if (mExecutor == null) {
    
            mExecutor = Executors.newSingleThreadScheduledExecutor();
        }
        if (mSeekbarPositionUpdateTask == null) {
    
            mSeekbarPositionUpdateTask = new Runnable() {
    
                @Override
                public void run() {
    
                    updateProgressCallbackTask();
                }
            };
        }

        mExecutor.scheduleAtFixedRate(
                mSeekbarPositionUpdateTask,
                0,
                PLAYBACK_POSITION_REFRESH_INTERVAL_MS,
                TimeUnit.MILLISECONDS
        );
    }

    @Override
    public void reset() {
    
        if (mMediaPlayer != null) {
    
            if (mMusicUrl != null) {
    
                loadMedia(mMusicUrl, mOnPrepareCompletedListener);
            } else {
    
                loadMedia(mMusicRawId, mOnPrepareCompletedListener);
            }
            if (mPlaybackInfoListener != null) {
    
                mPlaybackInfoListener.onStateChanged(STATUS_RESET);
            }
            stopUpdatingCallbackWithPosition(true);
        }
    }

    /**
     * 暂停
     **/
    @Override
    public void pause() {
    
        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
    
            mMediaPlayer.pause();
            if (mPlaybackInfoListener != null) {
    
                mPlaybackInfoListener.onStateChanged(STATUS_STOP);
            }
            stopUpdatingCallbackWithPosition(false);
        }
    }

    /**
     * 更新当前的进度
     **/
    private void updateProgressCallbackTask() {
    
        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
    
            try {
    
                int currentPosition = mMediaPlayer.getCurrentPosition();
                if (mPlaybackInfoListener != null) {
    
                    mPlaybackInfoListener.onPositionChanged(currentPosition);
                }
            } catch (Exception e) {
    
                e.printStackTrace();
            }
        }
    }

    /**
     * 加载完成回调:完成媒体流的装载
     **/
    private void medisaPreparedCompled() {
    
        int duration = mMediaPlayer.getDuration();//获取总时长
        if (mPlaybackInfoListener != null) {
    
            mPlaybackInfoListener.onTotalDuration(duration);
            mPlaybackInfoListener.onPositionChanged(0);
            mPlaybackInfoListener.onStateChanged(STATUS_PREPER_COMPLETE);
        }
    }

    /**
     * 滑动播放到某个位置
     **/
    @Override
    public void seekTo(int position) {
    
        if (mMediaPlayer != null) {
    
            mMediaPlayer.seekTo(position);
        }
    }

    /**
     * 播放完成回调监听
     **/
    private void stopUpdatingCallbackWithPosition(boolean resetUIPlaybackPosition) {
    
        if (mExecutor != null) {
    
            mExecutor.shutdownNow();
            mExecutor = null;
            mSeekbarPositionUpdateTask = null;
            if (resetUIPlaybackPosition && mPlaybackInfoListener != null) {
    
                mPlaybackInfoListener.onPositionChanged(0);
            }
        }
    }
}

参考:

  1. Android中播放音乐的几种方式
  2. Android多媒体开发(5)————利用Android AudioTrack播放mp3文件
  3. Android音频系统之AudioTrack(一)
  4. Android SoundPool 使用和封装
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36017059/article/details/114367925

智能推荐

uni-app修改页面背景色_人人都在发奋的博客-程序员信息网_uniapp设置背景色

全局背景颜色设置方式:在App.vue的style样式表中设置&lt;style lang="scss"&gt; page { background-color: #F0AD4E;}&lt;/style&gt;单页面背景色设置方式:对应页面中的style样式表中设置,且不能有scoped属性,如果需要使用带scoped属性的样式表,则重新创建一个样式表单独写背景色样式&lt;style lang="scss" scoped&gt;@import './home.scss';&lt;/

基于python实现多线程_LCY133的博客-程序员信息网

首先科普一个问题:多任务:两个事情同时执行;一个人同时执行两个事情的方式主要通过手头处理一件事情,另一件事情自动跑着,边扫地边用洗衣机洗衣服。或者根本用的不是一个器官……边跑步边听音乐。计算机处理两件事情,因为计算机处理事情用的是CPU,要处理事情需要CPU在两件事情间穿插,不停的切换此时要谈到CPU的执行任务方式:并发和并行并发:两件事情同时触发,穿插执行,此时的情况是CPU个数(几个核)&lt;任务个数,一个CPU穿插着执行几件事情并行:一个CPU只作一件事情本文通过python实现多线程,

SOA架构(文末附与微服务架构的区别)_光影和弦 一抹春色的博客-程序员信息网_soa架构

一 SOA架构1.1定义面向服务的架构(SOA):SOA 是一种架构风格,致力于将业务功能保持一致的服务(系统服务,应用服务,技术服务)作为设计、构建和编排组合业务流程以及解决方案的基本单元。1.2目的核心的关注点在于服务的业务内容以及内涵,面向服务的架构的真正的价值体现在当可重用的服务被灵活组合、编排在一起来构建敏捷的、灵活的业务流程,其中敏捷体现在服务可以快速调整,独立演化;灵活性体...

DRBD9和LINSTOR用户指南——使用DRBD——11.常见的管理任务——11.11。配置基于校验和的同步_allway2的博客-程序员信息网

11.11。配置基于校验和的同步默认情况下,不为资源启用基于校验和的同步。要启用它,请将以下行添加到您的资源配置中/etc/drbd.conf:resource &lt;resource&gt; net { csums-alg &lt;algorithm&gt;; } ...}&lt;algorithm&gt;可以是系统内核配置中内核加密API支持的任何...

传统的知识表示_刹那永恒HB的博客-程序员信息网

知识图谱定义知识图谱旨在从数据中识别、发现和推断事物与概念之间的复杂关系,是事物关系的可计算模型.知识图谱的构建涉及知识建莫、关系抽取、图存储、关系推理、实体融合等多方面的技术,而知识图谱 应用则涉及语义搜索、智能问答、语言理解、决策分析等多个领域.工业知识图谱应用(1) 可视化展示基于知识图谱,可以提供数据的可视化展示,比如通过一个设备编号, 知识图谱会呈现与之相关的状态信息、其他相关的设备和人员信息"通过知 识图谱的可视化展示把复杂的信息非常直观地呈现出来,使得人们对工业生 产整体关联的情况一目

5G NR - 总体架构与物理层_hzgao的博客-程序员信息网_nr架构

一 NR总体架构与功能划分 1.1 总体架构    NG-RAN节点包含两种类型:  l&nbsp; gNB:提供NR用户平面和控制平面协议和功能  l&nbsp; ng-eNB:提供E-UTRA用户平面和控制平面协议和功能  gNB与ng-eNB之间通过Xn接口连接,gNB/ng-eNB通过NG-C接口与AMF(Access and Mobility Management Function)连接,通过NG-U接口与UPF(User Plane Function)连接。  5G总体架构

随便推点

如何将电脑数据备份至NAS?_Noont的博客-程序员信息网

系统怕受到外界的破坏,如病毒,程序损坏等,造成开不了机,数据无法恢复。在这之前如果进行了备份,等于买了保险一样,出故障只需简单还原便可。说真话,我以前从来没用过这个傲梅备份,也就听说过非常方便好用。今天终于得以见到庐山真面目了。第一步应用中心下载、安装轻松备份软件(4.1系统)打开傲梅备份,选择你要备份的盘符,如我就选择了系统C盘。第二步就是备份到铁马威F4-421 NAS,这里需手动输...

oppo计算机的夜间模式,OPPO如何设置夜间护眼模式?OPPO手机护眼模式使用教程_姜东凯的博客-程序员信息网

手机已经是生活中必不可少的了,特别是在晚上,可玩多了会影响视力。虽然大部分的应用软件都有夜间模式,但是手机党们在退出应用时,往往被强光刺激,这对眼睛的伤害是非常大的。没想到R11已经自带夜间护眼模式喽,来看看吧~OPPO手机护眼模式使用教程全新夜间护眼,有效过滤蓝光,缓解眼部疲劳,三种护眼效果满足你的阅读需求,给你舒适的夜间阅读体验。以下是具体的操作方法:1、通过手机底部上划控制中心--点击“夜间...

【Java】Eclipse的左边菜单栏没有了_Sz刘欢欢的博客-程序员信息网_java左边的窗口不见了

【Java】Eclipse的左边菜单栏没有了恢复菜单栏具体操作:1最上边的菜单栏点击Window选项。2然后选中下面的show View选项。3最后打开Project Explorer。4左边的菜单栏就有了。(今天不小心把菜单栏弄没了,无奈)...

切换svn登录用户名和密码_YS_445463753的博客-程序员信息网_svn登录密码

1,当你登录svn服务器并保存好密码后,发现再一次登录会自动连接到服务器,不需要填写用户名和密码。如果你想要更换登录用户的话需要对SVN进行设置,在文件夹内右键选择:2,接着3,第三步4,修改信息后保存就可以了,还有一种办法就是清理登录权限信息,下次链接时会提示输入用户名和密码:5,好了,这就完成了!

挖洞技巧:支付漏洞之总结_hana-u的博客-程序员信息网_支付漏洞总结

文章目录前言0x01 修改支付价格0x02 修改支付状态0x03 修改购买数量0x04 修改附属值0x05 修改支付接口0x06 多重替换支付0x07 重复支付0x08 最小额支付0x09 值为最大值支付问题0x10 越权支付0x11 无限制试用0x12 修改优惠价前言支付漏洞一直以来就是高风险,对企业来说危害很大,对用户来说同样危害也大。就比如我用他人账户进行消费,这也属于支付漏洞中的越权问题。那么支付漏洞一般存在在哪些方面呢,根据名字就知道,凡是涉及购买、资金等方面的功能处就

Nginx + ModSecurity 配置安装_thlzjfefe的博客-程序员信息网

软件环境:centos7,nginx-1.14.1,modsec-3.0.3,ModSecurity-nginx.git软件环境安装#第一:准备编译和依赖环境 yum install gcc wget git geoip-devel libcurl-devel libxml2 libxml2-devel libgd-devel openssl-devel -y yum ...

推荐文章

热门文章

相关标签