sourceBuffer failed to execute appendBuffer on sourceBuffer this sourcebuffer is still processing an appendbugger or remove operation, 这个报错

这个错误通常是由于在 SourceBuffer 还在处理前一个 appendBufferremove 操作时,又尝试执行另一个 appendBuffer 操作。这意味着在 appendBuffer 调用之间没有正确处理并等待前一个操作完成。

要解决这个问题,可以使用一个队列来存储要追加的数据块,并在前一个操作完成后再执行下一个操作。这是通过监听 sourceBufferupdateend 事件来实现的。

以下是一个更详细的示例,确保在一个操作完成后再进行下一个操作:

完整的前端代码示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                // 处理数据块追加的竞争问题
                let queue = [];
                let isAppending = false;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('/audio') // 替换为你的音频流URL
                    .then(response => response.body.getReader())
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

后端模拟(Java Servlet)

保持与之前的示例相同的后端代码,模拟每秒钟返回1KB数据:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        // 构建假头信息
        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        // 模拟每秒返回1KB数据
        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

解释

  1. 创建 MediaSourceSourceBuffer:在 sourceopen 事件中创建 SourceBuffer

  2. 队列和标志位:使用一个队列(queue)存储需要追加的数据块和一个标志位(isAppending)来表示当前是否有数据正在追加。

  3. 追加数据块:通过 appendNextChunk 函数检查队列并追加数据块。如果 SourceBuffer 正在处理另一个操作,则数据块将被添加到队列中,等待当前操作完成后再追加。

  4. 监听 updateend 事件:在 SourceBuffer 完成当前操作时触发 updateend 事件,清除标志位并调用 appendNextChunk 以继续处理队列中的下一个数据块。

  5. 处理错误:添加对 error 事件的监听,以捕获并记录 SourceBuffer 错误。

  6. 数据读取和追加:通过 fetchReadableStream API 从服务器获取音频流数据,并将其逐块添加到队列中进行处理。

这样可以确保 appendBuffer 操作之间不会发生竞争,从而避免 sourceBuffer 错误。

再检查一下前端代码是否正确,说fetchAudioData 找不到

我检查并修正了一些可能导致 fetchAudioData 函数找不到的问题,确保代码正确并且函数定义和调用正确。下面是更新后的完整前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                // 处理数据块追加的竞争问题
                let queue = [];
                let isAppending = false;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('/audio') // 替换为你的音频流URL
                    .then(response => response.body.getReader())
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

解释

  1. 确保 fetchAudioData 函数在 sourceopen 事件处理程序中正确调用

    • MediaSource 打开时,事件处理程序调用 fetchAudioData
  2. 定义 fetchAudioData 函数

    • fetchAudioData 函数中,使用 fetch API 获取音频数据流。
    • 使用 ReadableStreamgetReader 方法逐块读取数据,并将每块数据添加到队列中。
  3. 定义 appendNextChunk 函数

    • appendNextChunk 函数负责从队列中获取下一个数据块并追加到 sourceBuffer
    • 使用 isAppending 标志位防止在当前操作完成之前开始新的操作。
  4. 监听 updateend 事件

    • sourceBufferupdateend 事件触发时,表示当前操作完成,清除标志位并调用 appendNextChunk 处理下一个数据块。
  5. 监听 error 事件

    • 处理和记录 sourceBuffer 错误。

后端模拟(Java Servlet)

保持与之前的示例相同的后端代码,用于模拟每秒钟返回1KB数据:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        // 构建假头信息
        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        // 模拟每秒返回1KB数据
        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

请确保后端代码正确部署并运行,以提供所需的音频数据流。这将确保前端代码能够正确获取并处理音频数据,避免 appendBuffer 竞争问题。

Uncaught (in promise) ReferenceError: queue is not defined 报错,前端

