Spring 项目,本地有一个录音文件,我需要以字节流的方式返回给前端,给我一个案例

qq_33192454 2024-07-25 14:33:01 阅读 99

在 Spring Boot 项目中,你可以使用 <code>@RestController 注解创建一个控制器,并通过 HTTP 请求以字节流的方式将本地录音文件返回给前端。以下是一个完整的示例:

创建 Spring Boot 项目:确保你已经创建了一个 Spring Boot 项目,并添加了 spring-boot-starter-web 依赖。

创建控制器:在控制器中编写代码读取本地文件并以字节流的方式返回。

package com.example.demo.controller;

import org.springframework.core.io.InputStreamResource;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpStatus;

import org.springframework.http.MediaType;

import org.springframework.http.ResponseEntity;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping("/stream")

public ResponseEntity<InputStreamResource> streamAudio() {

try {

// 本地录音文件路径

File audioFile = new File("path/to/your/audio/file.wav");

// 确保文件存在

if (!audioFile.exists()) {

return new ResponseEntity<>(HttpStatus.NOT_FOUND);

}

FileInputStream fileInputStream = new FileInputStream(audioFile);

InputStreamResource inputStreamResource = new InputStreamResource(fileInputStream);

// 设置响应头

HttpHeaders headers = new HttpHeaders();

headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

headers.setContentLength(audioFile.length());

headers.setContentDispositionFormData("attachment", audioFile.getName());

return new ResponseEntity<>(inputStreamResource, headers, HttpStatus.OK);

} catch (IOException e) {

return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);

}

}

}

运行应用:启动你的 Spring Boot 应用,然后访问 http://localhost:8080/audio/stream(假设你的应用运行在默认的 8080 端口),你应该能够以字节流的方式下载或播放录音文件。

注意事项

路径配置:确保 path/to/your/audio/file.wav 是你本地录音文件的正确路径。异常处理:上面代码简单处理了文件不存在和 IO 异常,你可以根据实际需要进行更复杂的异常处理。MediaType:根据你的音频文件类型,设置合适的 MediaType。例如,MediaType.APPLICATION_OCTET_STREAM 是通用的二进制流类型,你也可以使用 MediaType.AUDIO_WAVMediaType.AUDIO_MP3 等。

通过这种方式,前端可以通过 HTTP 请求获取到录音文件的字节流,并在浏览器中进行播放或下载处理。

若这个 文件,每隔2秒钟还会继续追加内容,针对追加的内容我还需要通过流给到前端,如何来做?

要实现一个可以持续传输动态更新内容(例如,每隔2秒追加内容)的音频流,你可以使用 Spring WebFlux 提供的响应式编程模型。 Spring WebFlux 支持服务器推送事件(SSE)和其他流媒体传输协议,可以处理这种持续数据流的场景。

下面是一个示例,演示如何通过 Spring WebFlux 实现动态更新内容的流媒体传输:

添加依赖:确保你的 pom.xml 中包含 spring-boot-starter-webflux 依赖。

xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 其他依赖 --> </dependencies>

创建控制器:在控制器中使用 Flux 来流式传输音频文件的内容。

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

// 本地录音文件路径

File audioFile = new File("path/to/your/audio/file.wav");

// 创建一个 Flux 来每隔2秒发送音频数据

return Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> {

try {

// 读取文件追加内容的逻辑

FileInputStream fileInputStream = new FileInputStream(audioFile);

byte[] buffer = new byte[1024];

int bytesRead = fileInputStream.read(buffer);

fileInputStream.close();

if (bytesRead > 0) {

DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(buffer, 0, bytesRead);

return Flux.just(ServerSentEvent.builder(dataBuffer).build());

} else {

return Flux.empty();

}

} catch (IOException e) {

e.printStackTrace();

return Flux.error(e);

}

});

}

}

解释:

依赖:引入了 spring-boot-starter-webflux 以支持 WebFlux 功能。流处理Flux.interval(Duration.ofSeconds(2)) 每隔2秒生成一个事件。读取文件:每次事件触发时读取文件内容并发送给前端。响应式传输:通过 ServerSentEvent 将数据作为 SSE 事件发送到前端。

