后端 while True一直每隔 两秒 向流中写入音频数据,但第一次的时候,直接放置了500KB的音频数据,前端如何只根据后端返回的第一段流,就解析出来这段流的可播放时长;

要实现前端解析后端返回的第一段流的可播放时长,你可以使用MediaSource Extensions (MSE) API 和 AudioContext API 结合来处理这个问题。MSE 允许你动态地创建媒体流并将其附加到HTMLMediaElement,而AudioContext可以用于解析音频数据并获取其时长。

下面是一个详细的解决方案:

后端代码

假设后端每隔两秒向流中写入音频数据,但第一次放置500KB的音频数据:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
public class AudioStreamController {

    private static final Path AUDIO_FILE_PATH = Paths.get("/path/to/your/audio/file.mp3");
    private static final int BUFFER_SIZE = 4096;

    @GetMapping("/audio-stream")
    public void streamAudio(HttpServletResponse response) throws IOException, InterruptedException {
        response.setContentType("audio/mpeg");
        response.setHeader("Connection", "keep-alive");

        byte[] initialBuffer = new byte[500 * 1024]; // 500KB buffer for the first write
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead;

        try (OutputStream outputStream = response.getOutputStream()) {
            // Read the initial 500KB data
            try (var inputStream = Files.newInputStream(AUDIO_FILE_PATH)) {
                bytesRead = inputStream.read(initialBuffer);
                outputStream.write(initialBuffer, 0, bytesRead);
                outputStream.flush();
            }

            // Keep writing remaining data every 2 seconds
            while (true) {
                try (var inputStream = Files.newInputStream(AUDIO_FILE_PATH)) {
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                        outputStream.flush();
                        Thread.sleep(2000); // Wait for 2 seconds
                    }
                }
            }
        }
    }
}

前端代码

前端代码使用MediaSourceAudioContext来解析音频流的可播放时长:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Stream</title>
</head>
<body>
    <button id="playButton">Play Audio</button>
    <audio id="audioPlayer" controls></audio>
    <div id="duration"></div>
    <script>
        document.getElementById('playButton').addEventListener('click', () => {
            const audioElement = document.getElementById('audioPlayer');
            const mediaSource = new MediaSource();
            audioElement.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', () => {
                const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
                fetch('/audio-stream')
                    .then(response => {
                        const reader = response.body.getReader();
                        let initialBuffer = [];

                        function readChunk() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                if (initialBuffer.length === 0) {
                                    initialBuffer = value;
                                    getDuration(initialBuffer);
                                }
                                sourceBuffer.appendBuffer(value);
                                readChunk();
                            });
                        }
                        readChunk();
                    });
            });

            function getDuration(buffer) {
                const audioContext = new (window.AudioContext || window.webkitAudioContext)();
                audioContext.decodeAudioData(buffer.buffer.slice(0, buffer.length), decodedData => {
                    const duration = decodedData.duration;
                    document.getElementById('duration').innerText = `Duration: ${duration.toFixed(2)} seconds`;
                });
            }
        });
    </script>
</body>
</html>

关键点

  1. 后端:

    • 在第一次写入时发送500KB的音频数据。
    • 每隔两秒写入后续的音频数据。
  2. 前端:

    • 使用MediaSource API处理流式音频数据。
    • 使用AudioContext API解析初始500KB数据的时长。

注意事项

  • 初始500KB数据量: 确保500KB的音频数据足够大,以包含足够的音频帧来计算时长。
  • 跨域问题: 确保服务器启用了CORS,以允许前端访问音频流。
  • 浏览器支持: 确保浏览器支持MediaSourceAudioContext API。

通过这种方式,前端可以在接收到后端的初始音频流数据后,解析并显示该音频数据的可播放时长。

Failed to execute appendBuffer on sourceBuffer this sourceBuffer is still processing an appendBuffer or remove operation 报了 上面的错

这个错误通常是由于在前一个 appendBuffer 操作完成之前,另一个 appendBuffer 操作被调用了。为了避免这个问题,需要确保每次调用 appendBuffer 之前,SourceBuffer 处于空闲状态。你可以通过监听 updateend 事件来确保 SourceBuffer 空闲时才进行下一次 appendBuffer 操作。

