我正在构建一个跨平台网络应用程序,其中音频在服务器上即时生成,并可能通过 HTML5 音频元素实时流式传输到浏览器客户端。在浏览器上,我将拥有必须与播放的音频精确同步的 Javascript 驱动的动画。 “精确”意味着音频和动画之间的间隔必须在一秒以内,最好在 250 毫秒以内(想想口型同步)。由于各种原因,我无法在服务器上制作音频和动画并直播生成的视频。
理想情况下,服务器上的音频生成和浏览器上的音频播放之间几乎没有或没有延迟,但我的理解是延迟将难以控制,可能在 3-7 秒范围内(浏览器- 、环境、网络和月相)。不过,如果我可以精确地实时测量实际延迟,以便我的浏览器 Javascript 知道何时显示正确的动画帧,我就可以处理这个问题。
因此,我需要精确测量将音频传输到流媒体服务器(Icecast?)与托管扬声器的计算机上的扬声器发出的音频之间的延迟。一些蓝天的可能性:
将元数据添加到音频流,并从播放的音频中解析它(我知道使用标准音频元素这是不可能的)
在音频中添加短暂的纯静音,然后在浏览器上检测它们(音频元素可以产生实际的音频样本吗?)
向服务器和浏览器查询各种缓冲区深度
用 Javascript 解码流式音频,然后获取元数据
关于如何做到这一点有什么想法吗?
最佳答案
利用timeupdate
<audio>
的事件元素,每秒触发三到四次,通过检查 .currentTime
在媒体流期间执行精确的动画。的 <audio>
元素。动画或过渡每秒可以启动或停止数次。
如果浏览器可用,您可以使用 fetch()
请求音频资源,地址为 .then()
返回 response.body.getReader()
返回 ReadableStream
资源的;创建一个新的 MediaSource
对象,设置 <audio>
或 new Audio()
.src
至 objectURL
的 MediaSource
;在 .read()
附加第一个流 block 链接.then()
至 sourceBuffer
的 MediaSource
与 .mode
设置为 "sequence"
;将剩余的 block 附加到 sourceBuffer
在 sourceBuffer
updateend
事件。
如果fetch()
response.body.getReader()
在浏览器中不可用,您仍然可以使用 timeupdate
或 progress
<audio>
的事件要检查的元素 .currentTime
,在流媒体播放所需的秒数开始或停止动画或过渡。
使用 canplay
<audio>
的事件当流在 MediaSource
处积累了足够的缓冲区时播放媒体的元素继续播放。
您可以使用属性设置为对应于 .currentTime
的数字的对象的 <audio>
动画应该出现的位置,值设置为 css
应设置动画以执行精确动画的元素的属性。
在 javascript
下面,动画每二十秒出现一次,从0
开始。 ,每六十秒一次,直到媒体播放结束。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<style>
body {
width: 90vw;
height: 90vh;
background: #000;
transition: background 1s;
}
span {
font-family: Georgia;
font-size: 36px;
opacity: 0;
}
</style>
</head>
<body>
<audio controls></audio>
<br>
<span></span>
<script type="text/javascript">
window.onload = function() {
var url = "/path/to/audio";
// given 240 seconds total duration of audio
// 240/12 = 20
// properties correspond to `<audio>` `.currentTime`,
// values correspond to color to set at element
var colors = {
0: "red",
20: "blue",
40: "green",
60: "yellow",
80: "orange",
100: "purple",
120: "violet",
140: "brown",
160: "tan",
180: "gold",
200: "sienna",
220: "skyblue"
};
var body = document.querySelector("body");
var mediaSource = new MediaSource;
var audio = document.querySelector("audio");
var span = document.querySelector("span");
var color = window.getComputedStyle(body)
.getPropertyValue("background-color");
//console.log(mediaSource.readyState); // closed
var mimecodec = "audio/mpeg";
audio.oncanplay = function() {
this.play();
}
audio.ontimeupdate = function() {
// 240/12 = 20
var curr = Math.round(this.currentTime);
if (colors.hasOwnProperty(curr)) {
// set `color` to `colors[curr]`
color = colors[curr]
}
// animate `<span>` every 60 seconds
if (curr % 60 === 0 && span.innerHTML === "") {
var t = curr / 60;
span.innerHTML = t + " minute" + (t === 1 ? "" : "s")
+ " of " + Math.round(this.duration) / 60
+ " minutes of audio";
span.animate([{
opacity: 0
}, {
opacity: 1
}, {
opacity: 0
}], {
duration: 2500,
iterations: 1
})
.onfinish = function() {
span.innerHTML = ""
}
}
// change `background-color` of `body` every 20 seconds
body.style.backgroundColor = color;
console.log("current time:", curr
, "current background color:", color
, "duration:", this.duration);
}
// set `<audio>` `.src` to `mediaSource`
audio.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener("sourceopen", sourceOpen);
function sourceOpen(event) {
// if the media type is supported by `mediaSource`
// fetch resource, begin stream read,
// append stream to `sourceBuffer`
if (MediaSource.isTypeSupported(mimecodec)) {
var sourceBuffer = mediaSource.addSourceBuffer(mimecodec);
// set `sourceBuffer` `.mode` to `"sequence"`
sourceBuffer.mode = "sequence";
fetch(url)
// return `ReadableStream` of `response`
.then(response => response.body.getReader())
.then(reader => {
var processStream = (data) => {
if (data.done) {
return;
}
// append chunk of stream to `sourceBuffer`
sourceBuffer.appendBuffer(data.value);
}
// at `sourceBuffer` `updateend` call `reader.read()`,
// to read next chunk of stream, append chunk to
// `sourceBuffer`
sourceBuffer.addEventListener("updateend", function() {
reader.read().then(processStream);
});
// start processing stream
reader.read().then(processStream);
// do stuff `reader` is closed,
// read of stream is complete
return reader.closed.then(() => {
// signal end of stream to `mediaSource`
mediaSource.endOfStream();
return mediaSource.readyState;
})
})
// do stuff when `reader.closed`, `mediaSource` stream ended
.then(msg => console.log(msg))
}
// if `mimecodec` is not supported by `MediaSource`
else {
alert(mimecodec + " not supported");
}
};
}
</script>
</body>
</html>
关于javascript - HTML5 音频流 : precisely measure latency?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38768375/