注意事项:

文件读取位置:示例代码中每次都从头读取文件并发送全部内容,实际应用中可以实现更智能的文件读取方式,例如记录上次读取的位置并仅发送新追加的部分。前端处理:前端需要处理 SSE 事件流,可以使用 JavaScript 的 EventSource 或其他库来接收和处理音频数据流。

前端示例:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

sourceNode.connect(audioContext.destination);

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

sourceNode.buffer = buffer;

sourceNode.start(0);

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

这个示例中,前端使用 EventSource 来接收 SSE 事件,并使用 AudioContext 来处理和播放音频数据。注意实际应用中需要根据具体需求调整文件读取和音频播放逻辑。

若我不用 这种 flux 的方式来给前端,还按照正常字节流的方式 能否实现呢,

可以实现这种方式,只是逻辑会稍微复杂一些。你需要在后台实现一个长连接(例如使用 ServletOutputStream),并不断地向这个连接写入新的音频数据。这种方式要求你管理好文件的读取位置以及流的刷新。以下是一个示例:

创建控制器:使用 HttpServletResponse 直接输出字节流。

package com.example.demo.controller;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServletResponse;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.RandomAccessFile;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping("/stream")

public void streamAudio(HttpServletResponse response) {

File audioFile = new File("path/to/your/audio/file.wav");

response.setContentType("audio/wav");

try (RandomAccessFile randomAccessFile = new RandomAccessFile(audioFile, "r")) {

ServletOutputStream outputStream = response.getOutputStream();

byte[] buffer = new byte[1024];

long filePointer = 0;

while (true) {

// 设置文件指针到上次读取的位置

randomAccessFile.seek(filePointer);

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

outputStream.write(buffer, 0, bytesRead);

outputStream.flush();

filePointer += bytesRead;

} else {

// 如果没有新的数据,等待2秒后再试

Thread.sleep(2000);

}

}

} catch (IOException | InterruptedException e) {

e.printStackTrace();

}

}

}

解释

文件读取:使用 RandomAccessFile 读取文件,这样可以控制文件指针的位置。长连接:通过 ServletOutputStream 进行字节流输出,并在每次读取后刷新流。文件指针filePointer 用于记录上次读取的位置,这样每次读取新的数据时不会重复读取。等待机制:如果没有新数据,则线程等待2秒后再继续读取。

前端示例

前端可以使用标准的 HTML5 音频标签来播放流式音频数据。

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay>code>

<source src="http://localhost:8080/audio/stream" type="audio/wav">code>

Your browser does not support the audio element.

</audio>

</body>

</html>

这个示例中,前端只是简单地使用 audio 标签来播放音频流。

注意事项

长连接:这种方式会占用一个长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用 ExecutorService 或其他方式来管理并发连接。异常处理:需要更完善的异常处理机制,保证在发生错误时能够正确关闭流和文件。性能:由于需要不断读取文件并保持连接,可能会对服务器性能造成影响,需要进行性能测试和优化。

这种方式可以满足你的需求,通过长连接不断地将新的音频数据推送到前端,前端则可以持续播放接收到的音频数据。

若这个 文件,每隔2秒钟还会继续追加内容,针对追加的内容我还需要通过流给到前端,使用webflux如何来做?同时支持记录上次读取的位置并仅发送新追加的部分。

为了实现每隔2秒读取文件的新内容并通过WebFlux流式传输到前端,可以使用 Flux 来不断地读取文件的新内容并发送到客户端。我们将使用 RandomAccessFile 来记录上次读取的位置,并仅发送新追加的部分。

下面是具体的实现步骤:

引入依赖:确保你的 pom.xml 中包含 spring-boot-starter-webflux 依赖。

xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 其他依赖 --> </dependencies>

创建控制器:使用 Flux 处理文件流,记录上次读取的位置并发送新追加的部分。

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

return Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> {

try {

// 本地录音文件路径

File audioFile = new File("path/to/your/audio/file.wav");

// 使用 RandomAccessFile 记录上次读取的位置

RandomAccessFile randomAccessFile = new RandomAccessFile(audioFile, "r");

long filePointer = randomAccessFile.length();

// 创建一个新的 Flux 来每隔2秒发送音频数据

return Flux.generate(sink -> {

try {

randomAccessFile.seek(filePointer);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

filePointer += bytesRead;

DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(buffer, 0, bytesRead);

sink.next(ServerSentEvent.builder(dataBuffer).build());

} else {

sink.complete();

}

} catch (IOException e) {

sink.error(e);

}

});

} catch (IOException e) {

return Flux.error(e);

}

});

}

}

解释

依赖:引入了 spring-boot-starter-webflux 以支持 WebFlux 功能。Flux.interval:每隔2秒生成一个事件。RandomAccessFile:使用 RandomAccessFile 读取文件内容,并记录上次读取的位置。Flux.generate:在每次间隔事件中,读取新的文件内容并通过 ServerSentEvent 发送到客户端。

前端示例

前端可以使用标准的 HTML5 音频标签来播放流式音频数据,并通过 EventSource 接收 SSE 数据。

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

sourceNode.connect(audioContext.destination);

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

sourceNode.buffer = buffer;

sourceNode.start(0);

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

这个示例中,前端使用 EventSource 来接收 SSE 事件,并使用 AudioContext 来处理和播放音频数据。实际应用中需要根据具体需求调整文件读取和音频播放逻辑。

注意事项

文件读取位置:示例代码中使用 RandomAccessFilefilePointer 记录上次读取的位置,并在每次间隔事件中读取新的文件内容。长连接:SSE 使用长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用 ExecutorService 或其他方式来管理并发连接。性能:需要进行性能测试和优化,以保证在高并发情况下的性能和稳定性。

通过这种方式,前端可以持续接收服务器推送的新音频数据,并进行播放。

若这个 文件,首次给到前端的时候,是文件中的所有内容,之后每隔2秒钟还会继续追加内容,针对追加的内容我还需要通过流给到前端,使用webflux如何来做?同时支持记录上次读取的位置并仅发送新追加的部分。

为了实现首次发送文件的所有内容,然后每隔2秒发送追加的内容,并记录上次读取的位置,可以利用 Spring WebFluxRandomAccessFile 来实现。以下是一个完整的解决方案:

引入依赖:确保你的 pom.xml 中包含 spring-boot-starter-webflux 依赖。

xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 其他依赖 --> </dependencies>

创建控制器:使用 Flux 处理文件流,首先发送文件的全部内容,然后每隔2秒发送追加的内容。

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFile(audioFile, 0))

.flatMapMany(initialData -> {

long initialFilePointer = initialData.length;

return Flux.concat(

Flux.just(ServerSentEvent.builder(initialData).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

byte[] newData = readFile(audioFile, initialFilePointer);

initialFilePointer += newData.length;

return ServerSentEvent.builder(new DefaultDataBufferFactory().wrap(newData)).build();

}))

);

});

}

private byte[] readFile(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

byte[] actualData = new byte[bytesRead];

System.arraycopy(buffer, 0, actualData, 0, bytesRead);

return actualData;

} else {

return new byte[0];

}

}

}

}

解释

读取文件readFile 方法用于读取文件内容,并从指定位置开始读取。初始数据发送:在 streamAudio 方法中,首先读取文件的全部内容并发送给前端。定期发送新数据:使用 Flux.interval(Duration.ofSeconds(2)) 每隔2秒检查文件的更新部分,并发送新追加的内容。组合 Flux:通过 Flux.concat 组合初始数据和定期发送的新数据。

前端示例

前端使用标准的 HTML5 音频标签来播放流式音频数据,并通过 EventSource 接收 SSE 数据。

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

sourceNode.connect(audioContext.destination);

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

sourceNode.buffer = buffer;

sourceNode.start(0);

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

注意事项

文件读取位置:示例代码中使用 RandomAccessFileposition 记录上次读取的位置,并在每次间隔事件中读取新的文件内容。长连接:SSE 使用长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用 ExecutorService 或其他方式来管理并发连接。性能:需要进行性能测试和优化,以保证在高并发情况下的性能和稳定性。