对,之前的代码里,queueisAppending 的作用域不正确,导致在某些情况下找不到这些变量。我们需要确保这些变量在适当的作用域内声明。以下是修正后的前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                // 处理数据块追加的竞争问题
                var queue = [];
                var isAppending = false;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData(queue, appendNextChunk);
            });

            function fetchAudioData(queue, appendNextChunk) {
                fetch('/audio') // 替换为你的音频流URL
                    .then(response => response.body.getReader())
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

解释

  1. 变量声明作用域:确保 queueisAppendingsourceopen 事件处理程序的作用域中声明,这样它们可以在 appendNextChunkfetchAudioData 中正确引用。

  2. 参数传递:将 queueappendNextChunk 作为参数传递给 fetchAudioData 函数,以便在该函数内部可以访问它们。

  3. 错误处理:添加对 fetchSourceBuffer 的错误处理,以捕获并记录可能的错误。

  4. 重复读取数据:通过 read 函数不断从流中读取数据块,并将它们添加到队列中。

这种方式确保了变量的正确引用,并且能够避免 appendBuffer 操作之间的竞争。请确保后端代码正确部署并运行,以提供所需的音频数据流。

请求完毕后,前端点了播放,没有效果啊,而且请求的路径返回也是有数据的

为了确保前端代码能够正确处理音频流,并确保音频数据被成功附加到 SourceBuffer,以下是一些步骤和改进的前端代码示例,以确保音频数据能够正确播放:

  1. 确认音频格式:确保服务器返回的音频数据格式与 SourceBuffer 支持的格式一致。
  2. 检查数据类型:在 fetch 请求中检查返回的 MIME 类型是否正确。

以下是改进的前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                // 处理数据块追加的竞争问题
                var queue = [];
                var isAppending = false;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending && !sourceBuffer.updating) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('/audio') // 替换为你的音频流URL
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

进一步检查

  1. 音频流URL:确保 /audio URL 返回的是有效的音频数据,且 MIME 类型为 audio/mpeg
  2. 检查控制台日志:查看浏览器控制台日志,确认是否有其他错误信息。

后端模拟(Java Servlet)

确保后端代码提供有效的音频流数据。这里是一个简化的示例:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        // 构建假头信息
        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        // 模拟每秒返回1KB数据
        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

请确保后端返回的数据能够被浏览器正确识别并播放。如果仍然无法播放,请检查网络请求的响应头、响应状态以及浏览器控制台中的详细错误信息。

那么我如何在流稍微达到一定程度哪怕2s ,前端播放器就能播放的效果呢,

为了实现前端播放器在流达到一定程度(例如2秒)后就能开始播放的效果,可以通过以下步骤进行优化:

  1. 适当的缓冲大小:确保在前端接收到足够的数据块(例如2秒的音频数据)后再开始播放。
  2. 使用适当的事件监听:通过事件监听器来确保在接收到足够的数据后立即开始播放。

以下是更新的前端代码示例,确保在接收到2秒的音频数据后开始播放:

前端代码


html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                // 处理数据块追加的竞争问题
                var queue = [];
                var isAppending = false;
                var totalBufferedDuration = 0; // 总缓冲的时长
                var requiredBufferDuration = 2; // 需要的缓冲时长(秒)

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending && !sourceBuffer.updating) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    totalBufferedDuration = sourceBuffer.buffered.end(0); // 更新总缓冲的时长

                    if (totalBufferedDuration >= requiredBufferDuration && audio.paused) {
                        audio.play(); // 在达到所需缓冲时长后开始播放
                    }
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('/audio') // 替换为你的音频流URL
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

解释

  1. 总缓冲时长计算

    • 使用 sourceBuffer.buffered.end(0) 获取当前缓冲区的结束时间。
    • updateend 事件触发时更新 totalBufferedDuration
  2. 检查缓冲时长并开始播放

    • totalBufferedDuration 大于或等于所需的缓冲时长(例如2秒)时,检查 audio 是否处于暂停状态。
    • 如果处于暂停状态且达到所需缓冲时长,调用 audio.play() 开始播放。
  3. 事件监听和错误处理

    • 监听 sourceopenupdateenderror 事件,确保在适当的时机执行相应的操作,并处理可能的错误。

后端模拟(Java Servlet)

保持与之前的示例相同的后端代码,用于模拟每秒钟返回1KB数据:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        // 构建假头信息
        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        // 模拟每秒返回1KB数据
        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

这样,前端代码会在音频缓冲达到所需的时长(例如2秒)后自动开始播放音频,确保用户体验的平滑性。

你上面的代码 点击播放 没有任何效果,我直接一个audio src标签的时候是确定能播放的

为了确保播放器能够在接收到足够的数据后开始播放,我们需要确保以下几点:

  1. 正确的MIME类型:服务器返回的音频数据必须与SourceBuffer支持的MIME类型匹配。
  2. 足够的缓冲:确保在appendBuffer中没有数据竞争,足够的数据被添加到SourceBuffer

