修复播报缺失文字的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);
            }
        }
    }
相关推荐
kyriewen5 分钟前
别再 console.log 了:5 个 Chrome DevTools 调试技巧,用过就回不去了
前端·javascript·面试
IT_陈寒2 小时前
Python搞不定字符串编码?这破玩意坑我两小时!
前端·人工智能·后端
DigitalOcean3 小时前
Laravel 开发者已在 DigitalOcean 上开通超过 10 万台服务器
前端·laravel
星始流年3 小时前
从 Tool 到 Skill——基于 LangChain 的服务端Skill实现
前端·langchain·agent
李惟3 小时前
开源本地通信库,纯客户端 RPC,像聊天一样通信
前端
YAwu113 小时前
深入解析 React 炫彩鼠标跟随标题组件:从坐标定位到动画性能
前端·react.js
GuWenyue3 小时前
排序效率低?5分钟吃透快速排序,性能飙升至O(nlogn)
前端·javascript·面试
OpenTiny社区3 小时前
🎨 看完 GenUI SDK 源码我悟了!
前端·vue.js·github
叁两4 小时前
前端转型AI Agent该如何学习?(前置篇)
前端·人工智能·node.js
何时梦醒4 小时前
深入理解递归与快速排序 —— 从基础入门到手写实现
前端·javascript