通过这种方式,前端可以持续接收服务器推送的新音频数据,并进行播放。首次请求时发送文件的所有内容,然后每隔2秒发送新追加的内容。

上面 你给我的方式,确定,一开始的时候,给到 前端 的是文件中的全部内容,而不是1024个byte?

对不起,我之前的代码示例中确实有一个问题,它只发送了1024字节的数据。我们需要确保在初始发送时,发送文件中的所有内容,而后续每隔2秒发送追加的内容。

为了实现这个目标,我们需要在读取初始内容时读取整个文件,而不仅仅是固定大小的字节块。以下是修正后的代码:

引入依赖:确保你的 pom.xml 中包含 spring-boot-starter-webflux 依赖。

xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 其他依赖 --> </dependencies>

创建控制器:使用 Flux 处理文件流,首先发送文件的全部内容,然后每隔2秒发送追加的内容。

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFileFromStart(audioFile))

.flatMapMany(initialData -> {

long initialFilePointer = initialData.length();

return Flux.concat(

Flux.just(ServerSentEvent.builder(initialData).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer);

initialFilePointer += newDataBuffer.readableByteCount();

return ServerSentEvent.builder(newDataBuffer).build();

}))

);

});

}

private DataBuffer readFileFromStart(File file) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

byte[] buffer = new byte[(int) randomAccessFile.length()];

randomAccessFile.readFully(buffer);

return new DefaultDataBufferFactory().wrap(buffer);

}

}

private DataBuffer readNewData(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

return new DefaultDataBufferFactory().wrap(buffer, 0, bytesRead);

} else {

return new DefaultDataBufferFactory().wrap(new byte[0]);

}

}

}

}

解释

读取整个文件readFileFromStart 方法用于读取文件的全部内容。发送初始数据:在 streamAudio 方法中,首先读取文件的全部内容并发送给前端。定期发送新数据:使用 Flux.interval(Duration.ofSeconds(2)) 每隔2秒检查文件的更新部分,并发送新追加的内容。组合 Flux:通过 Flux.concat 组合初始数据和定期发送的新数据。

前端示例

前端可以使用标准的 HTML5 音频标签来播放流式音频数据,并通过 EventSource 接收 SSE 数据。

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

sourceNode.connect(audioContext.destination);

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

sourceNode.buffer = buffer;

sourceNode.start(0);

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

注意事项

文件读取位置:示例代码中使用 RandomAccessFileposition 记录上次读取的位置,并在每次间隔事件中读取新的文件内容。长连接:SSE 使用长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用 ExecutorService 或其他方式来管理并发连接。性能:需要进行性能测试和优化,以保证在高并发情况下的性能和稳定性。

通过这种方式,前端可以持续接收服务器推送的新音频数据,并进行播放。首次请求时发送文件的所有内容,然后每隔2秒发送新追加的内容。

这种方式 是否会存在,后端追加的内容,前端无法播放的情况

在流式传输音频数据的过程中,确保前端能够持续无缝播放新追加的内容,确实有一些挑战。为了确保前端能够正确播放后端追加的音频内容,必须保证:

正确的音频数据流格式:音频数据流必须是前端播放器能够理解和连续播放的格式。顺序性和连贯性:传输的数据必须是顺序且连贯的,不能有数据丢失或重复。

修改后的方案

以下是经过调整的后端代码,确保文件内容首次传输时包括所有数据,然后每隔2秒传输追加内容。并且,确保音频数据格式正确,以便前端能正确播放:

后端代码

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFileFromStart(audioFile))

.flatMapMany(initialData -> {

long initialFilePointer = initialData.readableByteCount();

return Flux.concat(

Flux.just(ServerSentEvent.builder(initialData).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer);

initialFilePointer += newDataBuffer.readableByteCount();

return ServerSentEvent.builder(newDataBuffer).build();

}))

);

});

}

private DataBuffer readFileFromStart(File file) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

byte[] buffer = new byte[(int) randomAccessFile.length()];

randomAccessFile.readFully(buffer);

return new DefaultDataBufferFactory().wrap(buffer);

}

}

