在 Spring Boot 项目中,你可以使用
@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_WAV
或MediaType.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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls></audio>
<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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay>
<source src="http://localhost:8080/audio/stream" type="audio/wav">
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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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
来处理和播放音频数据。实际应用中需要根据具体需求调整文件读取和音频播放逻辑。
注意事项
- 文件读取位置:示例代码中使用
RandomAccessFile
和filePointer
记录上次读取的位置,并在每次间隔事件中读取新的文件内容。 - 长连接:SSE 使用长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用
ExecutorService
或其他方式来管理并发连接。 - 性能:需要进行性能测试和优化,以保证在高并发情况下的性能和稳定性。
通过这种方式,前端可以持续接收服务器推送的新音频数据,并进行播放。
若这个 文件,首次给到前端的时候,是文件中的所有内容,之后每隔2秒钟还会继续追加内容,针对追加的内容我还需要通过流给到前端,使用webflux如何来做?同时支持记录上次读取的位置并仅发送新追加的部分。
为了实现首次发送文件的所有内容,然后每隔2秒发送追加的内容,并记录上次读取的位置,可以利用
Spring WebFlux
和
RandomAccessFile
来实现。以下是一个完整的解决方案:
- 引入依赖:确保你的
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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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>
注意事项
- 文件读取位置:示例代码中使用
RandomAccessFile
和position
记录上次读取的位置,并在每次间隔事件中读取新的文件内容。 - 长连接: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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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>
注意事项
- 文件读取位置:示例代码中使用
RandomAccessFile
和position
记录上次读取的位置,并在每次间隔事件中读取新的文件内容。 - 长连接: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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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
,确保音频数据按顺序播放。
注意事项
- 文件读取位置:示例代码中使用
RandomAccessFile
和position
记录上次读取的位置,并在每次间隔事件中读取新的文件内容。 - 长连接:SSE 使用长连接,如果同时有多个用户访问,可能会导致资源耗尽。可以考虑使用
ExecutorService
或其他方式来管理并发连接。 - 性能:需要进行性能测试和优化,以保证在高并发情况下的性能和稳定性。
通过上述方式,可以实现前端在初始请求时接收到完整的音频文件内容,并且每隔2秒接收和播放文件追加的新内容。
DataBuffer newDataBuffer = readNewData(audioFile, initialFilePointer);这一段代码中报错:Transform 'initialFilePointer' into final one element array
这是因为
initialFilePointer
是在 lambda 表达式内部修改的,而 Java 需要在 lambda 中使用的变量是
final
或
effectively 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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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">
<head>
<meta charset="UTF-8">
<title>Audio Stream</title>
</head>
<body>
<audio id="audio" controls autoplay></audio>
<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
解码音频数据。
通过这些修改,应该能够正确处理音频数据,并解决解码失败的问题。
版权归原作者 qq_33192454 所有, 如有侵权,请联系我们删除。