以下是修正后的前端代码,使用队列来管理 appendBuffer 操作,确保每次只有一个 appendBuffer 操作在进行:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Stream</title>
</head>
<body>
    <button id="playButton">Play Audio</button>
    <audio id="audioPlayer" controls></audio>
    <div id="duration"></div>
    <script>
        document.getElementById('playButton').addEventListener('click', () => {
            const audioElement = document.getElementById('audioPlayer');
            const mediaSource = new MediaSource();
            audioElement.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', () => {
                const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
                const fetchQueue = [];
                let initialBufferReceived = false;

                fetch('/audio-stream')
                    .then(response => {
                        const reader = response.body.getReader();

                        function processBuffer(buffer) {
                            if (!initialBufferReceived) {
                                initialBufferReceived = true;
                                getDuration(buffer);
                            }

                            if (sourceBuffer.updating || fetchQueue.length > 0) {
                                fetchQueue.push(buffer);
                            } else {
                                sourceBuffer.appendBuffer(buffer);
                            }
                        }

                        sourceBuffer.addEventListener('updateend', () => {
                            if (fetchQueue.length > 0) {
                                sourceBuffer.appendBuffer(fetchQueue.shift());
                            }
                        });

                        function readChunk() {
                            reader.read().then(({ done, value }) => {
                                if (done) {
                                    mediaSource.endOfStream();
                                    return;
                                }
                                processBuffer(value);
                                readChunk();
                            });
                        }

                        readChunk();
                    });
            });

            function getDuration(buffer) {
                const audioContext = new (window.AudioContext || window.webkitAudioContext)();
                audioContext.decodeAudioData(buffer.buffer.slice(0, buffer.length))
                    .then(decodedData => {
                        const duration = decodedData.duration;
                        document.getElementById('duration').innerText = `Duration: ${duration.toFixed(2)} seconds`;
                    })
                    .catch(error => {
                        console.error('Error decoding audio data:', error);
                    });
            }
        });
    </script>
</body>
</html>

关键点

  1. 队列管理:

    • 使用一个数组 fetchQueue 来保存待处理的缓冲区。
    • 每次 appendBuffer 操作前,检查 sourceBuffer 是否在更新状态(sourceBuffer.updating)。
  2. 事件监听:

    • 监听 sourceBufferupdateend 事件。当 sourceBuffer 完成更新后,从队列中取出下一个缓冲区进行处理。
  3. 首次缓冲处理:

    • 在接收到第一个缓冲区时,通过 getDuration 方法使用 AudioContext 解析音频数据,并计算音频时长。

通过这种方式,确保每次只有一个 appendBuffer 操作在进行,避免了并发处理带来的错误。

后端解析音频文件的时长,从指定时刻开始读取流‘

为了在后端解析音频文件的时长并从指定时刻开始读取流,可以使用Java中的javax.sound.sampled包来处理音频文件。你可以使用AudioInputStream来读取音频数据并计算时长。此外,可以使用RandomAccessFile来从指定位置读取音频数据流。

下面是一个示例,展示如何在Spring Boot后端解析音频文件的时长,并从指定时刻开始读取流:

Maven依赖

首先,在你的pom.xml文件中添加必要的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.googlecode.soundlibs</groupId>
        <artifactId>mp3spi</artifactId>
        <version>1.9.5-1</version>
    </dependency>
    <dependency>
        <groupId>com.googlecode.soundlibs</groupId>
        <artifactId>tritonus-share</artifactId>
        <version>0.3.6-2</version>
    </dependency>
</dependencies>
控制器类

创建一个Spring Boot控制器类来处理音频文件:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.sound.sampled.*;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

@RestController
public class AudioStreamController {

    private static final String AUDIO_FILE_PATH = "/path/to/your/audio/file.mp3";

    @GetMapping("/audio-duration")
    public String getAudioDuration() throws UnsupportedAudioFileException, IOException {
        File audioFile = new File(AUDIO_FILE_PATH);
        AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(audioFile);
        if (fileFormat instanceof AudioFileFormat) {
            long microseconds = (Long) fileFormat.properties().get("duration");
            int seconds = (int) (microseconds / 1000000);
            return "Audio duration: " + seconds + " seconds";
        }
        return "Could not determine audio duration";
    }

    @GetMapping("/audio-stream")
    public void streamAudio(@RequestParam long startMillis, HttpServletResponse response) throws IOException, UnsupportedAudioFileException {
        File audioFile = new File(AUDIO_FILE_PATH);
        AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(audioFile);
        AudioFormat format = fileFormat.getFormat();
        long frameSize = format.getFrameSize();
        float frameRate = format.getFrameRate();

        // Calculate the starting frame based on the start time in milliseconds
        long startFrame = (long) (startMillis / 1000.0 * frameRate);

        response.setContentType("audio/mpeg");
        response.setHeader("Connection", "keep-alive");

        try (RandomAccessFile raf = new RandomAccessFile(audioFile, "r");
             OutputStream outputStream = response.getOutputStream()) {
            raf.seek(startFrame * frameSize); // Move to the starting frame

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = raf.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                outputStream.flush();
            }
        }
    }
}

关键点

  1. 解析音频时长:

    • 使用AudioSystem.getAudioFileFormat获取音频文件格式信息。
    • 从文件格式信息中提取duration属性来获取音频时长。
  2. 从指定时刻读取流:

    • 计算从指定时间开始的帧数。
    • 使用RandomAccessFile定位到指定帧的位置并读取音频数据。

前端代码

前端可以发送请求以获取音频文件的时长和从指定时刻开始读取流。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Stream</title>
</head>
<body>
    <button id="playButton">Play Audio from 10 seconds</button>
    <audio id="audioPlayer" controls></audio>
    <div id="duration"></div>
    <script>
        document.getElementById('playButton').addEventListener('click', () => {
            const audioElement = document.getElementById('audioPlayer');
            audioElement.src = '/audio-stream?startMillis=10000'; // Start from 10 seconds
            audioElement.play();
        });

        fetch('/audio-duration')
            .then(response => response.text())
            .then(duration => {
                document.getElementById('duration').innerText = duration;
            })
            .catch(error => console.error('Error fetching audio duration:', error));
    </script>