private DataBuffer readNewData(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

return new DefaultDataBufferFactory().wrap(buffer, 0, bytesRead);

} else {

return new DefaultDataBufferFactory().wrap(new byte[0]);

}

}

}

}

前端代码

前端代码需要处理接收到的音频数据并确保顺序播放。一个简单的实现如下:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

let audioQueue = [];

let isPlaying = false;

function playAudioBuffer(buffer) {

let newSourceNode = audioContext.createBufferSource();

newSourceNode.buffer = buffer;

newSourceNode.connect(audioContext.destination);

newSourceNode.start();

newSourceNode.onended = () => {

if (audioQueue.length > 0) {

playAudioBuffer(audioQueue.shift());

} else {

isPlaying = false;

}

};

}

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

if (isPlaying) {

audioQueue.push(buffer);

} else {

isPlaying = true;

playAudioBuffer(buffer);

}

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

解释

音频队列:在前端维护一个音频缓冲队列 audioQueue,确保音频数据按顺序播放。音频播放逻辑:使用 AudioContext 处理和播放音频数据,确保播放结束后自动播放队列中的下一个缓冲区。初始数据传输:后端在初始连接时发送整个文件的内容,然后每隔2秒发送追加的内容。

注意事项

确保文件完整性:需要确保传输的数据完整且顺序,特别是在文件内容被追加的情况下,避免数据丢失或重复。音频格式:确保音频数据格式正确,并且前端能够识别和播放。流处理:在实际应用中,需要考虑音频流的网络延迟和丢包问题,以及处理错误和异常的机制。

通过上述方式,可以实现前端在初始请求时接收到完整的音频文件内容,并且每隔2秒接收和播放文件追加的新内容。

new DefaultDataBufferFactory().wrap 这个 是只有一个参数的啊

对,你是对的,DefaultDataBufferFactory().wrap 确实只有一个参数。为了简化代码并确保正确处理所有音频数据,我们需要正确管理和传输这些数据。以下是修正后的完整解决方案:

后端代码

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFileFromStart(audioFile))

.flatMapMany(initialData -> {

long initialFilePointer = initialData.readableByteCount();

return Flux.concat(

Flux.just(ServerSentEvent.builder(initialData).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer);

initialFilePointer += newDataBuffer.readableByteCount();

return ServerSentEvent.builder(newDataBuffer).build();

}))

);

});

}

private DataBuffer readFileFromStart(File file) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

byte[] buffer = new byte[(int) randomAccessFile.length()];

randomAccessFile.readFully(buffer);

return new DefaultDataBufferFactory().wrap(buffer);

}

}

private DataBuffer readNewData(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

byte[] actualData = new byte[bytesRead];

System.arraycopy(buffer, 0, actualData, 0, bytesRead);

return new DefaultDataBufferFactory().wrap(actualData);

} else {

return new DefaultDataBufferFactory().wrap(new byte[0]);

}

}

}

}

前端代码

前端代码需要处理接收到的音频数据并确保顺序播放。一个简单的实现如下:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

let audioQueue = [];

let isPlaying = false;

function playAudioBuffer(buffer) {

let newSourceNode = audioContext.createBufferSource();

newSourceNode.buffer = buffer;

newSourceNode.connect(audioContext.destination);

newSourceNode.start();

newSourceNode.onended = () => {

if (audioQueue.length > 0) {

playAudioBuffer(audioQueue.shift());

} else {

isPlaying = false;

}

};

}

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

if (isPlaying) {

audioQueue.push(buffer);

} else {

isPlaying = true;

playAudioBuffer(buffer);

}

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

解释

读取整个文件readFileFromStart 方法读取文件的全部内容,并在初始连接时发送给前端。读取新数据readNewData 方法从上次读取的位置开始读取文件的新内容,并每隔2秒发送给前端。前端播放逻辑:前端使用 AudioContext 处理和播放音频数据,并维护一个音频缓冲队列 audioQueue,确保音频数据按顺序播放。

注意事项