让我们详细调试一下代码,并进行适当的调整,以确保播放器能够正常工作:

前端代码

确保前端代码能够处理缓冲区更新并在足够的数据被缓冲后播放音频:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                var queue = [];
                var isAppending = false;
                var totalBufferedDuration = 0;
                var requiredBufferDuration = 2;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending && !sourceBuffer.updating) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    if (sourceBuffer.buffered.length > 0) {
                        totalBufferedDuration = sourceBuffer.buffered.end(0);

                        if (totalBufferedDuration >= requiredBufferDuration && audio.paused) {
                            audio.play();
                        }
                    }
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('/audio')
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>
后端代码

确保后端正确返回音频数据:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

检查和调试

  1. 确保 MIME 类型匹配:确保 mediaSource.addSourceBuffer('audio/mpeg') 的类型和后端返回的 Content-Typeaudio/mpeg
  2. 检查浏览器控制台:确保没有其他错误信息。
  3. 验证数据是否到达:在 sourceBuffer.appendBuffer 之前打印日志,验证数据块确实到达前端并被处理。
  4. 尝试不同的数据块大小:测试不同的 CHUNK_SIZE 以确保数据能够顺利传输并缓冲。

以上步骤应确保播放器在接收到足够的数据后开始播放音频。如果问题仍然存在,请检查网络请求和响应的详细信息,并确认数据格式和内容正确。

为了确保使用 MediaSource API 进行流式音频播放,前端代码和后端返回的数据需要正确匹配,并且前端代码需要正确处理缓冲区的填充和播放逻辑。以下是一个更详细的检查和调试步骤,以确保音频流能够正常播放。

前端代码

确保前端代码能够正确处理 MediaSource API 和 SourceBuffer,并能够在缓冲区填充足够数据后开始播放:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                var queue = [];
                var isAppending = false;
                var totalBufferedDuration = 0;
                var requiredBufferDuration = 2;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending && !sourceBuffer.updating) {
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    if (sourceBuffer.buffered.length > 0) {
                        totalBufferedDuration = sourceBuffer.buffered.end(0);

                        if (totalBufferedDuration >= requiredBufferDuration && audio.paused) {
                            audio.play();
                        }
                    }
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('http://127.0.0.1:8080/audio/loading')
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                appendNextChunk();
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

检查和调试步骤

  1. 检查浏览器控制台:打开浏览器开发者工具,检查控制台是否有任何错误或警告信息。

  2. 验证网络请求:在浏览器开发者工具的网络标签中检查/audio/loading请求是否返回音频数据。确保响应状态是200,并且响应头包含Content-Type: audio/mpeg

  3. 确保正确的音频数据:确保从服务器返回的音频数据格式正确,并且能够被audio/mpeg解码。

  4. 打印调试信息:在关键位置添加调试信息,以便了解数据流动情况。下面是添加了一些调试信息的前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            var audio = document.getElementById('audioElement');
            var mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', function () {
                console.log('MediaSource opened');
                var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                var queue = [];
                var isAppending = false;
                var totalBufferedDuration = 0;
                var requiredBufferDuration = 2;

                function appendNextChunk() {
                    if (queue.length > 0 && !isAppending && !sourceBuffer.updating) {
                        console.log('Appending next chunk');
                        isAppending = true;
                        sourceBuffer.appendBuffer(queue.shift());
                    }
                }

                sourceBuffer.addEventListener('updateend', function () {
                    isAppending = false;
                    console.log('Update ended, total buffered duration:', sourceBuffer.buffered.end(0));
                    if (sourceBuffer.buffered.length > 0) {
                        totalBufferedDuration = sourceBuffer.buffered.end(0);

                        if (totalBufferedDuration >= requiredBufferDuration && audio.paused) {
                            console.log('Starting audio playback');
                            audio.play();
                        }
                    }
                    appendNextChunk();
                });

                sourceBuffer.addEventListener('error', function (e) {
                    console.error('SourceBuffer error:', e);
                });

                fetchAudioData();
            });

            function fetchAudioData() {
                fetch('http://127.0.0.1:8080/audio/loading')
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    console.log('Stream ended');
                                    mediaSource.endOfStream();
                                    return;
                                }
                                queue.push(value);
                                console.log('Received data chunk, queue length:', queue.length);
                                appendNextChunk();
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            }
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

