音频流式播放器AudioTrack配合WebSocket的完整案例

时间:2025-02-14 20:43:19
import android.annotation.SuppressLint; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.util.Log; import java.util.Arrays; import java.util.LinkedList; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; public class AudioTrackImpl implements AudioPlayerInterface { private static final String TAG = "AudioTrackImpl"; private AudioTrack audioTrack; private volatile boolean isPlaying = true; /** * 二进制数据流统计,当大于1时,通知View层可以播放数据。 */ private int mBytdDataCount = 0; /** * 服务端是否全部返回数据 */ private volatile boolean isWriteAudioFinish = false; private final BlockingDeque<byte[]> audioDataQueue = new LinkedBlockingDeque<>(); /** * 重新播放需要的数据 */ private final LinkedList<byte[]> mReplayData = new LinkedList<>(); private final PlayerListener mListener; public AudioTrackImpl(PlayerListener listener) { AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); AudioFormat audioFormat = new AudioFormat.Builder() .setSampleRate(AudioConfig.sampleRateInHz) .setEncoding(AudioConfig.audioFormat) .setChannelMask(AudioConfig.channelOutConfig) .build(); audioTrack = new AudioTrack(audioAttributes, audioFormat,AudioConfig.BUFFER_SIZE_IN_BYTES, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE); mListener = listener; } @Override public void writeAudioChunk(byte[] audioData) { try { if (mBytdDataCount == 0) { ThreadDispatcher.getInstance().postToMainThread(new Runnable() { @Override public void run() { mListener.onPlaybackStateChanged(PlayerListener.PLAYBACK_READY); } }); } mBytdDataCount++; audioDataQueue.put(audioData); mReplayData.add(audioData); } catch (InterruptedException e) { throw new RuntimeException(e); } } public void writeAudioFinish() { isWriteAudioFinish = true; } @Override public void clearAudioData() { audioDataQueue.clear(); mReplayData.clear(); mBytdDataCount = 0; } @Override public void playAudio() { isPlaying = true; mListener.onPlaybackStateChanged(PlayerListener.PLAYBACK_PLAYING); ThreadDispatcher.getInstance().runInBackground(this::playServer); } @SuppressLint("SuspiciousIndentation") private void playServer() { // 检查AudioTrack是否初始化成功 if (audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { audioTrack.play(); while (isPlaying) { try { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { byte[] audioData = audioDataQueue.take(); int writeResult = audioTrack.write(audioData, 0, audioData.length); if (writeResult < 0) { // 处理错误,丢掉这一块数据 continue; } addLostAudioData(audioData, writeResult); } if (audioDataQueue.isEmpty() && isWriteAudioFinish) { //注解1 isPlaying = false; isWriteAudioFinish = false; mListener.onPlaybackStateChanged(PlayerListener.PLAYBACK_FINISH); } } catch (Exception e) { e.printStackTrace(); break; } } } else { Log.e(TAG, "Initialization failed."); } } /** * 暂停时,未写入的数据会被丢弃。这样会导致一句话可能只说了一半,后面的都丢失。这里重新存储下被丢的数据 * @param audioData 待写入AudioTrack的二进制数组,可能会由于调用了pause,导致未写完。 * @param writeResult 已经写入AudioTrack的数据长度 */ private void addLostAudioData(byte[] audioData, int writeResult) { int audioDataLen = audioData.length; if (writeResult < audioDataLen) { byte[] subArray = Arrays.copyOfRange(audioData, writeResult, audioDataLen); audioDataQueue.addFirst(subArray); } } @Override public void replay() { mListener.onPlaybackStateChanged(PlayerListener.PLAYBACK_REPLAY); ThreadDispatcher.getInstance().runInBackground(this::realReplay); } private void realReplay() { try { audioDataQueue.clear(); for (byte[] mReplayDatum : mReplayData) { audioDataQueue.put(mReplayDatum); } isWriteAudioFinish = true; isPlaying = true; playServer(); } catch (InterruptedException e) { } } @Override public void pauseAudio() { if (audioTrack != null) { isPlaying = false; audioTrack.pause(); mListener.onPlaybackStateChanged(PlayerListener.PLAYBACK_PAUSE); } } @Override public void stopAudio() { if (audioTrack != null) { isPlaying = false; audioTrack.stop(); audioTrack.flush(); //释放音频缓存 clearAudioData(); } } @Override public boolean isPlaying() { return isPlaying; } @Override public void onDestroy() { // 确保在Activity销毁时释放资源 if (audioTrack != null) { isPlaying = false; audioTrack.flush(); audioTrack.stop(); //MODE_STREAM的stop()实际上将继续播放最后写入的缓冲区的剩余部分 audioTrack.release(); audioTrack = null; } clearAudioData(); } }