公司IOS APP兼容性问题
大家第一反应是,用h5audio元素就好了,何须自己实现。原因只有一个:audio元素出问题了,在公司IOS APP中出现了兼容性问题:
<audio preload="auto" id="videoPlay">
<source src={declarationUrl}/>
</audio>
- 列表页 > 详情页 详情页有个人宣言的短音频,点击播放。功能很简单。
- 到不同的详情页播放,时间久了(无规律)。就会出现播放不了的情况。
- 卸载重装APP正常,用久了再次出现。
- 尝试抓包,看上去是缓存了错误的音频(长度为0)。ios排查不出问题,坚称缓存机制没有问题。
- 压力之下,实在没办法,在小组架构的指导下,才知道还有底层API。
解决方案
排查下来,大概率还是 <audio>
自身的缓存策略和我们的 IOS webview
的缓存策略有点冲突。决定自己实现一个<audio>
,不采用缓存策略,音频完全下载以后调用底层 API
来实现播放。
组件代码:
class AudioBuffer {
constructor(props) {
this.url = props.url || ''; // 音频url
this.playBtn = props.playBtn; // 播放/暂停 按钮
this.stopBtn = props.stopBtn; // 停止 按钮
this.callback = props.callback; // 回调函数 param:play|pause|stop
this.sourceNode = null; // BufferSource对象节点
this.startedAt = 0; // 播放时间点
this.pausedAt = 0; // 暂停时间点
this.playing = false; // 播放中
['play', 'pause', 'stop', 'update', 'getCurrentTime', 'getDuration', 'init', 'bind'].forEach(method => {
this[method] = this[method].bind(this);
});
this.init();
}
// 播放
play() {
let offset = this.pausedAt;
// 如果已经播放完成,重新播放
if (offset >= this.getDuration()) {
offset = 0;
}
this.sourceNode = this.context.createBufferSource();
this.sourceNode.connect(this.context.destination);
this.sourceNode.buffer = this.buffer;
this.sourceNode.start(0, offset);
this.startedAt = this.context.currentTime - offset;
this.pausedAt = 0;
this.playing = true;
if (typeof (this.callback) === 'function') {
// console.log('------------callback: play');
this.callback('play', this);
}
this.update();
}
update() {
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
if (this.getCurrentTime() < this.getDuration()) {
window.requestAnimationFrame(this.update);
} else {
this.stop();
if (typeof (this.callback) === 'function') {
// console.log('------------callback: stop');
this.callback('stop', this);
}
}
}
// 暂停
pause() {
const elapsed = this.context.currentTime - this.startedAt;
this.stop();
this.pausedAt = elapsed;
if (typeof (this.callback) === 'function') {
// console.log('------------callback: pause');
this.callback('pause', this);
}
}
// 停止
stop() {
if (this.sourceNode) {
this.sourceNode.disconnect();
this.sourceNode.stop(0);
this.sourceNode = null;
}
this.pausedAt = 0;
this.startedAt = 0;
this.playing = false;
if (typeof (this.callback) === 'function') {
// console.log('------------callback: stop');
this.callback('stop', this);
}
}
// 已播放时间
getCurrentTime() {
if (this.pausedAt) {
return this.pausedAt;
}
if (this.startedAt) {
return this.context.currentTime - this.startedAt;
}
return 0;
}
// 总时长
getDuration() {
return this.buffer.duration;
}
// 事件绑定
bind(buffer) {
this.buffer = buffer;
if (this.stopBtn) {
this.stopBtn.addEventListener('click', () => {
this.stop();
});
}
this.playBtn.addEventListener('click', () => {
if (this.playing) {
this.pause();
} else {
this.play();
}
});
// function update() {
// window.requestAnimationFrame(update);
// // info.innerHTML = sound.getCurrentTime().toFixed(1) + '/' + sound.getDuration().toFixed(1);
// }
// update();
}
init() {
window._audioContext = window._audioContext || new (window.AudioContext || window.webkitAudioContext)();
this.context = window._audioContext;
const request = new XMLHttpRequest();
request.open('GET', this.url, true);
request.responseType = 'arraybuffer';
request.addEventListener('load', () => {
this.context.decodeAudioData(
request.response,
(buffer) => {
this.bind(buffer);
},
() => {
});
});
request.send();
}
}
export default AudioBuffer;
使用:
componentDidMount() {
const audio = this.refs.videoPlay
const url = audio.getAttribute('data-url')
if(audio && url ){
this.audio = new AudioBuffer({'url':url,'playBtn':this.refs.videoPlay})
}
}
}