后端代码

确保后端返回的音频数据能够被前端正确识别和解码:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio/loading")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

通过上述步骤和代码,你可以逐步排除可能的问题,确保音频流能够正确播放。如果问题仍然存在,请提供浏览器控制台中的具体错误信息,以便进一步调试。

前端 这种方式,不再使用 队列等等的方式,就给我一个简单的方式,直接播放

为了简化前端代码并直接播放音频流,可以移除队列机制,并且直接在数据读取时调用 appendBuffer。以下是一个简化版本的前端代码,它直接读取音频数据并播放:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            const audio = document.getElementById('audioElement');
            const mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', () => {
                const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                fetch('http://127.0.0.1:8080/audio/loading')
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                sourceBuffer.appendBuffer(value);
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            });
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

后端代码

确保后端代码能够以持续的方式发送音频数据:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio/loading")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

关键点

  1. 直接读取并追加:前端代码在读取到数据后直接调用 sourceBuffer.appendBuffer
  2. 保持简单:不再使用队列和复杂的状态管理,只是简单地读取和追加数据。
  3. 自动播放:移除播放控制逻辑,让音频在足够数据被缓冲后自动播放。

通过这种方式,简化了前端逻辑,使音频流能够更加直接地进行播放。

前端还是播放不了,再简单一些

为了简化实现,我们将移除所有复杂的逻辑,保持前端代码尽可能简单,直接将接收到的音频数据传递给播放器。

前端代码

确保前端代码尽量简单,直接读取和追加数据:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Audio Stream with MediaSource</title>
</head>
<body>
    <audio id="audioElement" controls></audio>
    <script>
        if ('MediaSource' in window) {
            const audio = document.getElementById('audioElement');
            const mediaSource = new MediaSource();
            audio.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', () => {
                const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

                fetch('http://127.0.0.1:8080/audio/loading')
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        return response.body.getReader();
                    })
                    .then(reader => {
                        function read() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                sourceBuffer.appendBuffer(value);
                                read();
                            }).catch(error => {
                                console.error('Reader read error:', error);
                            });
                        }
                        read();
                    }).catch(error => {
                        console.error('Fetching audio data failed:', error);
                    });
            });

            mediaSource.addEventListener('error', function (e) {
                console.error('MediaSource error:', e);
            });
        } else {
            console.log('The MediaSource API is not supported in this browser.');
        }
    </script>
</body>
</html>

后端代码

确保后端以持续的方式发送音频数据:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

@WebServlet("/audio/loading")
public class SlowAudioStreamServlet extends HttpServlet {

    private static final int CHUNK_SIZE = 1024; // 1 KB
    private static final int HEADER_SIZE = 128; // 假设头信息大小为128字节

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("audio/mpeg");
        OutputStream out = resp.getOutputStream();

        byte[] v3Header = createFakeV3Header();
        out.write(v3Header);
        out.flush();

        try {
            while (true) {
                byte[] chunk = createFakeAudioData(CHUNK_SIZE);
                out.write(chunk);
                out.flush();
                TimeUnit.SECONDS.sleep(1); // 每秒发送1KB数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private byte[] createFakeV3Header() {
        byte[] header = new byte[HEADER_SIZE];
        header[0] = 'V';
        header[1] = '3';
        header[2] = 'H';
        header[3] = 'D';
        for (int i = 4; i < HEADER_SIZE; i++) {
            header[i] = 0;
        }
        return header;
    }

    private byte[] createFakeAudioData(int size) {
        byte[] audioData = new byte[size];
        for (int i = 0; i < size; i++) {
            audioData[i] = (byte) ((Math.sin(2 * Math.PI * i / 44100) * 127) + 128); // 生成模拟音频数据
        }
        return audioData;
    }
}

关键点

  1. 直接读取并追加:前端代码在读取到数据后直接调用 sourceBuffer.appendBuffer
  2. 自动播放:移除播放控制逻辑,让音频在足够数据被缓冲后自动播放。
  3. 简单化:保持前端代码尽量简单,减少逻辑和状态管理。

通过这种方式,简化前端逻辑,使音频流能够更加直接地进行播放。如果问题仍然存在,请检查网络请求、响应的详细信息,并确认数据格式和内容正确。如果需要进一步调试,请查看浏览器控制台中的具体错误信息。

Logo

一站式 AI 云服务平台

更多推荐