文件读取位置:示例代码中使用 RandomAccessFileposition 记录上次读取的位置,并在每次间隔事件中读取新的文件内容。长连接:SSE 使用长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用 ExecutorService 或其他方式来管理并发连接。性能:需要进行性能测试和优化,以保证在高并发情况下的性能和稳定性。

通过上述方式,可以实现前端在初始请求时接收到完整的音频文件内容,并且每隔2秒接收和播放文件追加的新内容。

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer);这一段代码中报错:Transform 'initialFilePointer' into final one element array

这是因为 initialFilePointer 是在 lambda 表达式内部修改的,而 Java 需要在 lambda 中使用的变量是 finaleffectively final。为了绕过这个限制,可以使用一个长度为1的数组来代替 long 变量,使得它可以在 lambda 表达式内部修改。以下是修改后的代码:

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFileFromStart(audioFile))

.flatMapMany(initialData -> {

long[] initialFilePointer = {initialData.readableByteCount()};

return Flux.concat(

Flux.just(ServerSentEvent.builder(initialData).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer[0]);

initialFilePointer[0] += newDataBuffer.readableByteCount();

return ServerSentEvent.builder(newDataBuffer).build();

}))

);

});

}

private DataBuffer readFileFromStart(File file) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

byte[] buffer = new byte[(int) randomAccessFile.length()];

randomAccessFile.readFully(buffer);

return new DefaultDataBufferFactory().wrap(buffer);

}

}

private DataBuffer readNewData(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

byte[] actualData = new byte[bytesRead];

System.arraycopy(buffer, 0, actualData, 0, bytesRead);

return new DefaultDataBufferFactory().wrap(actualData);

} else {

return new DefaultDataBufferFactory().wrap(new byte[0]);

}

}

}

}

解释

initialFilePointer 数组:使用一个长度为1的数组来代替 long 变量,使得它可以在 lambda 表达式内部修改。读取整个文件readFileFromStart 方法读取文件的全部内容,并在初始连接时发送给前端。读取新数据readNewData 方法从上次读取的位置开始读取文件的新内容,并每隔2秒发送给前端。

前端代码

前端代码需要处理接收到的音频数据并确保顺序播放。一个简单的实现如下:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createBufferSource();

let audioQueue = [];

let isPlaying = false;

function playAudioBuffer(buffer) {

let newSourceNode = audioContext.createBufferSource();

newSourceNode.buffer = buffer;

newSourceNode.connect(audioContext.destination);

newSourceNode.start();

newSourceNode.onended = () => {

if (audioQueue.length > 0) {

playAudioBuffer(audioQueue.shift());

} else {

isPlaying = false;

}

};

}

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data);

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

if (isPlaying) {

audioQueue.push(buffer);

} else {

isPlaying = true;

playAudioBuffer(buffer);

}

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

通过上述修改,后端能够正确读取和传输文件内容,并且前端能够持续接收和播放追加的音频数据。

请求 后端 接口 报了 CORS error

CORS(跨域资源共享)错误通常发生在浏览器阻止了从一个域(例如,http://localhost:3000)向另一个域(例如,http://localhost:8080)发起的请求。为了允许跨域请求,后端需要设置CORS配置。

在Spring Boot项目中,可以通过以下方式配置CORS:

方法1:全局配置CORS

在Spring Boot应用程序中添加一个全局的CORS配置类:

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.cors.CorsConfiguration;

import org.springframework.web.cors.CorsConfigurationSource;

import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import org.springframework.web.filter.CorsFilter;

@Configuration

public class CorsConfig {

@Bean

public CorsFilter corsFilter() {

CorsConfiguration config = new CorsConfiguration();

config.addAllowedOrigin("*"); // 允许所有来源

config.addAllowedHeader("*"); // 允许所有头

config.addAllowedMethod("*"); // 允许所有方法(POST, GET, etc)

config.setAllowCredentials(true); // 允许携带凭证

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

source.registerCorsConfiguration("/**", config);

return new CorsFilter(source);

}

}

方法2:在控制器级别配置CORS

如果你只需要对某些特定的控制器或方法启用CORS,可以使用@CrossOrigin注解:

import org.springframework.web.bind.annotation.CrossOrigin;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.time.Duration;

@RestController

@RequestMapping("/audio")

public class AudioController {

@CrossOrigin(origins = "*") // 允许所有来源跨域请求

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<DataBuffer>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFileFromStart(audioFile))

.flatMapMany(initialData -> {

long[] initialFilePointer = {initialData.readableByteCount()};

return Flux.concat(

Flux.just(ServerSentEvent.builder(initialData).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer[0]);

initialFilePointer[0] += newDataBuffer.readableByteCount();

return ServerSentEvent.builder(newDataBuffer).build();

}))

);

});

}

