修复播报缺失文字的bug,改为“播放单个 -> 等待结束 -> 延迟 10ms秒 -> 播放下一个”的递归/循环模式

less 复制代码
```
 List<String> voicePlay = VoiceTextTemplate.genVoiceListByBean(builder);
                if (voicePlay == null || voicePlay.isEmpty()) {
                    return;
                }

//                Logger.d("================executeStart:getTransAmount::" + builder.getVoicePlayBean().getTransAmount());
                mExecutorService.execute(() -> startPlayAudio(voicePlay));
```
 /**
     * 开始播报 (支持间隔延迟)
     *
     * @param nameList 音频资源名称列表
     */
    private void startPlayAudio(final List<String> nameList) {
        if (nameList == null || nameList.isEmpty()) {
            return;
        }
        // 启动递归播放,从索引 0 开始
        playWithDelay(nameList, 0);
    }

    /**
     * 递归播放单个音频,结束后延迟再播下一个
     *
     * @param nameList 列表
     * @param index 当前播放索引
     */
    private void playWithDelay(final List<String> nameList, final int index) {
        // 终止条件:索引超出范围,表示播放完毕
        if (index >= nameList.size()) {
            return;
        }

        synchronized (VoicePlay.this) {
            final CountDownLatch mCountDownLatch = new CountDownLatch(1);
            SimpleExoPlayer player = ExplayerNetUtil.getInstance(mContext).getCurrentPlayer();

            // 1. 停止并清理之前的状态,防止重叠
            player.stop();
//            player.clearMediaSources();

            // 2. 获取当前音频资源
            String resourceName = nameList.get(index);
            int resId = mContext.getResources().getIdentifier(resourceName, "raw", mContext.getPackageName());

            if (resId == 0) {
                // 资源不存在,记录日志或直接跳过播放下一个
                Log.w("VoicePlay", "Resource not found: " + resourceName);
                // 直接递归调用下一个,不等待也不延迟(或者你也可以选择在这里 sleep)
                playWithDelay(nameList, index + 1);
                return;
            }

            Uri uri = getByUri(mContext, resId);

            // 3. 使用 Factory 构建 MediaSource (修复 deprecated 警告)
            MediaSource mediaSource = new ExtractorMediaSource.Factory(
                    ExplayerNetUtil.getInstance(mContext).getDataSourceFactory()
            )
                    .setExtractorsFactory(ExplayerNetUtil.getInstance(mContext).getDefaultExtractorsFactory())
                    .createMediaSource(uri);

            // 4. 配置播放器属性
            player.setAudioAttributes(
            new com.google.android.exoplayer2.audio.AudioAttributes.Builder()
                    .setContentType(C.CONTENT_TYPE_SPEECH)
                    .setUsage(C.USAGE_MEDIA)
                    .build()
//                    true // 处理音频焦点
            );
            player.setPlaybackParameters(new PlaybackParameters(1.0f));

            // 5. 添加监听器
            // 注意:这里每次都会 addListener,ExoPlayer 内部会管理,但最好确保逻辑简单
            player.addListener(new Player.EventListener() {
                @Override
                public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                    if (playbackState == Player.STATE_ENDED) {
                        mCountDownLatch.countDown();
                    }
                }

                @Override
                public void onPlayerError(ExoPlaybackException error) {
                    // 发生错误也释放锁,避免死锁,并尝试播放下一个
                    Log.e("VoicePlay", "Playback error", error);
                    mCountDownLatch.countDown();
                }

                // 其他回调省略...
                @Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {}
                @Override public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {}
                @Override public void onLoadingChanged(boolean isLoading) {}
                @Override public void onRepeatModeChanged(int repeatMode) {}
                @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {}
                @Override public void onPositionDiscontinuity(int reason) {}
                @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}
                @Override public void onSeekProcessed() {}
            });

            // 6. 准备并播放
            player.prepare(mediaSource);
            player.setPlayWhenReady(true);

            try {
                // 等待当前音频播放完成 (设置超时防止无限等待,例如 30 秒)
                boolean completed = mCountDownLatch.await(30, TimeUnit.SECONDS);

                if (completed) {
                    // 7. 【核心逻辑】播放完成后,强制休眠 10 毫秒 (10ms)
                    Thread.sleep(10);
                } else {
                    Log.w("VoicePlay", "Playback timed out for index: " + index);
                }

                // 8. 递归调用播放下一个
                playWithDelay(nameList, index + 1);

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                Log.e("VoicePlay", "Thread interrupted", e);
            }
        }
    }
相关推荐
颜酱2 小时前
提示词强化1:三个让大模型更「听话」的习惯
前端·javascript·人工智能
颜酱2 小时前
提示词强化 2:元提示(Meta-Prompt)与动态提示词
前端·javascript·人工智能
fengci.2 小时前
ctfshow其他(web408-web432)
android·开发语言·前端·学习·php
研☆香2 小时前
es6的一些更新特点介绍
前端·ecmascript·es6
hlvy2 小时前
Claude Code 太难看?我开源了一个 Web GUI
前端·ai·claude
颜酱2 小时前
提示词强化 3:JSON 与「流式」——前后端原理、BFF、以及两个示例页
前端·javascript·人工智能
蜡台2 小时前
VUE node EPERM: operation not permitted, unlink 错误
前端·javascript·vue.js
Wect2 小时前
深度解析前端性能优化
前端·面试·性能优化
|晴 天|2 小时前
AI智能助手功能实现
前端·vue.js·人工智能