</body>
</html>

关键点

  1. 获取音频时长:

    • 前端通过fetch请求/audio-duration端点获取音频时长并显示。
  2. 从指定时刻开始播放:

    • 在点击播放按钮时,将音频源设置为从指定时间开始的流,并调用play方法播放音频。

通过这种方式,你可以在后端解析音频文件的时长并从指定时刻开始读取流,前端可以根据需要播放指定时刻的音频数据。

后端解析音频文件的时长,从音频结尾处的前5秒钟开始读取流‘

为了在后端解析音频文件的时长并从音频结尾处的前5秒钟开始读取流,你可以结合使用 javax.sound.sampled 包来解析音频文件的时长,并使用 RandomAccessFile 从指定位置读取音频数据流。

后端代码示例

假设你使用 Spring Boot 来实现这一功能:

Maven 依赖

确保在你的 pom.xml 文件中添加必要的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.googlecode.soundlibs</groupId>
        <artifactId>mp3spi</artifactId>
        <version>1.9.5-1</version>
    </dependency>
    <dependency>
        <groupId>com.googlecode.soundlibs</groupId>
        <artifactId>tritonus-share</artifactId>
        <version>0.3.6-2</version>
    </dependency>
</dependencies>
控制器类

创建一个 Spring Boot 控制器类来处理音频文件:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.sound.sampled.*;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

@RestController
public class AudioStreamController {

    private static final String AUDIO_FILE_PATH = "/path/to/your/audio/file.mp3";

    @GetMapping("/audio-duration")
    public String getAudioDuration() throws UnsupportedAudioFileException, IOException {
        File audioFile = new File(AUDIO_FILE_PATH);
        AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(audioFile);
        if (fileFormat instanceof AudioFileFormat) {
            long microseconds = (Long) fileFormat.properties().get("duration");
            int seconds = (int) (microseconds / 1000000);
            return "Audio duration: " + seconds + " seconds";
        }
        return "Could not determine audio duration";
    }

    @GetMapping("/audio-stream")
    public void streamAudio(@RequestParam(required = false, defaultValue = "5") int lastSeconds, HttpServletResponse response) throws IOException, UnsupportedAudioFileException {
        File audioFile = new File(AUDIO_FILE_PATH);
        AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(audioFile);
        AudioFormat format = fileFormat.getFormat();
        long frameSize = format.getFrameSize();
        float frameRate = format.getFrameRate();

        // 计算音频时长
        long microseconds = (Long) fileFormat.properties().get("duration");
        int durationSeconds = (int) (microseconds / 1000000);

        // 计算从音频结尾前指定秒数开始的帧
        long startMillis = Math.max(0, (durationSeconds - lastSeconds) * 1000);
        long startFrame = (long) (startMillis / 1000.0 * frameRate);

        response.setContentType("audio/mpeg");
        response.setHeader("Connection", "keep-alive");

        try (RandomAccessFile raf = new RandomAccessFile(audioFile, "r");
             OutputStream outputStream = response.getOutputStream()) {
            raf.seek(startFrame * frameSize); // 移动到起始帧

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = raf.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                outputStream.flush();
            }
        }
    }
}

前端代码

前端可以发送请求以获取音频文件的时长和从音频结尾处的前5秒钟开始读取流。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Stream</title>
</head>
<body>
    <button id="playButton">Play Last 5 Seconds</button>
    <audio id="audioPlayer" controls></audio>
    <div id="duration"></div>
    <script>
        document.getElementById('playButton').addEventListener('click', () => {
            const audioElement = document.getElementById('audioPlayer');
            audioElement.src = '/audio-stream?lastSeconds=5'; // 从结尾前5秒开始
            audioElement.play();
        });

        fetch('/audio-duration')
            .then(response => response.text())
            .then(duration => {
                document.getElementById('duration').innerText = duration;
            })
            .catch(error => console.error('Error fetching audio duration:', error));
    </script>
</body>
</html>

关键点

  1. 解析音频时长

    • 使用 AudioSystem.getAudioFileFormat 获取音频文件格式信息。
    • 从文件格式信息中提取 duration 属性来获取音频时长。
  2. 从指定时刻读取流

    • 计算从音频结尾前指定秒数开始的帧数。
    • 使用 RandomAccessFile 定位到指定帧的位置并读取音频数据。
  3. 前端

    • 通过 fetch 请求 /audio-duration 端点获取音频时长并显示。
    • 在点击播放按钮时,将音频源设置为从结尾前指定时间开始的流,并调用 play 方法播放音频。

通过这种方式,你可以在后端解析音频文件的时长,并从音频结尾处的前5秒钟开始读取流,前端可以根据需要播放指定时刻的音频数据。

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值