private DataBuffer readFileFromStart(File file) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

byte[] buffer = new byte[(int) randomAccessFile.length()];

randomAccessFile.readFully(buffer);

return new DefaultDataBufferFactory().wrap(buffer);

}

}

private DataBuffer readNewData(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

byte[] actualData = new byte[bytesRead];

System.arraycopy(buffer, 0, actualData, 0, bytesRead);

return new DefaultDataBufferFactory().wrap(actualData);

} else {

return new DefaultDataBufferFactory().wrap(new byte[0]);

}

}

}

}

确认CORS配置生效

确保在应用程序启动时CORS配置被正确加载。你可以通过检查控制台日志或使用浏览器开发者工具来确认CORS配置是否生效。

再次确认前端请求

在前端,确认你正在使用正确的URL来请求后端接口。例如:

const source = new EventSource('http://localhost:8080/audio/stream');

确保端口和路径都正确无误。

通过以上配置,应该能够解决CORS错误,使前端能够成功请求后端接口并接收音频流。

前端页面 报错:Unit8array is not defined

前端页面报错 Uint8Array is not defined 可能是因为拼写错误或者其他问题。确保拼写正确是 Uint8Array 而不是 Unit8Array

以下是修正后的前端代码,确保正确处理接收到的音频数据并确保顺序播放:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let audioQueue = [];

let isPlaying = false;

function playAudioBuffer(buffer) {

let newSourceNode = audioContext.createBufferSource();

newSourceNode.buffer = buffer;

newSourceNode.connect(audioContext.destination);

newSourceNode.start();

newSourceNode.onended = () => {

if (audioQueue.length > 0) {

playAudioBuffer(audioQueue.shift());

} else {

isPlaying = false;

}

};

}

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data); // 修正拼写错误

