音频在 Chrome 但不是 Safari 中播放
Audio plays in Chrome but not Safari
我有一个 angular 5 应用程序,我在其中设置了按钮的 click
处理程序来下载音频文件并播放它。我正在使用此代码来执行此操作:
onPreviewPressed(media: Media): void {
const url = ".....";
this.httpClient.get(url, {responseType: 'blob'}).subscribe(x => {
const fileReader = new FileReader();
fileReader.onloadend = () => {
const context = new ((<any>window).AudioContext || (<any>window).webkitAudioContext)();
const source = context.createBufferSource();
context.decodeAudioData(fileReader.result, buffer => {
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
}, y => {
console.info("Error: " + y);
});
};
fileReader.readAsArrayBuffer(x);
});
}
如果我转到 Chrome 中的页面并按下按钮,音频就会立即启动。如果我在 Safari 中这样做,什么也不会发生。我知道 Safari 会锁定内容,但这是对按钮单击的响应,而不是 auto-play。
音频通过 PHP 脚本从服务器发回,它正在发送 headers 以防万一:
header("Content-Type: audio/mpeg");
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($_GET['file']));
header('Cache-Control: no-cache');
不,它不是“响应按钮点击”。
为响应此单击事件,您正在启动一个异步任务。当您调用 source.start(0)
时,您的事件早已结束(或者至少不再是 "trusted user gesture"。因此他们确实会阻止此调用。
为了避免这种情况,您可以简单地将您的上下文标记为允许并保持沉默。然后,当数据可用时,您就可以无限制地启动它了:
function markContextAsAllowed(context) {
const gain = context.createGain();
gain.gain.value = 0; // silence
const osc = context.createOscillator();
osc.connect(gain);
gain.connect(context.destination);
osc.onended = e => gain.disconnect();
osc.start(0);
osc.stop(0.01);
}
onPreviewPressed(media: Media): void {
const url = ".....";
// declare in the event handler
const context = new(window.AudioContext || window.webkitAudioContext)();
const source = context.createBufferSource();
// allow context synchronously
markContextAsAllowed(context);
this.httpClient.get(url, {
responseType: 'blob'
}).subscribe(x => {
const fileReader = new FileReader();
fileReader.onloadend = () => {
context.decodeAudioData(fileReader.result, buffer => {
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
}, y => {
console.info("Error: " + y);
});
};
fileReader.readAsArrayBuffer(x);
});
}
As a fiddle since Safari doesn't like over-protected StackSnippets®
此外,我的 angular 知识非常有限,但如果 httpClient.get
支持 {responseType: 'arraybuffer'}
选项,您可以摆脱这个 FileReader 并避免使用相同的内存填充两倍数据.
最后,如果您要多次播放此音频,请考虑对其进行预取和预解码,这样您就可以避免整个异步混乱。
我有一个 angular 5 应用程序,我在其中设置了按钮的 click
处理程序来下载音频文件并播放它。我正在使用此代码来执行此操作:
onPreviewPressed(media: Media): void {
const url = ".....";
this.httpClient.get(url, {responseType: 'blob'}).subscribe(x => {
const fileReader = new FileReader();
fileReader.onloadend = () => {
const context = new ((<any>window).AudioContext || (<any>window).webkitAudioContext)();
const source = context.createBufferSource();
context.decodeAudioData(fileReader.result, buffer => {
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
}, y => {
console.info("Error: " + y);
});
};
fileReader.readAsArrayBuffer(x);
});
}
如果我转到 Chrome 中的页面并按下按钮,音频就会立即启动。如果我在 Safari 中这样做,什么也不会发生。我知道 Safari 会锁定内容,但这是对按钮单击的响应,而不是 auto-play。
音频通过 PHP 脚本从服务器发回,它正在发送 headers 以防万一:
header("Content-Type: audio/mpeg");
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($_GET['file']));
header('Cache-Control: no-cache');
不,它不是“响应按钮点击”。
为响应此单击事件,您正在启动一个异步任务。当您调用 source.start(0)
时,您的事件早已结束(或者至少不再是 "trusted user gesture"。因此他们确实会阻止此调用。
为了避免这种情况,您可以简单地将您的上下文标记为允许并保持沉默。然后,当数据可用时,您就可以无限制地启动它了:
function markContextAsAllowed(context) {
const gain = context.createGain();
gain.gain.value = 0; // silence
const osc = context.createOscillator();
osc.connect(gain);
gain.connect(context.destination);
osc.onended = e => gain.disconnect();
osc.start(0);
osc.stop(0.01);
}
onPreviewPressed(media: Media): void {
const url = ".....";
// declare in the event handler
const context = new(window.AudioContext || window.webkitAudioContext)();
const source = context.createBufferSource();
// allow context synchronously
markContextAsAllowed(context);
this.httpClient.get(url, {
responseType: 'blob'
}).subscribe(x => {
const fileReader = new FileReader();
fileReader.onloadend = () => {
context.decodeAudioData(fileReader.result, buffer => {
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
}, y => {
console.info("Error: " + y);
});
};
fileReader.readAsArrayBuffer(x);
});
}
As a fiddle since Safari doesn't like over-protected StackSnippets®
此外,我的 angular 知识非常有限,但如果 httpClient.get
支持 {responseType: 'arraybuffer'}
选项,您可以摆脱这个 FileReader 并避免使用相同的内存填充两倍数据.
最后,如果您要多次播放此音频,请考虑对其进行预取和预解码,这样您就可以避免整个异步混乱。