Javascript compatibility on Safari

yahone chow
3 min readJan 26, 2021

slice

Array.prototype.slice

A simple method even through your are a newbie. It had two parameters start and end, but in so many using cases we ignore them.

Look up the MDN and tc39

let arr = [1,2,3,4,5];
let arrCopy = arr.slice();

Similar one

Arraybuffer.prototype.slice

var arr = [1,2,3,4,5,6]
var arrCopy = arr.slice();
var u8 = new Uint8Array(arr)
var u8Copy = u8.buffer.slice(); // slice without any param

The code above will working on chrome but safari cause chrome fixed that for us.

Look up the MDN and tc39 you will notice that the first param is non-ignoreable.

Its easy to mix up them. To make that correctly, just pass in a 0 to slice.

var u8Copy = u8.buffer.slice(0);

DecodeAudioData

This method returns a promise but for some older browsers they aren’t. spec.

// determine if safari
const yourAudioContext = new (window.AudioContext || window.webkitAudioContext)();
const DecodeAudioData = (buffer) => {
if(isAppleSafari){
return new Promise((resolve, reject) => {
yourAudioContext.decodeAudioData(buffer,(audioBuffer)=>{
resolve(audioBuffer);
});
})
}
return yourAudioContext.decodeAudioData(buffer);
};

For a part of files that metadata contain invalid character(in my testing they are chinese characters). They can’t be decode because the Safari thrown a null err.

You need to determine metadata.

Use the ffmpeg.

ffmpeg -i file.mp3 -vn -codec:a copy -map_metadata -1 out.mp3

Or just javascript to modify its bit data.

CreateBuffer

This method returns a AudioBuffer. Then you can called it through an AudioBufferSourceNode.

// it receive three params: numOfChannels, length, sampleRate.
let AB = audioContext.createBuffer(2, 1, 1);

that code will working on chrome but not on safari.

look up spec

… support sample rates in at least the range 8000 to 96000.

and the stack answer

… Safari’s Web Audio implementation does only support AudioBuffers with 22050 Hz and more.

So you just set it as

// it receive three params: numOfChannels, length, sampleRate.
let AB = yourAudioContext.createBuffer(2, 1, 22050);

Blob

If your creating a multiple media (video and audio) blob url and play it.

// mp3
const blob1 = new Blob([aAudioArrayBuffer])
const url1 = URL.createObjectURL(blob1);
aAudioDom.src = url1;// mp4
const blob2 = new Blob([aVideoArrayBuffer])
const url2 = URL.createObjectURL(blob2);
aVideoDom.src = url2;

that will be working on chrome but not on safari. There are two problems.

First one, you have to pass a mime type in while you get a instance of the Blob

const blob1 = new Blob([aAudioArrayBuffer],{ type : 'audio/mp3'});
const blob2 = new Blob([aVideoArrayBuffer],{ type : 'video/mp4'});

Another one is after you set the src of multiple media you need to invoke a function to update it.

aAudioDom.load();
aVideoDom.load();

ImageData

you can never delete a read-only property

const imageData = new ImageData(data, width, height);// this will thrown a error on safari
// but on chrome, it will be ignored.
delete imageData.data

InvalidStateError

If you are using AudioBufferSourceNode relative API and got a error and it said: InvalidStateError: The object is in an invalid state on the safari. you may need check your code those invokes like stop/start method.

Since the …AudioBufferSourceNode can only be played once. you have to write your owner status-controller to manage all your media buffer datas. try wrap it as a component and set a status for it while the user interact with it.

But for the simple using or temporarily fixing, you just need put them inside a try-catch pair statement.

try{
anABS.stop();
anABS.disconnect();
}catch(e){
// console.log(e);
}

Wechat video events

actually this also happened on some of android devices.

Sometimes we need to packback some videos from arraybuffer-blob-url, and render it to canvas.

generally you may watch the event oncanplay or other shits events to get video base info: such as width/height.In Wechat browser, these events will never been triggerd by video it self, but after user click play.

// onloadstart ondurationchange onloadedmetadata onloadeddata onprogress oncanplay oncanplaythrough

to approach that, you have to avoid use those events or create interval/timeout func to get video’s base info.

--

--