audioContext.decodeAudioData(audioData.buffer, function(buffer) {

if (isPlaying) {

audioQueue.push(buffer);

} else {

isPlaying = true;

playAudioBuffer(buffer);

}

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

解释

Uint8Array:确保拼写正确,Uint8Array 是一种 JavaScript 内置类型,用于处理字节数据。前端播放逻辑:维护一个音频缓冲队列 audioQueue,确保音频数据按顺序播放。

确认后端发送的数据格式

确保后端发送的音频数据格式正确,以便前端可以正确处理和播放音频数据。可以使用以下方式检查和确认数据格式:

检查后端响应:使用浏览器开发者工具检查 EventSource 接收到的数据,确保数据格式正确。测试代码:可以先测试固定的数据块,确保前端可以正确处理和播放音频数据。

通过以上修改,前端应该能够正确处理接收到的音频数据,并确保顺序播放。如果还有其他问题,请提供更多详细信息,以便进一步排查和解决。

decodeAudioData on baseAudioContext unable todecode audio data

decodeAudioData 方法返回一个 Promise,如果解码失败,需要捕获异常并处理错误。为了确保你的音频数据正确解码并播放,修改后的前端代码如下:

前端代码修正

确保在解码音频数据时正确处理错误:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let audioQueue = [];

let isPlaying = false;

function playAudioBuffer(buffer) {

let newSourceNode = audioContext.createBufferSource();

newSourceNode.buffer = buffer;

newSourceNode.connect(audioContext.destination);

newSourceNode.start();

newSourceNode.onended = () => {

if (audioQueue.length > 0) {

playAudioBuffer(audioQueue.shift());

} else {

isPlaying = false;

}

};

}

source.onmessage = function(event) {

const audioData = new Uint8Array(event.data); // 确保拼写正确

audioContext.decodeAudioData(audioData.buffer)

.then(buffer => {

if (isPlaying) {

audioQueue.push(buffer);

} else {

isPlaying = true;

playAudioBuffer(buffer);

}

})

.catch(error => {

console.error("Unable to decode audio data", error);

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

检查后端发送的数据格式

确保后端发送的数据格式正确。可能需要对数据进行 Base64 编码并在前端解码。以下是相应的后端和前端代码修改。

后端代码修正

对发送的数据进行 Base64 编码:

package com.example.demo.controller;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DefaultDataBufferFactory;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

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

import org.springframework.web.bind.annotation.RequestMapping;

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

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.nio.charset.StandardCharsets;

import java.time.Duration;

import java.util.Base64;

@RestController

@RequestMapping("/audio")

public class AudioController {

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<String>> streamAudio() {

File audioFile = new File("path/to/your/audio/file.wav");

return Mono.fromCallable(() -> readFileFromStart(audioFile))

.flatMapMany(initialData -> {

long[] initialFilePointer = {initialData.readableByteCount()};

return Flux.concat(

Flux.just(ServerSentEvent.builder(encodeToBase64(initialData)).build()),

Flux.interval(Duration.ofSeconds(2))

.flatMap(tick -> Mono.fromCallable(() -> {

DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer[0]);

initialFilePointer[0] += newDataBuffer.readableByteCount();

return ServerSentEvent.builder(encodeToBase64(newDataBuffer)).build();

}))

);

});

}

private DataBuffer readFileFromStart(File file) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

byte[] buffer = new byte[(int) randomAccessFile.length()];

randomAccessFile.readFully(buffer);

return new DefaultDataBufferFactory().wrap(buffer);

}

}

private DataBuffer readNewData(File file, long position) throws IOException {

try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {

randomAccessFile.seek(position);

byte[] buffer = new byte[1024];

int bytesRead = randomAccessFile.read(buffer);

if (bytesRead > 0) {

byte[] actualData = new byte[bytesRead];

System.arraycopy(buffer, 0, actualData, 0, bytesRead);

return new DefaultDataBufferFactory().wrap(actualData);

} else {

return new DefaultDataBufferFactory().wrap(new byte[0]);

}

}

}

private String encodeToBase64(DataBuffer dataBuffer) {

byte[] bytes = new byte[dataBuffer.readableByteCount()];

dataBuffer.read(bytes);

return Base64.getEncoder().encodeToString(bytes);

}

}

前端代码修正

在前端解码 Base64 数据:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<title>Audio Stream</title>

</head>

<body>

<audio id="audio" controls autoplay></audio>code>

<script>

const audioElement = document.getElementById('audio');

const source = new EventSource('http://localhost:8080/audio/stream');

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let audioQueue = [];

let isPlaying = false;

function playAudioBuffer(buffer) {

let newSourceNode = audioContext.createBufferSource();

newSourceNode.buffer = buffer;

newSourceNode.connect(audioContext.destination);

newSourceNode.start();

newSourceNode.onended = () => {

if (audioQueue.length > 0) {

playAudioBuffer(audioQueue.shift());

} else {

isPlaying = false;

}

};

}

source.onmessage = function(event) {

const audioDataBase64 = event.data;

const audioData = Uint8Array.from(atob(audioDataBase64), c => c.charCodeAt(0));

audioContext.decodeAudioData(audioData.buffer)

.then(buffer => {

if (isPlaying) {

audioQueue.push(buffer);

} else {

isPlaying = true;

playAudioBuffer(buffer);

}

})

.catch(error => {

console.error("Unable to decode audio data", error);

});

};

source.onerror = function(event) {

console.error("EventSource failed:", event);

source.close();

};

</script>

</body>

</html>

解释

后端编码:使用 Base64 编码音频数据,以确保数据在传输过程中不会被损坏。前端解码:使用 atob 方法将 Base64 编码的字符串转换为 Uint8Array,然后使用 decodeAudioData 解码音频数据。

通过这些修改,应该能够正确处理音频数据,并解决解码失败的问题。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。